В чем разница между «cat file | ./binary »и« ./binary <файл »?

102

У меня есть двоичный файл (который я не могу изменить), и я могу сделать:

./binary < file

Я также могу сделать:

./binary << EOF
> "line 1 of file"
> "line 2 of file"
...
> "last line of file"
> EOF

Но

cat file | ./binary

дает мне ошибку. Я не знаю, почему это не работает с трубой. Во всех трех случаях содержимое файла передается на стандартный ввод двоичного файла (по-разному):

  1. bash читает файл и передает его в стандартный двоичный файл
  2. bash читает строки из стандартного ввода (до EOF) и передает его в стандартный двоичный файл
  3. cat читает и помещает строки файла в стандартный вывод, bash перенаправляет их в стандартный двоичный файл

Двоичный файл не должен заметить разницу между этими 3, насколько я понял. Может кто-нибудь объяснить, почему третий случай не работает?

Кстати: ошибка, заданная двоичным файлом :

20170116 / 125624.689 - U3000011 Не удалось прочитать файл сценария '', код ошибки '14'.

Но мой главный вопрос: какова разница для любой программы с этими 3 вариантами?

Вот некоторые подробности: я попробовал это снова с помощью strace, и на самом деле были некоторые ошибки ESPIPE (незаконный поиск) от lseek, за которыми следовал EFAULT ( неправильный адрес) при чтении прямо перед сообщением об ошибке.

Двоичный файл, который я пытался контролировать с помощью сценария ruby ​​(без использования временных файлов), является частью программы Callapi из Automic (UC4) .

Борис
источник
25
Круто, в ваш двоичный файл встроен детектор UUOC . Я хочу это.
Ксиэнн
4
Что это за ОС (чтобы мы могли сказать, что это за 14, если она должна быть ошибочной)?
Стефан
6
Несмотря на то, что это возможно для программы реагировать таким образом, что это будет stangely глючит один , что сделал. Каждая не сумасшедшая программа, которая ожидает ввода от stdin вообще, должна работать, когда stdin является tty, и если она может работать как с tty, так и с файлом, то нет особых причин не поддерживать конвейеры тоже. Вероятно, у автора программы было временное кровоизлияние, и хотя все, что isatty()возвращает ложь, будет доступным для поиска или доступным файлом ...
Хеннинг
9
Код ошибки 14 означает EFAULT. При чтении, которое происходит, если объявленный вами буфер недействителен. Я бы остановил программу, но я подозреваю, что она ищет в конце файла размер буфера для чтения данных, плохо обрабатывает тот факт, что поиск не работает, и пытается выделить отрицательный размер (не обрабатывает плохой malloc) , Передача буфера для чтения, какие ошибки с учетом буфера недопустимы.
Мэтью Ифе
3
@ xhienne Нет, в нем есть catпревентор. Похоже, что вы не можете использовать его для объединения двух файлов, как это предполагается.
jpmc26

Ответы:

150

В

./binary < file

binarysdin - это файл, открытый в режиме только для чтения. Обратите внимание, что bashфайл вообще не читается, он просто открывается для чтения по дескриптору файла 0 (stdin) процесса, в котором он выполняется binary.

В:

./binary << EOF
test
EOF

В зависимости от оболочки, binarystdin будет либо удаленным временным файлом (AT & T ksh, zsh, bash ...), который содержит test\nкак положено туда оболочкой, так и концом чтения канала ( dash, yash; и оболочка пишет test\nпараллельно на другом конце трубы). В вашем случае, если вы используете bash, это будет временный файл.

В:

cat file | ./binary

В зависимости от оболочки, binarystdin будет либо концом чтения канала, либо одним концом пары сокетов, где направление записи отключено (ksh93) и catзаписывает содержимое fileдругого конца.

Когда stdin - обычный файл (временный или нет), он доступен для поиска. binaryможет идти в начало или конец, перематывать и т. д. Он также может отображать его, делать что-то ioctl()sвроде FIEMAP / FIBMAP (при использовании <>вместо <него он может обрезать / пробивать отверстия и т. д.).

трубы и пары сокетов, с другой стороны, являются средством межпроцессного взаимодействия, кроме данных мало что binaryможно сделать read(хотя есть также некоторые операции, например, некоторые специфичные для канала, ioctl()которые он может выполнять с ними, а не с обычными файлами) ,

В большинстве случаев, это недостающее способность , seekчто приводит к приложениям к сбою / жалуются при работе с трубами, но это может быть любой из других системных вызовов, которые действительны для обычных файлов , но не на различных типах файлов (как mmap(), ftruncate(), fallocate()) , В Linux также есть большая разница в поведении, когда вы открываете, /dev/stdinкогда fd 0 находится в канале или в обычном файле.

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

$ unzip -l file.zip
Archive:  file.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       11  2016-12-21 14:43   file
---------                     -------
       11                     1 file
$ unzip -l <(cat file.zip)
     # more or less the same as cat file.zip | unzip -l /dev/stdin
Archive:  /proc/self/fd/11
  End-of-central-directory signature not found.  Either this file is not
  a zipfile, or it constitutes one disk of a multi-part archive.  In the
  latter case the central directory and zipfile comment will be found on
  the last disk(s) of this archive.
unzip:  cannot find zipfile directory in one of /proc/self/fd/11 or
        /proc/self/fd/11.zip, and cannot find /proc/self/fd/11.ZIP, period.

unzipНеобходимо прочитать индекс, хранящийся в конце файла, а затем выполнить поиск в файле для чтения членов архива. Но здесь файл (обычный в первом случае, труба во втором) задается в качестве аргумента пути unzipи unzipоткрывает его сам (обычно на fd, отличном от 0), вместо того, чтобы наследовать fd, уже открытый родителем. Он не читает zip-файлы со своего стандартного устройства. stdin в основном используется для взаимодействия с пользователем.

Если вы запустите свой binaryбез перенаправления по приглашению интерактивной оболочки, запущенной в эмуляторе терминала, то binarystdin будет унаследован от его родительской оболочки, которая сама унаследует его от своего родительского эмулятора терминала и будет Устройство pty открывается в режиме чтения + записи (что-то вроде /dev/pts/n).

Эти устройства также не доступны для поиска. Таким образом, если binaryпри работе с терминала все работает нормально, возможно, проблема не в поиске.

Если это 14 должно быть ошибочным (код ошибки, установленный при сбое системных вызовов), то в большинстве систем это будет EFAULT( неверный адрес ). read()Системный вызов потерпит неудачу с этой ошибкой , если попросил прочитать в адрес памяти , который не доступен для записи. Это будет зависеть от того, будет ли fd считывать данные из точек в канал или обычный файл, и обычно будет указывать на ошибку 1 .

binaryвозможно определяет тип файла, открытого на его стандартном вводе (с fstat()), и сталкивается с ошибкой, когда он не является ни обычным файлом, ни устройством tty.

Трудно сказать, не зная больше о приложении. Запуск его под strace(или truss/ или tuscаналогичным образом в вашей системе) может помочь нам увидеть, что такое системный вызов, если он здесь не работает.


1 Сценарий, предусмотренный Мэтью Ифе в комментарии к вашему вопросу, звучит здесь очень правдоподобно. Цитирую его:

Я подозреваю, что он пытается до конца файла получить размер буфера для чтения данных, плохо обрабатывая тот факт, что поиск не работает, и пытается выделить отрицательный размер (не обрабатывая неверный malloc). Передача буфера для чтения, какие ошибки с учетом буфера недопустимы.

Стефан Шазелас
источник
14
Очень интересно ... это первый раз, когда я услышал, что перенаправленный стандартный ввод в стиле « ./binary < fileищется»!
Дэвид З
2
@DavidZ это файл, который был отредактирован, openи он ведет себя так же, как и любой файл, который был openотредактирован. Просто так случилось, что он унаследован от родительского процесса, но это не так уж редко.
Хоббс
3
Если система содержит strace или аналогичный инструмент, его можно использовать для проверки того, какой системный вызов дает сбой.
Пабук
2
«Он также может усекать его, отображать, пробивать отверстия и т. Д.» - Ну нет. Файл открыт в режиме только для чтения. Программа должна была бы открыть это в режиме записи, чтобы сделать это. Но он не может открыть его в режиме записи, потому что нет интерфейса для непосредственного выполнения этого, а также нет интерфейса для поиска «директивной» записи каталога, соответствующей открытому файлу (что, если есть два таких зубных ряда или ноль?) , Он должен был бы проверить файл и затем проверить файловую систему на предмет с таким же номером инода. Это было бы слишком медленно.
Кевин
1
@ StéphaneChazelas: да, open("/proc/self/fd/0", O_RDWR)работает, даже на удаленных файлах. Глупый я: P. echo foo>foo; (sleep 0.5; ll -L /proc/self/fd/0; strace ./a.out; ll -L /proc/self/fd/0) < foo & sleep 0.1 && rm fooотменяет связь fooперед запуском a.out со своим перенаправленным stdin foo.
Питер Кордес
46

Вот простой пример программы, которая иллюстрирует ответ Стефана Шазела, используя lseek(2)его входные данные:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
    int c;
    off_t off;
    off = lseek(0, 10, SEEK_SET);
    if (off == -1)
    {
        perror("Error");
        return -1;
    }
    c = getchar();
    printf("%c\n", c);
}

Тестирование:

$ make seek
cc     seek.c   -o seek
$ cat foo
abcdefghijklmnopqrstuwxyz
$ ./seek < foo
k
$ ./seek <<EOF
> abcdefghijklmnopqrstuvwxyz
> EOF
k
$ cat foo | ./seek
Error: Illegal seek

Трубы недоступны для поиска, и это единственное место, где программа может жаловаться на трубы.

Мур
источник
21

Труба и перенаправление, так сказать, разные животные. Когда вы используете here-docredirection ( <<) или перенаправление stdin, < текст не появляется из ничего - он фактически входит в файловый дескриптор (или временный файл, если хотите), и именно на него будет указывать stdin двоичного файла.

В частности, вот выдержка из bash'sисходного кода, файл redir.c (версия 4.3):

/* Create a temporary file holding the text of the here document pointed to
   by REDIRECTEE, and return a file descriptor open for reading to the temp
   file.  Return -1 on any error, and make sure errno is set appropriately. */
static int
here_document_to_fd (redirectee, ri)

Таким образом, поскольку перенаправление в основном можно рассматривать как файлы, двоичные файлы могут перемещаться по ним или seek()легко перемещаться по файлу, переходя к любому байту файла.

Каналы, поскольку они представляют собой буферы объемом 64 КиБ (по крайней мере в Linux) с записью в 4096 байт или менее с гарантией, что они являются атомарными, не доступны для поиска, то есть вы не можете свободно перемещаться по ним - только для последовательного чтения. Однажды я реализовал tailкоманду в Python. 29 миллионов строк текста можно найти в микросекундах, если они перенаправлены, но если catих отредактировать по конвейеру, то ничего не поделаешь - так что все это нужно читать последовательно.

Другая возможность заключается в том, что двоичный файл может специально открыть файл и не хочет получать входные данные из канала. Обычно это делается с помощью fstat()системного вызова и проверки, поступает ли ввод из S_ISFIFOтипа файла (который обозначает канал / именованный канал).

Ваш конкретный двоичный файл, так как мы не знаем, что это такое, вероятно, пытается искать, но не может искать каналы. Рекомендуется ознакомиться с его документацией, чтобы узнать, что именно означает код ошибки 14.

ПРИМЕЧАНИЕ . Некоторые оболочки, такие как dash (Debian Almquist Shell, по умолчанию /bin/shв Ubuntu), осуществляют here-docперенаправление с внутренними каналами , поэтому могут быть недоступны для поиска. Суть остается той же - каналы являются последовательными и не могут легко перемещаться, и попытки сделать это приведут к ошибкам.

Сергей Колодяжный
источник
В ответе Стефана говорится, что here-документы могут быть реализованы с помощью конвейеров, и что некоторые распространенные оболочки вроде dashэтого. Этот ответ объясняет наблюдаемое поведение с bash, но это поведение явно не гарантируется для других оболочек.
Питер Кордес
@PeterCordes это абсолютно так, и я только что проверил это dashв моей системе. Я не знал об этом ранее. Спасибо за указание
Сергей Колодяжный
Еще один комментарий: вы бы использовали fstat()stdin, чтобы проверить, является ли это каналом. statберет путь Но на самом деле, просто попытка lseek- это, пожалуй, самый разумный способ определить, можно ли искать fd после того, как он уже открыт.
Питер Кордес
5

Основное отличие заключается в обработке ошибок.

В следующем случае сообщается об ошибке

$ /bin/cat < z.txt
-bash: z.txt: No such file or directory
$ echo $?
1

В следующем случае ошибка не сообщается.

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0

С bash вы все еще можете использовать PIPESTATUS:

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo ${PIPESTATUS[0]}
1

Но это доступно только сразу после выполнения команды:

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0
$ echo ${PIPESTATUS[0]}
0
# oops !

Есть еще одно отличие, когда мы используем функции оболочки вместо двоичных файлов. В bash, функции, которые являются частью конвейера, выполняются в подоболочках (за исключением последнего компонента конвейера, если lastpipeопция включена и не bashявляется интерактивной), поэтому изменение переменных не оказывает влияния на родительскую оболочку:

$ a=a
$ b=b
$ x(){ a=x;}
$ y(){ b=y;}

$ echo $a $b
a b

$ x | y
$ echo $a $b
a b

$ cat t.txt | y
$ echo $a $b
a b

$ x | cat
$ echo $a $b
a b

$ x < t.txt
$ y < t.txt
$ echo $a $b
x y
Vouze
источник
4
Итак, вы показываете, что обработка ошибок с >помощью выполняется оболочкой, а с конвейером - командой, которая создает текст. ХОРОШО. Но в этом конкретном вопросе OP использует существующий файл, так что это не проблема, и ясно, что ошибка вызвана двоичным файлом.
Сергей Колодяжный
1
Хотя это в основном не относится к делу, этот ответ действительно имеет отношение к этим вопросам и ответам в общем случае и является в основном правильным, поэтому я не думаю, что он заслуживает этих отрицательных ответов.
Стефан
@Serg: когда вы используете shell в качестве командной строки, это не важно. Но в сценариях обработка ошибок может быть очень важной.
Вауз