Sự khác biệt giữa tập tin mèo trên mạng | ./binary và và.


102

Tôi có một nhị phân (mà tôi không thể sửa đổi) và tôi có thể làm:

./binary < file

Tôi cũng có thể làm:

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

Nhưng

cat file | ./binary

cho tôi một lỗi Tôi không biết tại sao nó không hoạt động với một đường ống. Trong cả 3 trường hợp, nội dung của tệp được cung cấp cho đầu vào tiêu chuẩn của nhị phân (theo các cách khác nhau):

  1. bash đọc tệp và đưa nó cho stdin của nhị phân
  2. bash đọc các dòng từ stdin (cho đến EOF) và đưa nó cho stdin của nhị phân
  3. mèo đọc và đặt các dòng của tập tin vào thiết bị xuất chuẩn, bash chuyển hướng chúng đến stdin của nhị phân

Nhị phân không nên nhận thấy sự khác biệt giữa 3 người theo như tôi hiểu. Ai đó có thể giải thích tại sao trường hợp thứ 3 không hoạt động?

BTW: Lỗi do nhị phân đưa ra là:

20170116/125624.689 - U3000011 Không thể đọc tệp tập lệnh '', mã lỗi '14'.

Nhưng câu hỏi chính của tôi là, làm thế nào có sự khác biệt cho bất kỳ chương trình nào với 3 tùy chọn đó.

Dưới đây là một số chi tiết khác: Tôi đã thử lại với strace và trên thực tế đã có một số lỗi ESPIPE (Tìm kiếm bất hợp pháp) từ lseek theo sau là EFAULT (Địa chỉ xấu) từ đọc ngay trước thông báo lỗi.

Nhị phân mà tôi đã cố kiểm soát bằng tập lệnh ruby ​​(không sử dụng tệp tạm thời) là một phần của callapi từ Automic (UC4) .


25
Thật tuyệt, có một máy dò UUOC được nhúng trong tệp nhị phân của bạn. Tôi muốn nó.
xhienne

4
Nó là hệ điều hành gì (vì vậy chúng ta có thể biết 14 là gì nếu nó có nghĩa là một lỗi)?
Stéphane Chazelas

6
Mặc dù nó là có thể cho một chương trình để phản ứng theo cách này, nó sẽ là một stangely lỗi rằng đã làm. Mọi chương trình không điên mà mong đợi bất kỳ đầu vào nào từ stdin đều cần hoạt động khi stdin là một tty, và nếu nó có thể hoạt động với cả tty và tệp, thì có rất ít lý do để không hỗ trợ các đường ống. Có lẽ tác giả của chương trình đã bị xuất huyết tạm thời và mặc dù bất cứ điều gì isatty()trả về sai sẽ là một tập tin có thể tìm kiếm hoặc mmappable ...
Henning Makholm

9
Mã lỗi 14 là viết tắt của EFAULT. Trên một lần đọc xảy ra nếu bộ đệm bạn đã khai báo không hợp lệ. Tôi sẽ sải bước chương trình nhưng tôi nghi ngờ nó đang tìm đến cuối tập tin để có kích thước bộ đệm để đọc dữ liệu, xử lý không tốt việc tìm kiếm không hoạt động và cố gắng phân bổ kích thước âm (không xử lý một malloc xấu) . Truyền bộ đệm để đọc lỗi nào được cung cấp cho bộ đệm không hợp lệ.
Matthew Ife

3
@xhienne Không, nó có một ưu thế cattrong đó. Dường như bạn không thể sử dụng nó để kết hợp hai tệp, như mục đích sử dụng.
jpmc26

Câu trả lời:


150

Trong

./binary < file

binaryStdin là tập tin mở ở chế độ chỉ đọc. Lưu ý rằng bashhoàn toàn không đọc tệp, nó chỉ mở tệp để đọc trên bộ mô tả tệp 0 (stdin) của quy trình mà nó thực hiện binary.

Trong:

./binary << EOF
test
EOF

Tùy thuộc vào shell, binarystdin của sẽ là một tệp tạm thời bị xóa (AT & T ksh, zsh, bash ...) có chứa test\nnhư được đặt ở đó bởi shell hoặc đầu đọc của ống ( dash, yash; và shell ghi test\nsong song ở đầu kia của ống). Trong trường hợp của bạn, nếu bạn đang sử dụng bash, nó sẽ là một tệp tạm thời.

Trong:

cat file | ./binary

Tùy thuộc vào vỏ, binarystdin của nó sẽ là đầu đọc của ống hoặc một đầu của cặp ổ cắm nơi hướng viết đã bị tắt (ksh93) và catđang viết nội dung fileở đầu kia.

Khi stdin là một tệp thông thường (tạm thời hoặc không), nó có thể tìm kiếm được. binarycó thể đi đến đầu hoặc cuối, tua lại, v.v. Nó cũng có thể tạo ra nó, làm một số thứ ioctl()snhư FIEMAP / FIBMAP (nếu sử dụng <>thay vì <, nó có thể cắt / đục lỗ trong đó, v.v.).

Mặt khác, các cặp ống và ổ cắm là một phương tiện giao tiếp giữa các quá trình, không binarythể làm gì nhiều ngoài readviệc nhập dữ liệu (mặc dù cũng có một số thao tác như một số ống cụ thể ioctl()mà nó có thể thực hiện trên chúng chứ không phải trên các tệp thông thường) .

Hầu hết các lần, đó là khả năng thiếu để seekgây ra các ứng dụng để thất bại / phàn nàn khi làm việc với ống, nhưng nó có thể là bất kỳ của các cuộc gọi hệ thống khác có giá trị trên các tập tin thường xuyên nhưng không phải trên các loại khác nhau của các file (như mmap(), ftruncate(), fallocate()) . Trên Linux, cũng có một sự khác biệt lớn về hành vi khi bạn mở /dev/stdintrong khi fd 0 nằm trên một đường ống hoặc trên một tệp thông thường.

Có rất nhiều các lệnh ra khỏi đó mà chỉ có thể đối phó với seekable file, nhưng khi đó là trường hợp, đó là thường không cho các tập tin mở trên stdin của họ.

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

unzipcần đọc chỉ mục được lưu trữ ở cuối tệp, sau đó tìm trong tệp để đọc các thành viên lưu trữ. Nhưng ở đây, tệp (thông thường trong trường hợp đầu tiên, ống trong lần thứ hai) được đưa ra làm đối số đường dẫn đến unzipunzipmở chính nó (thường là trên fd khác 0) thay vì kế thừa fd đã được mở bởi cha mẹ. Nó không đọc các tập tin zip từ stdin của nó. stdin chủ yếu được sử dụng cho tương tác người dùng.

Nếu bạn chạy cái binarycủa bạn mà không chuyển hướng theo dấu nhắc của trình vỏ tương tác đang chạy trong trình giả lập thiết bị đầu cuối, thì binarystdin của nó sẽ được thừa hưởng từ lớp vỏ của nó, chính nó sẽ được thừa hưởng từ trình giả lập thiết bị đầu cuối của nó và sẽ là một thiết bị pty mở ở chế độ đọc + ghi (đại loại như /dev/pts/n).

Những thiết bị này cũng không thể tìm kiếm được. Vì vậy, nếu binaryhoạt động tốt khi lấy đầu vào từ thiết bị đầu cuối, có thể vấn đề không nằm ở việc tìm kiếm.

Nếu 14 đó có nghĩa là một lỗi (mã lỗi được đặt bằng cách không thực hiện các cuộc gọi hệ thống), thì trên hầu hết các hệ thống, đó sẽ là EFAULT( Địa chỉ xấu ). Cuộc read()gọi hệ thống sẽ thất bại với lỗi đó nếu được yêu cầu đọc vào một địa chỉ bộ nhớ không thể ghi. Điều đó sẽ độc lập với việc fd đọc dữ liệu từ các điểm đến một đường ống hoặc tệp thông thường và thường chỉ ra lỗi 1 .

binarycó thể xác định loại tệp mở trên stdin của nó fstat()và gặp lỗi khi đó không phải là tệp thông thường cũng không phải là thiết bị tty.

Khó để nói mà không biết thêm về ứng dụng. Chạy nó bên dưới strace(hoặc truss/ tusctương đương trên hệ thống của bạn) có thể giúp chúng tôi xem hệ thống gọi là gì nếu có bất kỳ lỗi nào ở đây.


1 Kịch bản được Matthew Ife đưa ra trong một bình luận cho câu hỏi của bạn nghe có vẻ rất hợp lý ở đây. Trích dẫn anh ấy:

Tôi nghi ngờ nó đang tìm đến cuối tệp để có kích thước bộ đệm để đọc dữ liệu, xử lý không tốt thực tế là tìm kiếm không hoạt động và cố gắng phân bổ kích thước âm (không xử lý một malloc xấu). Truyền bộ đệm để đọc lỗi nào được cung cấp cho bộ đệm không hợp lệ.


14
Rất thú vị ... đây là lần đầu tiên tôi nghe nói rằng đầu vào tiêu chuẩn được chuyển hướng theo kiểu có thể tìm kiếm ./binary < fileđược!
David Z

2
@DavidZ đó là một tệp đã được chỉnh sửa openvà nó hoạt động giống như bất kỳ tệp nào đã được chỉnh sửa open. Nó chỉ được thừa hưởng từ một quá trình cha mẹ, nhưng điều đó không quá hiếm.
hobbs

3
Nếu hệ thống chứa strace hoặc một công cụ tương tự, nó có thể được sử dụng để kiểm tra xem hệ thống nào gọi nhị phân không thành công.
pabouk

2
"Nó cũng có thể cắt nó, mmap nó, đục lỗ trên nó, v.v." - Ồ không. Các tập tin được mở trong chế độ chỉ đọc. Chương trình sẽ phải mở nó trong chế độ ghi để làm điều đó. Nhưng nó không thể mở nó trong chế độ ghi, vì không có giao diện để thực hiện trực tiếp, cũng như không có giao diện nào để tìm "mục" thư mục tương ứng với một tệp đang mở (nếu có hai vết răng như vậy, hoặc bằng 0 thì sao?) . Nó sẽ phải thống kê tệp và sau đó quét hệ thống tệp cho một đối tượng có cùng số inode. Điều đó sẽ rất chậm.
Kevin

1
@ StéphaneChazelas: oh đúng, open("/proc/self/fd/0", O_RDWR)hoạt động, ngay cả trên các tệp đã bị xóa. Ngớ ngẩn với tôi: 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 foohủy liên kết footrước khi a.out chạy với stdin được chuyển hướng từ foo.
Peter Cordes

46

Đây là một chương trình ví dụ đơn giản minh họa câu trả lời của Stéphane Chazelas bằng cách sử dụng lseek(2)trên đầu vào của nó:

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

Kiểm tra:

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

Không thể tìm kiếm đường ống và đó là một nơi mà chương trình có thể phàn nàn về đường ống.


21

Đường ống và chuyển hướng là những động vật khác nhau, có thể nói như vậy. Khi bạn sử dụng here-docchuyển hướng ( <<) hoặc chuyển hướng stdin < , văn bản sẽ không xuất hiện - nó thực sự đi vào một mô tả tệp (hoặc tệp tạm thời, nếu bạn muốn), và đó là nơi stdin của nhị phân sẽ được trỏ.

Cụ thể, đây là đoạn trích từ bash'smã nguồn, tệp redir.c (phiên bản 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)

Vì vậy, về cơ bản chuyển hướng có thể được coi là tệp, các nhị phân có thể điều hướng chúng hoặc seek()thông qua tệp một cách dễ dàng, nhảy đến bất kỳ byte nào của tệp.

Các đường ống, vì chúng là bộ đệm 64 KiB (ít nhất là trên Linux) với ghi 4096 byte hoặc ít được bảo đảm là nguyên tử, không thể tìm kiếm, tức là bạn không thể tự do điều hướng chúng - chỉ đọc tuần tự. Tôi đã từng thực hiện taillệnh trong python. 29 triệu dòng văn bản có thể được tìm kiếm trong vài giây nếu được chuyển hướng, nhưng nếu catthông qua đường ống, thì không có gì có thể làm được - vì vậy tất cả phải được đọc tuần tự.

Một khả năng khác là nhị phân có thể muốn mở một tệp cụ thể và không muốn nhận đầu vào từ một đường ống. Nó thường được thực hiện thông qua fstat()cuộc gọi hệ thống và kiểm tra xem đầu vào đến từ một S_ISFIFOloại tệp (có nghĩa là đường ống / ống có tên).

Nhị phân cụ thể của bạn, vì chúng tôi không biết nó là gì, có thể cố gắng tìm kiếm, nhưng không thể tìm kiếm đường ống. Bạn nên tham khảo tài liệu của nó để tìm hiểu chính xác mã lỗi 14 có nghĩa là gì.

LƯU Ý : Một số hệ vỏ, chẳng hạn như dấu gạch ngang (Debian Almquist Shell, mặc định /bin/shtrên Ubuntu) thực hiện here-docchuyển hướng với các đường ống bên trong , do đó có thể không thể tìm kiếm được. Điểm vẫn giữ nguyên - các đường ống là tuần tự và không thể điều hướng dễ dàng, và cố gắng làm như vậy sẽ dẫn đến lỗi.


Câu trả lời của Stephane nói rằng tài liệu ở đây có thể được thực hiện bằng các đường ống và một số vỏ thông thường như dashvậy. Câu trả lời này giải thích hành vi được quan sát bằng bash, nhưng hành vi đó dường như không được đảm bảo trên các vỏ khác.
Peter Cordes

@PeterCordes hoàn toàn là như vậy và tôi chỉ xác minh nó dashtrên hệ thống của mình. Tôi đã không nhận thức được điều đó trước đây. Cảm ơn bạn đã chỉ ra
Sergiy Kolodyazhnyy

Một nhận xét khác: bạn sẽ sử dụng fstat()trên stdin để kiểm tra xem đó có phải là đường ống không. statcó một tên đường dẫn. Nhưng thực sự, chỉ cố gắng lseeklà cách có lẽ lành mạnh nhất để xác định xem một fd có thể tìm kiếm được sau khi nó đã mở hay không.
Peter Cordes

5

Sự khác biệt chính là trong việc xử lý lỗi.

Trong trường hợp sau đây, lỗi được báo cáo

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

Trong trường hợp sau đây, lỗi không được báo cáo.

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

Với bash, bạn vẫn có thể sử dụng PIPESTATUS:

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

Nhưng nó chỉ khả dụng ngay sau khi thực hiện lệnh:

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

Có một sự khác biệt khác, khi chúng ta sử dụng các hàm shell thay vì nhị phân. Trong bash, các hàm là một phần của đường ống được thực thi trong các lớp vỏ phụ (ngoại trừ thành phần đường ống cuối cùng nếu lastpipetùy chọn được bật và bashkhông tương tác), do đó, việc thay đổi các biến không có hiệu ứng trong lớp vỏ mẹ:

$ 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
Vì vậy, bạn đang chỉ ra rằng việc xử lý lỗi >được thực hiện bởi trình bao, nhưng với đường ống, nó được thực hiện bằng lệnh tạo ra văn bản. ĐỒNG Ý. Nhưng trong câu hỏi cụ thể này, OP đang sử dụng một tệp hiện có, vì vậy đó không phải là vấn đề và rõ ràng lỗi được tạo ra là do nhị phân.
Sergiy Kolodyazhnyy

1
Mặc dù chủ yếu là bên cạnh vấn đề, câu trả lời này có một số liên quan đến câu hỏi và trả lời này trong trường hợp chung và hầu hết là chính xác, vì vậy tôi không nghĩ rằng nó xứng đáng với những điều đó.
Stéphane Chazelas

@Serg: Khi bạn sử dụng shell làm dòng lệnh, điều này không quan trọng. Nhưng trong các kịch bản, việc xử lý lỗi có thể rất quan trọng.
Vouze
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.