«Именованный канал» на самом деле является очень точным названием того, чем он является — он похож на обычный канал, за исключением того, что он имеет имя (в файловой системе ).
Канал — обычный, не -названный («анонимный» ), используемый в some-command | grep pattern
, — это файл особого типа. И я имею в виду файл, вы читаете и пишете в него так же, как и в любой другой файл. Grep на самом деле не волнует¹, что он читает из канала, а не из терминала³ или из обычного файла.
Технически за кулисами происходит то, что stdin, stdout и stderr — это три открытых файла, (файловые дескрипторы ), которые передаются при каждом запуске команды. Дескрипторы файлов (, которые используются в каждом системном вызове для чтения/записи/и т. д. файлы )— это просто числа; stdin, stdout и stderr — это файловые дескрипторы 0, 1 и 2. Итак, когда ваша оболочка устанавливает some-command | grep
, она делает нечто следующее:
Запрашивает у ядра анонимный канал. Имени нет, поэтому это нельзя сделать с помощью open
, как для обычного файла — вместо этого это делается с помощью pipe
или pipe2
, которые возвращают два файловых дескриптора.⁴
Разветвляет дочерний процесс(fork()
создает копию родительского процесса; здесь открыты обе стороны канала ), копирует сторону записи -канала в fd 1 (stdout ). Ядро имеет системный вызов для копирования номеров файловых дескрипторов; это dup2()
или dup3()
. Затем он закрывает сторону чтения и другую копию стороны записи. Наконец, он использует execve
для выполнения some-command
. Так как канал равен fd 1, стандартный вывод some-command
является каналом.
Вилки другого дочернего процесса. На этот раз он дублирует сторону чтения канала на fd 0 (stdin )и выполняет grep
. Таким образом, grep будет читать из канала как стандартный ввод.
Затем он ожидает выхода обоих дочерних элементов.
В этот момент ядро замечает, что канал больше не открыт, и собирает мусор. Вот что на самом деле разрушает трубу.
Именованный канал просто дает этому анонимному каналу имя, помещая его в файловую систему. Итак, теперь любой процесс в любой момент в будущем может получить дескриптор файла для канала с помощью обычного open
системного вызова. Концептуально канал не будет уничтожен до тех пор, пока все читатели/писатели не закроют его и он unlink
не будет удален из файловой системы.²
Вот так, кстати, работают файлы в Unix.unlink
(системный вызовrm
)просто удаляет одно из имен файла; только когда все имена удалены и файл не открыт, он действительно будет удален. Несколько ответов здесь исследуют это:
СНОСКИ
grep pattern
в своей оболочке, grep
будет считываться с терминала. Единственное, что приходит на ум, это если вы собираетесь что-то вставить в терминал. $ awk -v RS='' '{ $1 = $1; print }' file
HEAD1 IF FI GH
HEAD2 PU GT
HEAD3 FG DF YT GU
Когда RS
, разделитель входных записей, пуст, а не установлен по умолчанию, символ новой строки awk
разделит ввод на записи на основе появления двух или более последовательных символов новой строки во вводе, т.е. больше пустых строк будет считаться концом записи. Обычно это называется переводом awk
в «режим абзаца».
Затем код устанавливает первое поле в себя. Это похоже на операцию -без операции, но она провоцирует awk
на повторное -формирование текущей записи для вывода. Значения по умолчанию дляORS
(разделителя выходных записей )иOFS
(разделителя выходных полей )используются (символ новой строки и символ пробела соответственно ), поэтому при печати записи все поля печатаются на одной строке с пробелами в -между ними, заканчиваясь в конце символом новой строки.
Вы можете получить поля, разделенные другими строками или символами, изменив значениеOFS
:
$ awk -v RS='' -v OFS='\t' '{ $1 = $1; print }' file
HEAD1 IF FI GH
HEAD2 PU GT
HEAD3 FG DF YT GU
$ awk -v RS='' -v OFS=',' '{ $1 = $1; print }' file
HEAD1,IF,FI,GH
HEAD2,PU,GT
HEAD3,FG,DF,YT,GU
$ awk -v RS='' -v OFS='::' '{ $1 = $1; print }' file
HEAD1::IF::FI::GH
HEAD2::PU::GT
HEAD3::FG::DF::YT::GU
Код awk
можно было бы сократить до { $1 = $1 }; 1
, где замыкающий 1
приводил бы к печати текущей записи путем безусловного вызова действия по умолчанию. Это довольно распространенный способ безусловной печати текущей записи.
Использование sed
вместо:
$ sed -e '/./ { H; $!d; }' -e 'x; y/\n/ /; s/.//' file
HEAD1 IF FI GH
HEAD2 PU GT
HEAD3 FG DF YT GU
Добавляет текущую строку в область хранения с H
, если строка содержит что-либо. Строка добавляется к пространству удержания с помощью разделительного символа новой строки. Если это была не последняя строка ввода, пробелы шаблонов отбрасываются с помощью d
, и мы сразу же продолжаем с начала со следующей строки ввода.
Если текущая строка пуста, мы меняем место хранения на x
(, что, поскольку пространство шаблона пусто, также приводит к очистке пространства хранения ), заменяет все новые строки пробелами и lop от первого символа (, который будет дополнительным символом пробела ).
С помощью awk
в режиме абзаца:
awk -v RS= -v FS='\n' -v OFS=' ' '{
for (i=1;i<=NF;i++) {
printf "%s%s", $i, (i<NF ? OFS : ORS)
}
}' file
HEAD1 IF FI GH
HEAD2 PU GT
HEAD3 FG DF YT GU
RS=
При такой установке RS
входные записи разделяются пустыми строками. FS='\n'
Таким образом, вы можете рассматривать каждую строку в каждом абзаце как поле записи. for
проходим по всем полям и с printf
печатаем их под условным выражением, известным какternary expression
:если exp1 i<NF
возвращает true, OFS
выполняется; в противном случае выполняется ORS
.