Bash - з’єднайте кожен рядок файлу


10

Це питання сильно пов'язане з цим і цим питанням. У мене є файл, який містить кілька рядків, де кожен рядок - це шлях до файлу. Тепер я хочу спаровувати кожен рядок з кожною різною лінією (не самою). Також пара для моїх цілей A Bдорівнює B Aпарі, тому слід випускати лише одну з цих комбінацій.

Приклад

files.dat читається так у скороченому позначенні, кожна літера - це шлях до файлу (абсолютний або відносний)

a
b
c
d
e

Тоді мій результат повинен виглядати приблизно так:

a b
a c
a d
a e
b c
b d
b e
c d
c e
d e

Переважно я хотів би вирішити це в башті. На відміну від інших питань, мій список файлів досить малий (близько 200 рядків), тому використання циклів та ємності оперативної пам’яті не створює проблем.


Це повинно бути власне в bash , або просто щось доступне в командному рядку bash? Інші утиліти краще розміщувати для обробки тексту.
Джефф Шаллер

@JeffSchaller Щось доступне через командну лінію bash. Мені було трохи незрозуміло, вибачте
Енно

Це майже стає кодом Golf : P
Річард де Віт

3
Як правило, доки вам потрібно зробити щось нетривіальне, використовуйте свою улюблену мову сценаріїв над BASH. Це буде менш тендітним (наприклад, проти спеціальних символів або пробілів), і набагато простіше розширюватися, коли вам це потрібно (якщо вам потрібно три, або відфільтрувати деякі з них). Python або Perl повинні бути встановлені майже в будь-якому вікні Linux, тому вони є хорошим вибором (якщо ви не працюєте над вбудованими системами, наприклад, Busybox).
Davidmh

Відповіді:


7

Використовуйте цю команду:

awk '{ name[$1]++ }
    END { PROCINFO["sorted_in"] = "@ind_str_asc"
        for (v1 in name) for (v2 in name) if (v1 < v2) print v1, v2 }
        ' files.dat

PROCINFOможе бути gawkрозширенням. Якщо ваш awkне підтримує його, просто залиште PROCINFO["sorted_in"] = "@ind_str_asc"лінію та підключіть висновок sort(якщо ви хочете, щоб вихід був відсортований).

(Для цього не потрібно сортувати вхід.)


8
$ join -j 2 -o 1.1,2.1 file file | awk '!seen[$1,$2]++ && !seen[$2,$1]++'
a b
a c
a d
a e
b c
b d
b e
c d
c e
d e

Це передбачає, що жодна рядок у вхідному файлі не містить жодного пробілу. Також передбачається, що файл сортується .

joinКоманда створює повне декартовій твір рядків у файлі. Це робиться шляхом з'єднання файлу з самим собою в неіснуючому полі. Нестандартне -j 2може бути замінено на -1 2 -2 2(але не, -j2якщо ви не використовуєте GNU join).

awkКоманда зчитує результат цього і виводить тільки результати , які є парами , які ще не бачили.


Що ви маєте на увазі під "сортування файлу"? Відсортовано за якими критеріями?
Енно

@Enno Сортував так, як sort -bби це сортувало . joinвимагають відсортовані вхідні файли.
Kusalananda

8

pythonРозчин. Вхідний файл подається itertools.combinationsзі стандартної бібліотеки, яка генерує двосторонні кортежі, відформатовані та надруковані на стандартний вихід.

python3 -c 'from itertools import combinations
with open("file") as f:
    lines = (line.rstrip() for line in f)
    lines = ("{} {}".format(x, y) for x, y in combinations(lines, 2))
    print(*lines, sep="\n")
'

6

Якщо ви rubyвстановили:

$ ruby -0777 -F'\n' -lane '$F.combination(2) { |c| puts c.join(" ")}' ip.txt
a b
a c
a d
a e
b c
b d
b e
c d
c e
d e
  • -0777 slurp весь файл (має бути нормально, як це згадується в ОП, що розмір файлу невеликий)
  • -F'\n'розділити на основі нового рядка, тому кожен рядок буде елементом у $Fмасиві
  • $F.combination(2)генерувати комбіновані 2елементи за раз
  • { |c| puts c.join(" ")} друкувати за потребою
  • якщо вхідний файл може містити дублікати, використовуйте $F.uniq.combination(2)


для 3 елементів одночасно:

$ ruby -0777 -F'\n' -lane '$F.combination(3) { |c| puts c.join(" ")}' ip.txt
a b c
a b d
a b e
a c d
a c e
a d e
b c d
b c e
b d e
c d e


З perl(не загальне)

$ perl -0777 -F'\n' -lane 'for $i (0..$#F) {
                             for $j ($i+1..$#F) { 
                               print "$F[$i] $F[$j]\n" } }' ip.txt
a b
a c
a d
a e
b c
b d
b e
c d
c e
d e


З awk

$ awk '{ a[NR]=$0 }
       END{ for(i=1;i<=NR;i++)
              for(j=i+1;j<=NR;j++)
                print a[i], a[j] }' ip.txt 
a b
a c
a d
a e
b c
b d
b e
c d
c e
d e

5

Ось один із чистої оболонки.

test $# -gt 1 || exit
a=$1
shift
for f in "$@"
do
  echo $a $f
done
exec /bin/sh $0 "$@"

Приклад:

~ (137) $ sh test.sh $(cat file.dat)
a b
a c
a d
a e
b c
b d
b e
c d
c e
d e
~ (138) $ 

1
Смуги для заміни команд прокладають нові рядки, тож вам краще щось подібне <file.dat xargs test.shдоtest.sh $(cat file.dat)
iruvar

1

Використовуючи Perlми можемо це зробити, як показано:

$ perl -lne '
     push @A, $_}{
     while ( @A ) {
        my $e = shift @A;
        print "$e $_" for @A;
     }
' input.txt
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.