เชลล์สั่งรันคำสั่งและเปลี่ยนเส้นทางสตรีมในลำดับใด


32

ผมพยายามที่จะเปลี่ยนเส้นทางทั้งสองstdoutและstderrไปยังแฟ้มในวันนี้และฉันมาข้ามนี้:

<command> > file.txt 2>&1

นี้เห็นได้ชัดว่าการเปลี่ยนเส้นทางstderrไปstdoutก่อนแล้วจึงส่งผลให้มีการเปลี่ยนเส้นทางไปยังstdoutfile.txt

อย่างไรก็ตามทำไมถึงไม่สั่งซื้อ<command> 2>&1 > file.txt? หนึ่งธรรมชาติจะอ่านหนังสือเล่มนี้เป็น (สมมติว่าการดำเนินการจากซ้ายไปขวา) คำสั่งที่กำลังดำเนินการครั้งแรกที่stderrถูกนำไปstdoutแล้วส่งผลให้ถูกเขียนไปstdout file.txtแต่ข้างต้นเท่านั้นเปลี่ยนเส้นทางstderrไปที่หน้าจอ

เชลล์ตีความทั้งสองคำสั่งอย่างไร


7
TLCL กล่าวว่า "ขั้นแรกเราเปลี่ยนเส้นทางเอาต์พุตมาตรฐานไปยังไฟล์จากนั้นเราเปลี่ยนเส้นทางไฟล์ descriptor 2 (ข้อผิดพลาดมาตรฐาน) ไปยัง file descriptor หนึ่ง (เอาต์พุตมาตรฐาน)" และ "โปรดสังเกตว่าลำดับการเปลี่ยนเส้นทางมีความสำคัญ จะต้องเกิดขึ้นเสมอหลังจากเปลี่ยนเส้นทางเอาต์พุตมาตรฐานหรือไม่ทำงาน "
Zanna

@Zanna: ใช่คำถามของฉันมาจากการอ่านใน TLCL! :) ฉันสนใจที่จะรู้ว่าทำไมมันไม่ทำงานเช่นวิธีที่เชลล์ตีความคำสั่งโดยทั่วไป
ฝึกซ้อม Heartnet

ในคำถามของคุณคุณพูดตรงข้าม "สิ่งนี้เห็นได้ชัดว่าเปลี่ยนเส้นทาง stderr ไปที่ stdout ก่อน ... " - สิ่งที่ฉันเข้าใจจาก TLCL คือเชลล์ส่ง stdout ไปที่ไฟล์แล้ว stderr ไปยัง stdout (เช่นไปยังไฟล์) การตีความของฉันคือถ้าคุณส่ง stderr ไปยัง stdout แล้วมันจะปรากฏใน terminal และการเปลี่ยนเส้นทางที่ตามมาของ stdout จะไม่รวม stderr (เช่นการเปลี่ยนเส้นทางของ stderr ไปยังหน้าจอเสร็จสมบูรณ์ก่อนการเปลี่ยนเส้นทางของ stdout เกิดขึ้น?)
Zanna

7
ฉันรู้ว่านี้เป็นสิ่งที่ล้าสมัยที่จะพูด แต่เปลือกของคุณมาพร้อมกับคู่มือที่อธิบายถึงสิ่งเหล่านี้ - เช่นการเปลี่ยนเส้นทางในbashคู่มือ โดยวิธีการเปลี่ยนเส้นทางไม่ใช่คำสั่ง
Reinier โพสต์

คำสั่งไม่สามารถดำเนินการได้ก่อนที่จะมีการตั้งค่าการเปลี่ยนเส้นทาง: เมื่อexecv-family syscall ถูกเรียกใช้เพื่อมอบกระบวนการย่อยให้กับคำสั่งที่กำลังเริ่มต้นเชลล์จะออกจากลูป (ไม่มีรหัสดำเนินการในกระบวนการนั้นอีกต่อไป ) และไม่มีวิธีที่เป็นไปได้ในการควบคุมสิ่งที่เกิดขึ้นจากจุดนั้น ดังนั้นการเปลี่ยนเส้นทางทั้งหมดจะต้องดำเนินการก่อนที่การดำเนินการจะเริ่มต้นขึ้นในขณะที่เชลล์มีสำเนาของตัวเองกำลังทำงานอยู่ในกระบวนการที่fork()ed เพื่อเรียกใช้คำสั่งของคุณ
Charles Duffy

คำตอบ:


41

เมื่อคุณเรียกใช้<command> 2>&1 > file.txtstderr จะถูกเปลี่ยนเส้นทาง2>&1ไปยังที่ที่ stdout ไปในขณะนี้เทอร์มินัลของคุณ หลังจากนั้น stdout จะถูกเปลี่ยนเส้นทางไปยังไฟล์โดย>แต่ stderr จะไม่ถูกเปลี่ยนเส้นทางด้วยดังนั้นจึงยังคงเป็นเอาต์พุตเทอร์มินัล

เมื่อ<command> > file.txt 2>&1stdout ถูกเปลี่ยนเส้นทางไปยังไฟล์เป็นครั้งแรกจาก>นั้น2>&1เปลี่ยนเส้นทาง stderr ไปยังตำแหน่งที่ stdout กำลังดำเนินการซึ่งเป็นไฟล์

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


มันสมเหตุสมผลถ้าคุณคิดในแง่ของ file-descriptors และการเรียก "dup / fdreopen" ถูกดำเนินการตามลำดับจากซ้ายไปขวา
Mark K Cowan

20

มันอาจสมเหตุสมผลถ้าคุณตามรอย

ในการเริ่มต้น stderr และ stdout ไปในสิ่งเดียวกัน (โดยปกติแล้ว terminal ซึ่งฉันเรียกที่นี่pts):

fd/0 -> pts
fd/1 -> pts
fd/2 -> pts

ฉันหมายถึง stdin, stdout และ stderr โดยหมายเลขอธิบายไฟล์ของพวกเขาที่นี่: พวกเขาเป็นตัวอธิบายไฟล์ 0, 1 และ 2 ตามลำดับ

ตอนนี้อยู่ในชุดแรกของการเปลี่ยนเส้นทางของเรามีและ> file.txt2>&1

ดังนั้น:

  1. > file.txt: ตอนนี้ไปfd/1 file.txtด้วย>, 1เป็นอธิบายไฟล์โดยนัยเมื่อไม่มีอะไรที่ระบุไว้ดังนั้นนี่คือ1>file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
  2. 2>&1: fd/2ไปที่ใดก็ได้fd/1 ในปัจจุบัน :

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> file.txt

ในทางกลับกันด้วย2>&1 > file.txtลำดับที่ถูกกลับรายการ:

  1. 2>&1: fd/2ไปที่ใดก็ได้fd/1ในปัจจุบันซึ่งหมายความว่าไม่มีอะไรเปลี่ยนแปลง

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
  2. > file.txt: fd/1ไปที่file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts

จุดสำคัญคือการเปลี่ยนเส้นทางไม่ได้หมายความว่าตัวบอกไฟล์ที่เปลี่ยนเส้นทางจะติดตามการเปลี่ยนแปลงในอนาคตทั้งหมดของตัวบ่งชี้ไฟล์เป้าหมาย มันจะใช้กับสถานะปัจจุบันเท่านั้น


ขอบคุณดูเหมือนว่าจะเป็นคำอธิบายที่เป็นธรรมชาติมากกว่านี้! :) คุณพิมพ์ผิดเล็กน้อยใน2.ส่วนที่สอง; และไม่ได้fd/1 -> file.txt fd/2 -> file.txt
ฝึกซ้อม Heartnet

11

ฉันคิดว่ามันช่วยในการคิดว่าเชลล์จะตั้งค่าการเปลี่ยนเส้นทางทางซ้ายก่อนและดำเนินการให้เสร็จก่อนที่จะตั้งค่าการเปลี่ยนเส้นทางครั้งต่อไป

Linux Command Lineของ William Shotts กล่าว

ก่อนอื่นเราเปลี่ยนเส้นทางเอาต์พุตมาตรฐานไปยังไฟล์จากนั้นเราเปลี่ยนเส้นทางไฟล์ descriptor 2 (ข้อผิดพลาดมาตรฐาน) ไปยัง file descriptor หนึ่ง (เอาต์พุตมาตรฐาน)

มันสมเหตุสมผลแล้ว แต่

ขอให้สังเกตว่าลำดับของการเปลี่ยนเส้นทางมีความสำคัญ การเปลี่ยนเส้นทางของข้อผิดพลาดมาตรฐานจะต้องเกิดขึ้นเสมอหลังจากเปลี่ยนเส้นทางเอาต์พุตมาตรฐานหรือไม่ทำงาน

แต่ที่จริงแล้วเราสามารถเปลี่ยนเส้นทาง stdout ไปยัง stderr หลังจากเปลี่ยนเส้นทาง stderr ไปยังไฟล์ที่มีผลเหมือนกัน

$ uname -r 2>/dev/null 1>&2
$ 

ดังนั้นในcommand > file 2>&1เชลล์ส่ง stdout ไปยังไฟล์จากนั้นส่ง stderr ไปยัง stdout (ซึ่งกำลังถูกส่งไปยังไฟล์) ในcommand 2>&1 > fileกรณีแรกเชลล์เปลี่ยนเส้นทาง stderr ไปยัง stdout (เช่นแสดงใน terminal ที่ปกติ stdout ไป) แล้วหลังจากนั้นเปลี่ยนเส้นทาง stdout ไปที่ไฟล์ TLCL ทำให้เข้าใจผิดว่าเราต้องเปลี่ยนเส้นทาง stdout ก่อน: เนื่องจากเราสามารถเปลี่ยนเส้นทาง stderr ไปยังไฟล์ก่อนแล้วจึงส่ง stdout ไป สิ่งที่เราไม่สามารถทำได้คือเปลี่ยนเส้นทาง stdout ไปยัง stderr หรือกลับกันก่อนเปลี่ยนเส้นทางไปยังไฟล์ ตัวอย่างอื่น

$ strace uname -r 1>&2 2> /dev/null 
4.8.0-30-generic

เราอาจคิดว่าสิ่งนี้จะกำจัด stdout ไปยังสถานที่เดียวกันกับ stderr แต่มันไม่เปลี่ยน stdout ไปเป็น stderr (หน้าจอ) ก่อนจากนั้นจึงเปลี่ยนเส้นทาง stderr เท่านั้นเมื่อเราลองอีกทางหนึ่ง ...

ฉันหวังว่านี่จะทำให้แสงสว่างเล็กน้อย ...


มีคารมคมคายมากขึ้น!
Arronical

อ่าตอนนี้ฉันเข้าใจแล้ว! ขอบคุณมาก @Zanna และ @Arronical! เพิ่งเริ่มออกเดินทางบรรทัดคำสั่งของฉัน :)
ฝึกซ้อม Heartnet

@TrainHeartnet มันเป็นความสุข! หวังว่าคุณจะสนุกกับมันมากเท่ากับฉัน: D
Zanna

@Zanna: จริงฉัน! : D
ฝึกซ้อม Heartnet

2
@TrainHeartnet ไม่ต้องกังวลโลกแห่งความหงุดหงิดและความสุขรอคุณอยู่!
Arronical

10

คุณได้รับคำตอบที่ดีมากแล้ว ฉันขอเน้นว่ามีสองแนวคิดที่แตกต่างกันที่นี่ความเข้าใจซึ่งช่วยอย่างมาก:

พื้นหลัง: ตัวอธิบายไฟล์เทียบกับตารางไฟล์

ไฟล์ descriptor ของคุณเป็นเพียงตัวเลข 0 ... n ซึ่งเป็นดัชนีในตาราง descriptor ไฟล์ในกระบวนการของคุณ ตามแบบแผน STDIN = 0, STDOUT = 1, STDERR = 2 (โปรดทราบว่าคำศัพท์STDINอื่น ๆ ที่นี่เป็นเพียงสัญลักษณ์ / มาโครที่ใช้โดยการประชุมในภาษาการเขียนโปรแกรมและหน้าคนไม่มีวัตถุ "จริง" เรียกว่า STDIN สำหรับ จุดประสงค์ของการสนทนานี้ STDIN คือ 0 เป็นต้น)

ตารางตัวอธิบายไฟล์นั้นในตัวมันเองไม่มีข้อมูลใด ๆ เกี่ยวกับไฟล์จริง แต่จะมีตัวชี้ไปยังตารางไฟล์อื่น หลังมีข้อมูลเกี่ยวกับไฟล์จริง (หรืออุปกรณ์บล็อกหรือไปป์หรืออะไรก็ตามที่ Linux สามารถจัดการผ่านกลไกไฟล์) และข้อมูลเพิ่มเติม (เช่นไม่ว่าจะเป็นการอ่านหรือเขียน)

ดังนั้นเมื่อคุณใช้>หรือ<ในเชลล์คุณเพียงแค่เปลี่ยนพอยน์เตอร์ของไฟล์ descriptor ตามลำดับเพื่อชี้ไปยังสิ่งอื่น ไวยากรณ์2>&1เพียงแค่บ่งชี้ 2 ไปที่ใดก็ได้ 1 คะแนน > file.txtเพียงแค่เปิดfile.txtสำหรับการเขียนและให้ STDOUT (ไฟล์ decsriptor 1) ชี้ไปที่

มีสารพัดอื่น ๆ เช่น2>(xxx) (เช่น: สร้างกระบวนการใหม่ทำงานxxxสร้างไพพ์เชื่อมต่อไฟล์ descriptor 0 ของกระบวนการใหม่ไปยังจุดสิ้นสุดการอ่านของไพพ์และเชื่อมต่อไฟล์ descriptor 2 ของกระบวนการดั้งเดิมไปยังจุดสิ้นสุดการเขียนของ ท่อ).

นี่เป็นพื้นฐานสำหรับ "file handle magic" ในซอฟต์แวร์อื่นที่ไม่ใช่เชลล์ของคุณ ตัวอย่างเช่นในสคริปต์ Perl ของคุณให้dupไลเซนส์ตัวอธิบายไฟล์ STDOUT เป็นอีกตัวหนึ่ง (ชั่วคราว) จากนั้นเปิด STDOUT อีกครั้งเป็นไฟล์ชั่วคราวที่สร้างขึ้นใหม่ จากจุดนี้เป็นต้นไปเอาต์พุต STDOUT ทั้งหมดจากสคริปต์ Perl ของคุณเองและการsystem()เรียกใช้สคริปต์ทั้งหมดจะสิ้นสุดในไฟล์ชั่วคราวนั้น เมื่อเสร็จแล้วคุณสามารถdupSTDOUT ของคุณไปที่ descriptor ชั่วคราวที่คุณบันทึกไว้และ presto ทั้งหมดนี้เป็นเหมือนเดิม คุณสามารถเขียนไปที่ descriptor ชั่วคราวนั้นได้ในขณะที่เอาท์พุท STDOUT ที่แท้จริงของคุณไปที่ไฟล์ชั่วคราวคุณยังสามารถเอาท์พุทของจริง ๆ ไปยังSTDOUT ตัวจริง

ตอบ

หากต้องการใช้ข้อมูลพื้นหลังที่ให้ไว้กับคำถามของคุณ:

เชลล์สั่งรันคำสั่งและเปลี่ยนเส้นทางสตรีมในลำดับใด

จากซ้ายไปขวา

<command> > file.txt 2>&1

  1. fork ออกกระบวนการใหม่
  2. เปิดfile.txtและเก็บตัวชี้ไว้ในตัวอธิบายไฟล์ 1 (STDOUT)
  3. ชี้ STDERR (ตัวอธิบายไฟล์ 2) ไปยังสิ่งที่ fd 1 ชี้ไปในขณะนี้ (ซึ่งเป็นfile.txtหลักสูตรที่เปิดอยู่แล้ว)
  4. exec <command>

เห็นได้ชัดว่านี่เปลี่ยนเส้นทาง stderr ไปยัง stdout ก่อนจากนั้น stdout ที่ได้จะถูกเปลี่ยนเส้นทางไปยัง file.txt

นี่จะสมเหตุสมผลถ้ามีเพียงหนึ่งตาราง แต่ตามที่อธิบายข้างต้นมีสอง ตัวให้คำอธิบายไฟล์ไม่ได้ชี้ไปซ้ำ ๆ กันมันไม่มีเหตุผลที่จะคิดว่า "เปลี่ยนเส้นทาง STDERR ไปยัง STDOUT" ความคิดที่ถูกต้องคือ "ชี้ STDERR ไปยังทุกจุดของ STDOUT" หากคุณเปลี่ยน STDOUT ในภายหลัง STDERR จะคงอยู่ในที่เดิมมันจะไม่ไปพร้อมกับการเปลี่ยนแปลงเพิ่มเติมใน STDOUT อย่างน่าอัศจรรย์


การ upvoting สำหรับบิต "file handle magic" - แม้ว่าจะไม่ตอบคำถามที่ฉันได้เรียนรู้สิ่งใหม่ในวันนี้โดยตรง ...
Floris

3

คำสั่งซื้อจากซ้ายไปขวา คู่มือของ Bash ได้ครอบคลุมสิ่งที่คุณถามไปแล้ว อ้างอิงจากREDIRECTIONส่วนของคู่มือ:

   Redirections  are  processed  in  the
   order they appear, from left to right.

และอีกไม่กี่บรรทัดในภายหลัง:

   Note that the order of redirections is signifi
   cant.  For example, the command

          ls > dirlist 2>&1

   directs both standard output and standard error
   to the file dirlist, while the command

          ls 2>&1 > dirlist

   directs   only  the  standard  output  to  file
   dirlist, because the standard error was  dupli
   cated from the standard output before the stan
   dard output was redirected to dirlist.

สิ่งสำคัญคือต้องทราบว่าการเปลี่ยนเส้นทางได้รับการแก้ไขก่อนที่คำสั่งใด ๆ จะทำงาน ดูhttps://askubuntu.com/a/728386/295286


3

มันซ้ายไปขวาเสมอ ... ยกเว้นเมื่อไหร่

เช่นเดียวกับในคณิตศาสตร์ที่เราทำจากซ้ายไปขวายกเว้นการคูณและการหารทำก่อนการบวกและการลบยกเว้นการดำเนินการในวงเล็บ (+ -) จะดำเนินการก่อนการคูณและการหาร

ตามคู่มือการเริ่มต้นของ Bash ที่นี่ ( Bash Beginners Guide ) มีลำดับ 8 ลำดับขั้นของสิ่งที่มาก่อน (จากซ้ายไปขวา):

  1. การขยายรั้ง "{}"
  2. การขยายตัวของ Tilde "~"
  3. พารามิเตอร์ของเชลล์และนิพจน์ตัวแปร "$"
  4. การทดแทนคำสั่ง "$ (command)"
  5. นิพจน์ทางคณิตศาสตร์ "$ ((EXPRESSION))"
  6. ประมวลผลการทดแทนสิ่งที่เรากำลังพูดถึงที่นี่ "<(LIST)" หรือ "> (LIST)"
  7. การแยกคำ "'<space> <แท็บ> <newline>'"
  8. การขยายชื่อไฟล์ "*", "?" ฯลฯ

ดังนั้นจึงเป็นซ้ายไปขวาเสมอ ... ยกเว้นเมื่อ ...


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