Apa perbedaan antara “file cat | ./binary ”dan“ ./binary <file ”?

102

Saya memiliki biner (yang tidak dapat saya modifikasi) dan saya dapat melakukannya:

./binary < file

Saya juga bisa melakukan:

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

Tapi

cat file | ./binary

memberi saya kesalahan. Saya tidak tahu mengapa itu tidak bekerja dengan pipa. Dalam semua 3 kasus, konten file diberikan ke input standar biner (dalam berbagai cara):

  1. bash membaca file dan memberikannya ke stdin dari biner
  2. bash membaca baris dari stdin (hingga EOF) dan memberikannya ke stdin dari biner
  3. cat membaca dan meletakkan baris file ke stdout, bash mengarahkan mereka ke stdin biner

Biner seharusnya tidak memperhatikan perbedaan antara 3 sejauh yang saya mengerti. Adakah yang bisa menjelaskan mengapa case ke-3 tidak berfungsi?

BTW: Kesalahan yang diberikan oleh biner adalah:

20170116 / 125624.689 - U3000011 Tidak dapat membaca file skrip '', kode kesalahan '14'.

Tapi pertanyaan utama saya adalah, bagaimana ada perbedaan untuk setiap program dengan 3 opsi itu.

Berikut adalah beberapa perincian lebih lanjut: Saya mencobanya lagi dengan strace dan ternyata ada beberapa kesalahan ESPIPE (pencarian ilegal) dari lseek diikuti oleh EFAULT (Alamat buruk) dari pembacaan tepat sebelum pesan kesalahan.

Biner yang saya coba kontrol dengan skrip ruby ​​(tanpa menggunakan file sementara) adalah bagian dari callapi dari Automic (UC4) .

Boris
sumber
25
Keren, ada detektor UUOC yang tertanam dalam biner Anda. Saya menginginkannya.
xhienne
4
OS apa itu (jadi kita bisa tahu apa itu 14 jika itu dimaksudkan sebagai kesalahan)?
Stéphane Chazelas
6
Meskipun mungkin bagi suatu program untuk bereaksi seperti ini, itu akan menjadi buggy yang aneh. Setiap program non-gila yang mengharapkan input dari stdin sama sekali perlu bekerja ketika stdin adalah tty, dan jika itu dapat bekerja dengan tty dan file, ada sedikit alasan untuk tidak mendukung pipa juga. Mungkin penulis program mengalami pendarahan sementara dan meskipun segala sesuatu yang isatty()mengembalikan false untuk akan menjadi file yang dapat dicari atau mmappable ...
Henning Makholm
9
Kode kesalahan 14 adalah singkatan dari EFAULT. Pada pembacaan yang terjadi jika buffer yang Anda nyatakan tidak valid. Saya akan strace program tetapi saya curiga ini mencari ke akhir file untuk mendapatkan ukuran buffer untuk membaca data, menangani fakta bahwa pencarian tidak bekerja dan berusaha untuk mengalokasikan ukuran negatif (tidak menangani malloc buruk) . Melewati buffer untuk membaca kesalahan yang diberikan buffer tidak valid.
Matthew Ife
3
@xhienne Tidak, ia memiliki catpencegah di dalamnya. Tampaknya Anda tidak dapat menggunakannya untuk menggabungkan dua file, seperti penggunaan yang dimaksudkan.
jpmc26

Jawaban:

150

Di

./binary < file

binarystdin adalah file yang dibuka dalam mode read-only. Catatan yang bashtidak membaca file sama sekali, itu hanya membukanya untuk membaca file descriptor 0 (stdin) dari proses yang dijalankannya binary.

Di:

./binary << EOF
test
EOF

Bergantung pada shell, binarystdin akan berupa file sementara yang dihapus (AT&T ksh, zsh, bash ...) yang berisi test\nseperti yang diletakkan di sana oleh shell atau ujung pembacaan pipa ( dash, yash; dan shell menulis test\nsecara paralel) di ujung lain pipa). Dalam kasus Anda, jika Anda menggunakan bash, itu akan menjadi file temp.

Di:

cat file | ./binary

Tergantung pada cangkang, binarystdin akan menjadi ujung pembacaan pipa, atau salah satu ujung pasangan soket di mana arah penulisan telah ditutup (ksh93) dan catsedang menulis konten filedi ujung lainnya.

Ketika stdin adalah file biasa (sementara atau tidak), itu dapat dicari. binarydapat pergi ke awal atau akhir, mundur, dll. Ini juga dapat mmap itu, melakukan beberapa ioctl()sseperti FIEMAP / FIBMAP (jika menggunakan <>bukan <, itu bisa memotong / membuat lubang di dalamnya, dll).

pasangan pipa dan soket di sisi lain adalah sarana komunikasi antar-proses, tidak banyak yang binarybisa dilakukan selain readdata (meskipun ada juga beberapa operasi seperti beberapa pipa-spesifik ioctl()yang bisa dilakukan pada mereka dan tidak pada file biasa) .

Sebagian besar waktu, itu kemampuan yang hilang untuk seekyang menyebabkan aplikasi gagal / mengeluh ketika bekerja dengan pipa, tetapi bisa menjadi salah satu panggilan sistem lain yang berlaku pada file biasa namun tidak pada berbagai jenis file (seperti mmap(), ftruncate(), fallocate()) . Di Linux, ada juga perbedaan besar dalam perilaku ketika Anda membuka /dev/stdinketika fd 0 berada di atas pipa atau pada file biasa.

Ada banyak perintah di luar sana yang hanya dapat menangani file yang dapat dicari , tetapi ketika itu masalahnya, itu umumnya tidak untuk file yang terbuka di stdin mereka.

$ 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.

unzipperlu membaca indeks yang disimpan di akhir file, dan kemudian mencari dalam file untuk membaca anggota arsip. Tapi di sini, file (reguler dalam kasus pertama, pipa dalam yang kedua) diberikan sebagai argumen path unzip, dan unzipmembukanya sendiri (biasanya pada fd selain 0) alih-alih mewarisi fd yang sudah dibuka oleh induknya. Itu tidak membaca file zip dari stdin-nya. stdin banyak digunakan untuk interaksi pengguna.

Jika Anda menjalankan itu binarydari Anda tanpa pengalihan pada prompt shell interaktif yang berjalan di emulator terminal, maka binarystdin akan diwarisi dari induknya shell, yang dengan sendirinya akan mewarisinya dari induknya terminal emulator dan akan menjadi Perangkat pty terbuka dalam mode baca + tulis (sesuatu seperti /dev/pts/n).

Perangkat-perangkat itu juga tidak bisa dicari. Jadi, jika binaryberfungsi dengan baik ketika mengambil input dari terminal, mungkin masalahnya bukan tentang mencari.

Jika 14 itu dimaksudkan sebagai errno (kode kesalahan yang ditetapkan oleh kegagalan panggilan sistem), maka pada sebagian besar sistem, itu akan menjadi EFAULT( Alamat buruk ). The read()system call akan gagal dengan kesalahan bahwa jika diminta untuk membaca ke alamat memori yang tidak dapat ditulis. Itu akan terlepas dari apakah fd membaca data dari titik ke pipa atau file biasa dan umumnya akan menunjukkan bug 1 .

binarymungkin menentukan jenis file yang terbuka pada stdin (with fstat()) dan mengalami bug ketika itu bukan file biasa atau perangkat tty.

Sulit dikatakan tanpa mengetahui lebih lanjut tentang aplikasi tersebut. Menjalankannya di bawah strace(atau truss/ tuscsetara pada sistem Anda) dapat membantu kami melihat apa yang disebut sistem panggilan jika ada yang gagal di sini.


1 Skenario yang dibayangkan oleh Matthew Ife dalam komentar untuk pertanyaan Anda terdengar sangat masuk akal di sini. Mengutipnya:

Saya menduga itu sedang mencari di akhir file untuk mendapatkan ukuran buffer untuk membaca data, menangani fakta bahwa pencarian tidak berfungsi dan berusaha untuk mengalokasikan ukuran negatif (tidak menangani malloc buruk). Melewati buffer untuk membaca kesalahan yang diberikan buffer tidak valid.

Stéphane Chazelas
sumber
14
Sangat menarik ... ini adalah pertama kalinya saya mendengar bahwa input standar yang dialihkan dalam gaya ./binary < filedapat dicari!
David Z
2
@ DavidZ adalah file yang telah diedit opendan berperilaku sama dengan file yang telah opendiedit. Itu kebetulan telah diwarisi dari proses induk, tapi itu tidak biasa.
hobbs
3
Jika sistem mengandung strace atau alat serupa itu dapat digunakan untuk memeriksa sistem mana panggilan biner gagal.
pabouk
2
"Itu juga bisa memotongnya, mmap itu, membuat lubang di dalamnya dll." - Tidak. File terbuka dalam mode baca-saja. Program harus membukanya dalam mode tulis untuk melakukan itu. Tetapi itu tidak dapat membukanya dalam mode tulis, karena tidak ada antarmuka untuk melakukan itu secara langsung, juga tidak ada antarmuka untuk menemukan entri direktori "yang" sesuai dengan file terbuka (bagaimana jika ada dua gigi seperti itu, atau nol?) . Itu harus meng-stat file dan kemudian memindai sistem file untuk suatu objek dengan nomor inode yang sama. Itu akan sangat lambat.
Kevin
1
@ StéphaneChazelas: oh benar, open("/proc/self/fd/0", O_RDWR)bekerja, bahkan pada file yang dihapus. Konyol saya: 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 foobatalkan tautan foosebelum a.out berjalan dengan stdin yang dialihkan dari foo.
Peter Cordes
46

Berikut adalah contoh program sederhana yang menggambarkan jawaban Stéphane Chazelas menggunakan lseek(2)inputnya:

#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);
}

Pengujian:

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

Pipa tidak bisa dicari, dan itu adalah satu tempat di mana sebuah program mungkin mengeluh tentang pipa.

muru
sumber
21

Pipa dan pengalihan adalah hewan yang berbeda, jadi untuk berbicara. Ketika Anda menggunakan here-docpengalihan ( <<) atau mengarahkan stdin < teks tidak keluar dari udara tipis - itu benar-benar masuk ke file descriptor (atau file sementara, jika Anda mau), dan di situlah biner's stdin akan menunjuk.

Secara khusus, inilah kutipan dari bash'skode sumber, file redir.c (versi 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)

Jadi karena pengalihan pada dasarnya dapat diperlakukan sebagai file, binari dapat menavigasi mereka, atau seek()melalui file dengan mudah, melompat ke byte file apa pun.

Pipes, karena mereka adalah buffer 64 KiB (setidaknya di Linux) dengan penulisan 4096 byte atau kurang dijamin atom, tidak dapat dicari, artinya Anda tidak dapat menavigasi dengan bebas - hanya membaca secara berurutan. Saya pernah mengimplementasikan tailperintah dengan python. 29 juta baris teks dapat dicari dalam mikrodetik jika diarahkan, tetapi jika catmelalui pipa, tidak ada yang bisa dilakukan - jadi semuanya harus dibaca berurutan.

Kemungkinan lain adalah bahwa biner mungkin ingin membuka file secara khusus, dan tidak ingin menerima input dari sebuah pipa. Biasanya dilakukan melalui fstat()system call, dan memeriksa apakah input berasal dari S_ISFIFOjenis file (yang menandakan pipa / pipa bernama).

Biner khusus Anda, karena kami tidak tahu apa itu, mungkin berusaha mencari, tetapi tidak dapat mencari pipa. Anda disarankan untuk membaca dokumentasinya untuk mengetahui apa sebenarnya arti kode kesalahan 14.

CATATAN : Beberapa shell, seperti dash (Debian Almquist Shell, default /bin/shdi Ubuntu) menerapkan here-docredirection dengan pipa secara internal , sehingga mungkin tidak dapat dicari. Intinya tetap sama - pipa berurutan dan tidak dapat dinavigasi dengan mudah, dan upaya untuk melakukannya akan menghasilkan kesalahan.

Sergiy Kolodyazhnyy
sumber
Jawaban Stephane mengatakan bahwa di sini-docs dapat diimplementasikan dengan pipa, dan bahwa beberapa shell umum suka dashmelakukannya. Jawaban ini menjelaskan perilaku yang diamati dengan bash, tetapi perilaku itu tampaknya tidak dijamin di shell lain.
Peter Cordes
@PeterCordes benar-benar begitu, dan saya baru saja memverifikasi dengan dashdi sistem saya. Saya tidak menyadarinya sebelumnya. Terima kasih telah menunjukkan
Sergiy Kolodyazhnyy
Komentar lain: Anda akan menggunakan fstat()stdin untuk memeriksa apakah itu sebuah pipa. statmengambil pathname. Tapi sungguh, hanya mencoba lseekadalah mungkin cara yang paling waras untuk menentukan apakah suatu fd dapat dicari setelah itu sudah terbuka.
Peter Cordes
5

Perbedaan utama adalah dalam penanganan kesalahan.

Dalam kasus berikut kesalahan dilaporkan

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

Dalam kasus berikut kesalahan tidak dilaporkan.

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

Dengan bash, Anda masih dapat menggunakan PIPESTATUS:

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

Tetapi ini hanya tersedia segera setelah eksekusi perintah:

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

Ada perbedaan lain, saat kita menggunakan fungsi shell alih-alih binari. Dalam bash, fungsi-fungsi yang merupakan bagian dari pipeline dieksekusi dalam sub-shells (kecuali untuk komponen pipeline terakhir jika lastpipeopsi diaktifkan dan bashnon-interaktif), sehingga perubahan variabel tidak memiliki efek dalam shell induk:

$ 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
sumber
4
Jadi, Anda menunjukkan bahwa penanganan kesalahan dengan >dilakukan oleh shell, tetapi dengan pipa itu dilakukan oleh perintah yang menghasilkan teks. BAIK. Tetapi dalam pertanyaan khusus ini, OP menggunakan file yang sudah ada, jadi itu bukan masalah, dan jelas kesalahan yang dihasilkan oleh biner.
Sergiy Kolodyazhnyy
1
Meskipun sebagian besar tidak penting, jawaban ini memang memiliki relevansi dengan T&J ini dalam kasus umum dan sebagian besar benar, jadi saya tidak berpikir itu layak untuk downvote tersebut.
Stéphane Chazelas
@Erg: Ketika Anda menggunakan shell sebagai baris perintah, ini tidak penting. Namun dalam skrip, penanganan kesalahan bisa sangat penting.
Vouze