Яка різниця між "файлом cat" | ./binary ”та“ ./binary <файл ”?


102

У мене є двійковий файл (який я не можу змінювати) і можу:

./binary < file

Я також можу:

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

Але

cat file | ./binary

дає мені помилку. Я не знаю, чому це не працює з трубою. У всіх 3 випадках вміст файлу надається стандартному вводу двійкового (різними способами):

  1. bash зчитує файл і надає його до stdin з бінарного
  2. bash зчитує рядки зі stdin (до EOF) і надає його до stdin бінарного
  3. кішка читає і ставить рядки файлу до stdout, bash перенаправляє їх до stdin бінарного

Бінарний не повинен помічати різницю між цими 3, наскільки я це зрозумів. Чи може хтось пояснити, чому третя справа не працює?

BTW: Помилка, яку задає двійковий файл :

20170116 / 125624.689 - U3000011 Не вдалося прочитати файл сценарію '', код помилки '14'.

Але моє головне питання - як існує різниця для будь-якої програми з цими 3 варіантами.

Ось деякі додаткові деталі: я спробував ще раз з трасування і був насправді деякі помилки ESPIPE (Illegal шукати) з lseek з подальшим EFAULT (Bad адреса) від читання перед повідомленням про помилку.

Бінарний файл, який я намагався контролювати за допомогою сценарію з рубіном (без використання тимчасових файлів), є частиною callapi від Automic (UC4) .


25
Класно, у ваш двійковий файл вбудований детектор UUOC . Я хочу це.
xhienne

4
Що це за ОС (тож ми можемо сказати, що таке 14, якщо це означає бути помилкою)?
Стефан Шазелас

6
Незважаючи на те, що це можливо для програми реагувати таким чином, що це буде stangely глючить один , що зробив. Кожна не божевільна програма, яка очікує на будь-який вклад від stdin, взагалі повинна працювати, коли stdin - це tty, і якщо вона може працювати як з tty, так і з файлом, є мало причин не підтримувати труби теж. Ймовірно, у автора програми було тимчасове крововилив, і хоч усе, що isatty()повернеться фальшивим, - це файл, який можна знайти або піддається mmappable ...
Геннінг Макгольм

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 знаходиться на трубі або у звичайному файлі.

Існує багато команд, які можуть працювати лише з файлами, які шукаються , але коли це так, це, як правило, не для файлів, відкритих на їхньому stdin.

$ 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. Він не читає поштові файли зі свого stdin. stdin здебільшого використовується для взаємодії з користувачем.

Якщо ви запускаєте цю binaryсвою без перенаправлення під запит інтерактивної оболонки, що працює в емуляторі терміналу, то binarystdin буде успадкований від свого батьківського оболонки, який сам успадкував його від свого батьківського термінального емулятора і буде pty пристрій відкрито в режимі читання + запису (щось подібне /dev/pts/n).

Ці пристрої також не можна знайти. Отже, якщо binaryпрацює нормально, коли приймається вхід з терміналу, можливо, питання не в тому, щоб шукати.

Якщо це 14 мається на увазі помилка (код помилки, встановлений невдачею системних викликів), то для більшості систем це буде EFAULT( неправильна адреса ). read()Системний виклик зазнає невдачі з цією помилкою , якщо попросив прочитати на адресу пам'яті , який не доступний для запису. Це не залежатиме від того, чи буде fd читати дані з пунктів до труби чи звичайного файлу, і загалом би вказувало на помилку 1 .

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

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


1 Сценарій, передбачений Метью Іфе у коментарі до вашого питання, тут звучить дуже правдоподібно. Цитуючи його:

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


14
Дуже цікаво ... це перше, що я чув, що перенаправлений стандартний вклад у стилі ./binary < fileшукається!
David Z

2
@DavidZ - це файл, який було відредаговано, openі він поводиться так само, як і будь-який файл, який openредагував. Це просто трапляється у спадок від батьківського процесу, але це не так вже й рідко.
варення

3
Якщо система містить штрих або подібний інструмент, це може бути використано для перевірки, яка система виклику двійкових невдач.
пабук

2
"Він також може вкорочувати його, mmap, пробивати в ньому отвори тощо". - Ну, ні. Файл відкритий у режимі лише для читання. Для цього програмі доведеться відкрити його в режимі запису. Але він не може відкрити його в режимі запису, тому що немає інтерфейсу для цього безпосередньо, а також немає інтерфейсу для пошуку "каталогу" запису, який відповідає відкритому файлу (що робити, якщо там два таких зубних рядів чи нуль?) . Потрібно було б статистика файлу, а потім сканувати файлову систему на предмет з тим же номером inode. Це було б надзвичайно повільно.
Кевін

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-docперенаправлення ( <<) або перенаправлення 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 KiB (принаймні на Linux) з записом 4096 байт або менш гарантовано атомними, не є доступними, тобто ви не можете вільно орієнтуватися по них - читайте лише послідовно. Я одного разу реалізував tailкоманду в python. 29 мільйонів рядків тексту можна шукати в мікросекундах, якщо їх перенаправляти, але якщо cat'ed через трубу, ну нічого не можна зробити - тому все це потрібно читати послідовно.

Інша можливість полягає в тому, що двійковий файл може спеціально відкрити файл і не хоче отримувати вхід з труби. Зазвичай це робиться за допомогою fstat()системного виклику та перевірки, чи входить дані S_ISFIFOтипу файлу (який позначає трубу / названу трубу).

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

ПРИМІТКА . Деякі оболонки, такі як тире (Debian Almquist Shell, за замовчуванням /bin/shUbuntu), здійснюють here-docперенаправлення з трубами всередині , таким чином, вони не піддаються пошуку. Суть залишається незмінною - труби послідовні і не можуть легко переміщуватися, і спроби зробити це призведе до помилок.


Відповідь Стефана говорить, що тут-документи можна реалізувати за допомогою труб і що деякі звичайні оболонки люблять dashце робити. Ця відповідь пояснює спостережувану поведінку з баш, але ця поведінка, очевидно, не гарантована в інших оболонках.
Пітер Кордес

@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

4
Отже, ви показуєте, що обробка помилок >виконується оболонкою, але з трубою це виконується командою, яка створює текст. ГАРАЗД. Але в цьому конкретному питанні ОП використовує існуючий файл, тому це не проблема, і явна помилка створюється двійковим.
Сергій Колодяжний

1
Незважаючи на те, що це здебільшого поза точкою, ця відповідь має загальне значення для цього питання і відповідей у ​​загальному випадку, і є здебільшого правильною, тому я не думаю, що він заслуговує на ці голоси.
Стефан Шазелас

@Serg: Якщо ви використовуєте shell як командний рядок, це не важливо. Але в сценаріях поводження з помилками може бути дуже важливим.
Vouze
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.