Эта строка сворачивает файловую базу по первому столбцу.

awk '{if($1==x){i=i" "$2}else{if(NR>1){print i};i=$0};x=$1;y=$2}' test.cov <(echo)

Ввод:

1001  hisk01
1001  hisk02
1001  hisk03
1002  hisk04
1002  hisk05
1002  hisk06
1003  hisk07
1003  hisk08

Выход:

1001 hisk01 hisk02 hisk03
1002 hisk04 hisk05 hisk06
1003 hisk07 hisk08

Это работает, но я не знаю, как здесь работает <(echo). Кто-нибудь может мне помочь?

Благодарность

0
αғsнιη 18 Май 2021 в 18:08
Как говорится в ответе @Kaz ниже, <(echo) использует функцию, доступную в некоторых оболочках (включая ksh, bash, zsh), называемую заменой процесса. Вы можете прочитать хорошее краткое определение на странице Process Substitution в Википедии.
 – 
cas
19 Май 2021 в 09:17

3 ответа

<( — это «подстановка процесса», которая является функцией оболочки GNU Bourne-Again Shell (Bash). Это не в POSIX.

Подстановка процесса — это часть синтаксиса, которая расширяется до аргумента командной строки. Целевая программа может открыть аргумент, как если бы это было имя файла. Результирующий файловый дескриптор подключается к командному каналу, указанному в синтаксисе в скобках.

Иными словами, <(echo) расширяется до некоторого слова, например /magic/path/53. Когда программа получает этот путь и открывает его для файла в качестве входных данных, она получает дескриптор канала, который читается из echo.

Что делает echo? Выдает пустую строку.

Единственная разница между

some-command <(echo)

А также

some-command /dev/null

Заключается в том, что <(echo) создает пустую строку, тогда как /dev/null ничего не производит.

Вы можете представить, что <(echo) — это путь к файлу, содержащему одну пустую строку (при условии, что этот путь открыт только для ввода, а не вывода).

Здесь идея, кажется, состоит в том, чтобы гарантировать, что ввод awk содержит завершающую пустую строку. То есть, что бы ни было во входном файле test.cov, будет дополнительная пустая строка. Этого требует логика скрипта, потому что он сохраняет состояние между последовательными строками. Существует переменная i, содержимое которой зависит от предыдущей строки и печатается при поступлении следующей строки. i, рассчитанный для последней строки, никогда не печатается, поэтому без дополнительной пустой строки, исходящей из <(echo), последняя строка test.cov не будет полностью обработана.

Обратите внимание, что если в test.cov отсутствует завершающий символ новой строки, то <(echo) не просто добавит эту новую строку; несколько входных файлов для awk не просто объединяются в один символьный поток. Запись будет разделена в конце первого файла, независимо от того, присутствует ли новая строка, а второй файл создает новую запись.

Существует простой способ удалить зависимость от функции подстановки процесса из окружающей оболочки:

awk '{if($1==x){i=i" "$2}else{if(NR>1){print i};i=$0};x=$1;y=$2}; END {print i}'

Вот и все! Мы добавляем блок END, который печатает все накопленные i. Нам не нужен тест NR > 1, потому что если NR == 0, это означает, что никакие записи не были обработаны, и поэтому i не определено, печатается как ничего. Однако в этом случае выводится пустая пустая строка, чего можно избежать с помощью END {if (i) print i}.

2
Kaz 18 Май 2021 в 18:51
Спасибо за подробное объяснение. Кроме того, является ли "y=$2" одноразовым, так как он больше нигде не упоминается.
 – 
Yuxiang li
18 Май 2021 в 19:01
Что y должен был использоваться здесь i=i" "$2 вместо $2, но использование меньшего количества временных переменных допустимо, поэтому вы можете безопасно удалить y=$2 из своего кода, поскольку он избыточен.
 – 
αғsнιη
18 Май 2021 в 19:08
1
y не имеет смысла. Использование его вместо $2 при вычислении i было бы неправильным, поскольку это $2 предыдущей записи.
 – 
Kaz
18 Май 2021 в 19:13

Это просто приводит к выводу последнего содержимого, буферизованного в переменной i. Это создает пустую строку, поэтому awk запускается еще раз для этой пустой строки, и поэтому он выводит буферизованное значение в переменной i. вместо <(echo) вы можете использовать блок END{ print i }.

См. также мой этот ответ о том, как вы могли бы сделать это другими способами в обоих случаях, когда ваш ввод отсортирован/не отсортирован.

2
Community 18 Май 2021 в 18:52
1
Я вижу, большое спасибо за помощь.
 – 
Yuxiang li
18 Май 2021 в 18:24
Привет, мне также любопытно, почему END {print i} не прерывает работу awk. Не могли бы вы подробнее рассказать об этом?
 – 
Yuxiang li
18 Май 2021 в 19:49
Почему это должно сломаться? Блок END запускается только один раз после того, как awk обработает все входные строки со своего входа. это оператор exit, который прерывает работу awk, подробнее об этом читайте здесь gnu.org/software/gawk/manual/html_node/Using-BEGIN_002fEND.html
 – 
αғsнιη
18 Май 2021 в 20:04
В нем говорится, что END будет запускаться только один раз, поэтому я не понимаю, почему END{print i } может постоянно печатать результаты каждого отдельного «i». Я думал, что END{print i} напечатает только результат последней строки или последнего "i"
 – 
Yuxiang li
18 Май 2021 в 20:18
1
Разве ты не знаешь, какую команду ты запускаешь? вы можете видеть, что есть часть if(NR>1){print i}, которая что-то печатает.... это не только блок END печатает все, блок END печатает только последнее значение переменной i и больше ничего. если вы все еще не знаете, как работает END, посмотрите данное руководство по блоку END еще раз.
 – 
αғsнιη
18 Май 2021 в 22:30

У вас есть ответ на вопрос, который вы задали, но, к вашему сведению, один из наиболее распространенных способов сделать то, что вы пытаетесь сделать, это просто:

$ awk -v ORS= '$1!=prev{print rec; ORS=RS; rec=prev=$1} {rec=rec OFS $2} END{print rec}' file
1001 hisk01 hisk02 hisk03
1002 hisk04 hisk05 hisk06
1003 hisk07 hisk08
0
Ed Morton 18 Май 2021 в 19:29
Или что-то вроде datamash -W -g 1 collapse 2 <file (но вы получите значения, разделенные запятыми).
 – 
Kusalananda
18 Май 2021 в 19:34