อะไรคือความแตกต่างระหว่าง“ ไฟล์ cat | ./binary” และ“ ./binary <file”


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. cat อ่านและวางบรรทัดของไฟล์เป็น stdout, bash จะเปลี่ยนเส้นทางไปยัง stdin ของbinary

ไบนารีไม่ควรสังเกตความแตกต่างระหว่าง 3 เท่าที่ฉันเข้าใจ มีคนอธิบายได้ไหมว่าทำไมคดีที่ 3 ถึงไม่ทำงาน

BTW: ข้อผิดพลาดที่กำหนดโดยไบนารีคือ:

20170116 / 125624.689 - U3000011 ไม่สามารถอ่านไฟล์สคริปต์ '', รหัสข้อผิดพลาด '14'

แต่คำถามหลักของฉันคือโปรแกรมต่างมี 3 ตัวเลือกอย่างไร

นี่คือรายละเอียดเพิ่มเติม: ฉันลองอีกครั้งด้วยstrace และในความเป็นจริงมีข้อผิดพลาดบางอย่างESPIPE (การค้นหาที่ผิดกฎหมาย)จากlseek ตามด้วยEFAULT (ที่อยู่ไม่ถูกต้อง)จากการอ่านก่อนข้อความแสดงข้อผิดพลาด

ไบนารีผมพยายามที่จะควบคุมด้วยสคริปต์ทับทิม (โดยไม่ต้องใช้ไฟล์ชั่วคราว) เป็นส่วนหนึ่งของcallapiจากAutomic (UC4)


25
เยี่ยมมากมีตัวตรวจจับUUOCฝังอยู่ในไบนารีของคุณ ฉันต้องการมัน.
xhienne

4
มันคือระบบปฏิบัติการอะไร (เพื่อให้เราสามารถบอกได้ว่า 14 คือถ้ามันหมายถึงว่าเป็น errno)
Stéphane Chazelas

6
แม้ว่ามันจะเป็นไปได้สำหรับโปรแกรมที่จะตอบสนองด้วยวิธีนี้ แต่มันก็เป็นรถที่น่าเบื่ออย่างยิ่ง ทุกโปรแกรมที่ไม่บ้าที่คาดว่าข้อมูลใด ๆ จาก stdin ที่ทุกคนต้องการที่จะทำงานเมื่อ stdin เป็น tty และถ้ามันสามารถทำงานได้กับทั้ง tty และไฟล์มีเหตุผลเล็กน้อยที่จะไม่สนับสนุนท่อเกินไป อาจเป็นผู้เขียนโปรแกรมที่มีอาการตกเลือดชั่วคราวและแม้ว่าสิ่งที่isatty()ผลตอบแทนที่ผิดพลาดสำหรับจะเป็นไฟล์ที่หาได้หรือ mmappable ...
Henning Makholm

9
รหัสข้อผิดพลาด 14 หมายถึง EFAULT ในการอ่านที่เกิดขึ้นหากบัฟเฟอร์ที่คุณประกาศไม่ถูกต้อง ฉันจะ strace โปรแกรม แต่ฉันสงสัยว่ามันกำลังมองหาที่จุดสิ้นสุดของไฟล์เพื่อรับขนาดบัฟเฟอร์สำหรับการอ่านข้อมูลการจัดการกับความจริงที่ว่าการค้นหาไม่ได้ผลและพยายามจัดสรรขนาดลบ (ไม่ใช่การจัดการ malloc ที่ไม่ดี) . การส่งผ่านบัฟเฟอร์เพื่ออ่านว่าข้อบกพร่องใดที่กำหนดให้บัฟเฟอร์นั้นไม่ถูกต้อง
Matthew Ife

3
@xhienne ไม่มันมีcatpreventer อยู่ด้วย ดูเหมือนว่าคุณไม่สามารถใช้มันเพื่อรวมสองไฟล์เข้าด้วยกันเช่นเดียวกับการใช้งานตามที่ตั้งใจไว้
jpmc26

คำตอบ:


150

ใน

./binary < file

binary's stdin คือไฟล์ที่เปิดในโหมดอ่านอย่างเดียว โปรดทราบว่า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อาจไปที่จุดเริ่มต้นหรือจุดสิ้นสุดย้อนกลับ ฯลฯ นอกจากนี้ยังสามารถ mmap ทำบางอย่างioctl()sเช่น FIEMAP / FIBMAP (หากใช้<>แทน<มันสามารถตัด / เจาะรูใน ฯลฯ )

ท่อและซ็อกเก็ตคู่ในอีกทางหนึ่งคือการสื่อสารระหว่างกระบวนการหมายความว่าไม่มีอะไรที่binaryสามารถทำได้นอกเหนือreadจากข้อมูล (แม้ว่าจะมีการดำเนินการบางอย่างเช่นท่อเฉพาะบางอย่างioctl()ที่สามารถทำได้กับพวกเขาและไม่ใช่ไฟล์ปกติ) .

มากที่สุดเท่าที่มันเป็นความสามารถไปseekที่ทำให้เกิดการใช้งานที่จะล้มเหลว / บ่นเมื่อทำงานร่วมกับท่อ แต่มันอาจจะเป็นที่ใด ๆ ของสายระบบอื่น ๆ ที่ถูกต้องเกี่ยวกับไฟล์ปกติ แต่ไม่เกี่ยวกับชนิดของไฟล์ (เช่นmmap(), ftruncate(), fallocate()) . บน Linux ยังมีพฤติกรรมที่แตกต่างกันมากเมื่อคุณเปิด/dev/stdinในขณะที่ fd 0 อยู่ในไพพ์หรือไฟล์ปกติ

มีคำสั่งจำนวนมากออกมีที่สามารถจัดการกับseekableไฟล์ แต่เมื่อเป็นกรณีที่ว่าโดยทั่วไปไม่ได้สำหรับไฟล์ที่เปิด 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 ที่เปิดโดยผู้ปกครอง ไม่อ่านไฟล์ zip จาก stdin stdin ส่วนใหญ่จะใช้สำหรับการโต้ตอบกับผู้ใช้

หากคุณเรียกใช้งานbinaryของคุณโดยไม่มีการเปลี่ยนเส้นทางที่พร้อมต์ของเชลล์แบบโต้ตอบที่ทำงานในเทอร์มินัลอีมูเลเตอร์binarystdin ของจะถูกสืบทอดมาจากพาเรนต์ของเชลล์ซึ่งตัวมันเองจะได้รับสืบทอดจากพาเรนต์เทอร์มินัล อุปกรณ์ pty เปิดในโหมดอ่าน + เขียน (คล้าย/dev/pts/n)

อุปกรณ์เหล่านั้นหาไม่ได้เช่นกัน ดังนั้นหากใช้binaryงานได้ดีเมื่อรับข้อมูลจากเทอร์มินัลปัญหาอาจไม่เกี่ยวกับการค้นหา

หาก 14 นั้นหมายถึงว่าเป็น errno (รหัสข้อผิดพลาดที่ตั้งค่าโดยการเรียกระบบล้มเหลว) แสดงว่าในระบบส่วนใหญ่นั้นจะเป็นEFAULT( ที่อยู่ไม่ถูกต้อง) การread()เรียกของระบบอาจล้มเหลวพร้อมกับข้อผิดพลาดนั้นหากถูกขอให้อ่านที่อยู่หน่วยความจำที่ไม่สามารถเขียนได้ ที่จะเป็นอิสระจากว่า FD ในการอ่านข้อมูลจากจุดไปยังท่อหรือแฟ้มปกติและโดยทั่วไปจะระบุข้อผิดพลาด1

binaryอาจกำหนดประเภทของไฟล์ที่เปิดอยู่บน stdin (พร้อมfstat()) และพบข้อผิดพลาดเมื่อไม่ใช่ไฟล์ปกติหรืออุปกรณ์ tty

ยากที่จะบอกโดยไม่ทราบเพิ่มเติมเกี่ยวกับแอปพลิเคชัน การเรียกใช้ภายใต้strace(หรือtruss/ tuscเทียบเท่าในระบบของคุณ) สามารถช่วยให้เราเห็นว่าการเรียกของระบบคืออะไรหากมีสิ่งใดที่ล้มเหลวที่นี่


1สถานการณ์สมมติโดยMatthew Ifeแสดงความคิดเห็นต่อคำถามของคุณฟังดูมีเหตุผลมากที่นี่ อ้างถึงเขา:

ฉันสงสัยว่ามันกำลังมองหาจุดสิ้นสุดของไฟล์เพื่อรับขนาดบัฟเฟอร์สำหรับการอ่านข้อมูลการจัดการกับความจริงที่ว่าการค้นหาไม่ได้ผลและพยายามจัดสรรขนาดลบ (ไม่จัดการ malloc ที่ไม่ดี) การส่งผ่านบัฟเฟอร์เพื่ออ่านว่าข้อบกพร่องใดที่กำหนดให้บัฟเฟอร์นั้นไม่ถูกต้อง


14
น่าสนใจมาก ... นี่เป็นครั้งแรกที่ฉันได้ยินมาว่าอินพุตมาตรฐานที่เปลี่ยนทิศทางในรูปแบบของหา./binary < fileได้!
David Z

2
@DavidZ เป็นไฟล์ที่ถูกopenแก้ไขและทำงานเหมือนกับไฟล์ใด ๆ ที่ถูกopenแก้ไข มันเพิ่งเกิดขึ้นมาจากกระบวนการผู้ปกครอง แต่นั่นไม่ใช่เรื่องแปลก
ฮอบส์

3
หากระบบมีstraceหรือเครื่องมือที่คล้ายกันก็สามารถใช้เพื่อตรวจสอบว่าระบบการเรียกไบนารีล้มเหลว
pabouk

2
"มันยังสามารถตัดมัน mmap มันเจาะรูมันเป็นต้น" - ดีไม่ ไฟล์เปิดในโหมดอ่านอย่างเดียว โปรแกรมจะต้องเปิดมันในโหมดการเขียนเพื่อทำเช่นนั้น แต่มันไม่สามารถเปิดได้ในโหมดเขียนเนื่องจากไม่มีส่วนต่อประสานสำหรับการทำสิ่งนั้นโดยตรงและไม่มีส่วนต่อประสานในการค้นหารายการไดเรกทอรี "ที่" ที่สอดคล้องกับไฟล์เปิด (จะเกิดอะไรขึ้นถ้ามีฟันสองซี่หรือศูนย์) . มันจะต้อง stat ไฟล์แล้วสแกนระบบไฟล์สำหรับวัตถุที่มีหมายเลขไอโหนดเดียวกัน นั่นจะช้าเกินไป
Kevin

1
@ StéphaneChazelas: ใช่แล้วopen("/proc/self/fd/0", O_RDWR)ใช้ได้แม้กระทั่งไฟล์ที่ถูกลบ Silly me: 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ของการเปลี่ยนเส้นทางจาก
Peter Cordes

46

นี่คือตัวอย่างโปรแกรมง่ายๆที่แสดงคำตอบของStéphane Chazelas ที่ใช้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คำสั่งในหลาม สามารถค้นหาข้อความได้ 29 ล้านบรรทัดในหน่วยไมโครวินาทีหากเปลี่ยนเส้นทาง แต่ถ้าcat'ed ผ่านไปป์ก็ไม่มีอะไรที่สามารถทำได้ - ดังนั้นทุกอย่างต้องอ่านตามลำดับ

ความเป็นไปได้อีกอย่างหนึ่งคือไบนารีอาจต้องการเปิดไฟล์โดยเฉพาะและไม่ต้องการรับอินพุตจากไพพ์ โดยปกติแล้วจะทำผ่านการfstat()เรียกของระบบและตรวจสอบว่าอินพุตมาจากS_ISFIFOประเภทของไฟล์ (ซึ่งหมายถึงไพพ์ / ไพพ์ที่มีชื่อ)

ไบนารีเฉพาะของคุณเนื่องจากเราไม่รู้ว่ามันคืออะไรอาจพยายามค้นหา แต่ไม่สามารถค้นหาท่อได้ ขอแนะนำให้คุณอ่านเอกสารประกอบเพื่อค้นหาว่ารหัสข้อผิดพลาด 14 หมายถึงอะไร

หมายเหตุ : เชลล์บางตัวเช่น dash (Debian Almquist Shell ซึ่งเป็นค่าเริ่มต้น/bin/shบน Ubuntu) ใช้การhere-docเปลี่ยนเส้นทางด้วยไพพ์ภายในดังนั้นจึงอาจไม่สามารถค้นหาได้ จุดยังคงเหมือนเดิม - ไปป์เป็นลำดับและไม่สามารถนำทางได้อย่างง่ายดายและการพยายามทำเช่นนั้นจะส่งผลให้เกิดข้อผิดพลาด


คำตอบของ Stephane บอกว่าที่นี่ docs สามารถนำไปใช้กับไพพ์และเชลล์บางตัวที่ชอบdashทำ คำตอบนี้จะอธิบายถึงพฤติกรรมที่สังเกตได้ด้วยการทุบตี แต่พฤติกรรมนั้นไม่ได้รับประกันกับกระสุนอื่น ๆ
Peter Cordes

@ PeterCordes เป็นอย่างนั้นและฉันเพิ่งตรวจสอบด้วยdashในระบบของฉัน ก่อนหน้านี้ฉันไม่ทราบมาก่อน ขอบคุณสำหรับการชี้ให้เห็น
Sergiy Kolodyazhnyy

ความคิดเห็นอื่น: คุณจะใช้fstat()กับ stdin เพื่อตรวจสอบว่าเป็นไปได้ไหม statใช้ชื่อพา ธ แต่ที่จริงแล้วการพยายามlseekเป็นวิธีที่มีเหตุผลมากที่สุดในการพิจารณาว่า fd นั้นหาได้หลังจากที่มันเปิดอยู่หรือไม่
Peter Cordes

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ฟังก์ชั่นที่เป็นส่วนหนึ่งของไปป์ไลน์จะถูกดำเนินการใน sub-shells (ยกเว้นสำหรับชิ้นส่วนไปป์ไลน์สุดท้ายหากlastpipeเปิดใช้งานตัวเลือกและbashไม่ใช่แบบโต้ตอบ) ดังนั้นการเปลี่ยนแปลงของตัวแปรจึงไม่มีผลกระทบใน parent parent:

$ 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
ดังนั้นคุณแสดงให้เห็นว่าการจัดการข้อผิดพลาดด้วย>ถูกทำโดยเชลล์ แต่ด้วยไพพ์มันทำโดยคำสั่งที่สร้างข้อความ ตกลง. แต่ในคำถามเฉพาะนี้ OP กำลังใช้ไฟล์ที่มีอยู่ดังนั้นนั่นไม่ใช่ปัญหาและข้อผิดพลาดที่ชัดเจนที่เกิดขึ้นคือไบนารี
Sergiy Kolodyazhnyy

1
ในขณะที่ส่วนใหญ่อยู่ข้างจุดคำตอบนี้มีความเกี่ยวข้องกับคำถาม & คำตอบในกรณีทั่วไปและถูกต้องส่วนใหญ่ดังนั้นฉันไม่คิดว่ามันสมควรได้รับ downvotes เหล่านั้น
Stéphane Chazelas

@Serg: เมื่อคุณใช้เชลล์เป็นบรรทัดคำสั่งนี่ไม่สำคัญ แต่ในสคริปต์การจัดการข้อผิดพลาดมีความสำคัญมาก
2560
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.