นี่คือการพิมพ์ผิดในส่วนการเปลี่ยนเส้นทางของคู่มือทุบตีหรือไม่?


13
Note that the order of redirections is significant.  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 duplicated from the standard output before the standard
   output was redirected to dirlist.

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

ดูเหมือนว่าควรพูดว่า "เนื่องจากข้อผิดพลาดมาตรฐานซ้ำจากเอาท์พุทมาตรฐานหลังจากที่เอาต์พุตมาตรฐานถูกเปลี่ยนเส้นทางไปยัง dirlist" หาก STDERR ถูกส่งไปยัง STDOUT ก่อนที่ STDOUT จะถูกนำไปยังไฟล์ไฟล์นั้นจะไม่มี STDOUT และ STDERR หรือไม่

ใครช่วยกรุณาเคลียร์สิ่งนี้ให้ฉันได้ไหม มันเป็นเพียงความเข้าใจในการอ่านที่ไม่ดีในส่วนของฉันหรือไม่? การใช้คำซ้ำนี้ดูแปลกสำหรับฉันในบริบทนี้ บางทีนั่นอาจเป็นการขว้างฉัน



1
กรณีคลาสสิกของการมิกซ์อัพการทำงานที่ "ตามตัวอักษร" vs "โดยการอ้างอิง" เมื่อคุณทำซ้ำ descriptor ไฟล์มันเป็นการดำเนินการตามค่า ในการเขียนโปรแกรมหลังจากที่a = 1; b = a; a = 2คุณคาดหวังว่าa == 2 && b == 1จะเป็นจริง การเปลี่ยนเส้นทาง2>&1คล้ายกับการb = aมอบหมาย - มันเป็นไปตามค่าไม่ใช่โดยการอ้างอิง 2>&1ไม่ได้จัดไฟล์ file descriptor 2 เป็น file descriptor 1 สำหรับ eternity ทั้งหมด - พวกมันยังคงเป็น 2 descriptor ไฟล์ที่แตกต่างกันซึ่งเกิดขึ้นเพื่อชี้ไปที่ไฟล์เดียวกัน
jw013

คำตอบ:


23

การทำสำเนาเป็นส่วนสำคัญที่นี่

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

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

ตอนนี้ถ้าเราเรียกโดยไม่ต้องเปลี่ยนเส้นทางการส่งออกและข้อความผิดพลาดไปที่ใต้เครื่องของฉันls -l/dev/pts/1

หากเราเปลี่ยนเส้นทางSTDOUTไปยังไฟล์ ( ls -l > dirlist) เป็นครั้งแรกจะมีลักษณะดังนี้:

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

เมื่อเราแล้วเปลี่ยนเส้นทางSTDERRไปยังที่ซ้ำกันของSTDOUT's อธิบายไฟล์ ( ls -l > dirlist 2>&1) STDERRจะไปซ้ำกับ/home/bon/dirlist:

STDOUT ---> /home/bon/dirlist
STDERR ---> /home/bon/dirlist

ถ้าเราจะเป็นครั้งแรกเปลี่ยนเส้นทางSTDERRไปซ้ำกับSTDOUT's อธิบายไฟล์ ( ls -l 2>&1):

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

และจากนั้น STDOUTไปยังแฟ้ม ( ls -l 2>&1 > dirlist) เราจะได้รับนี้

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

ที่นี่STDERRยังคงไปที่สถานี

คุณจะเห็นว่าการสั่งซื้อในหน้าคนถูกต้อง


การทดสอบการเปลี่ยนเส้นทาง

ตอนนี้คุณสามารถทดสอบด้วยตัวเอง ใช้ls -l /proc/$$/fd/คุณจะเห็นที่ไหนSTDOUT(กับ fd 1) และSTDERR(กับ fd 2) จะไปสำหรับกระบวนการปัจจุบัน:

$ ls -l /proc/$$/fd/
total 0
lrwx------ 1 bon bon 64 Jul 24 18:19 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 07:41 2 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 255 -> /dev/pts/1

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

$ cat > lookfd.sh
#!/bin/sh
ls -l /proc/$$/fd/
^D
$ chmod +x lookfd.sh

(ด้วยCtrlDคุณส่งสิ้นสุดไฟล์และหยุดcatคำสั่งจากการอ่านSTDIN)

ตอนนี้เรียกสคริปต์นี้ด้วยการเปลี่ยนเส้นทางชุดค่าผสมที่แตกต่างกัน:

$ ./lookfd.sh 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:08 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:08 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh 2>&1 > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out 2>&1
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:11 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:11 1 -> /home/bon/foo.out
l-wx------ 1 bon bon 64 Jul 24 19:11 2 -> /home/bon/foo.out
lr-x------ 1 bon bon 64 Jul 24 19:11 255 -> /home/bon/lookfd.sh

คุณสามารถเห็นได้ว่าตัวอธิบายไฟล์ 1 (สำหรับSTDOUT) และ 2 (สำหรับSTDERR) แตกต่างกันไป เพื่อความสนุกคุณสามารถเปลี่ยนเส้นทางSTDINและดูผลลัพธ์:

$ ./lookfd.sh < /dev/zero
total 0
lr-x------ 1 bon bon 64 Jul 24 19:18 0 -> /dev/zero
lrwx------ 1 bon bon 64 Jul 24 19:18 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:18 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:18 255 -> /home/bon/lookfd.sh

(เหลือคำถามสำหรับผู้อ่าน: ไฟล์ descriptor 255 point อยู่ที่ไหน? ;-))


+1 - คำตอบที่ยอดเยี่ยม ตัวอย่างที่เขียนดีและยอดเยี่ยมมาก ขอบคุณ!!!
slm

ฉันเห็นฉันคิดว่าความเข้าใจผิดของฉันคือการเปลี่ยนเส้นทางจะคงอยู่สำหรับคำสั่งต่อไปนี้ทั้งหมดเพื่อให้ STDERR ใด ๆ ที่เหลือของบรรทัดจะไปที่ STDOUT
Gregg Leventhal

2

ไม่คู่มือถูกต้อง

หาก 1 จุดแรกไปยังเทอร์มินัลและ 2 ไปยังเทอร์มินัลด้วยเช่นกัน:

command  2>&1   1>somewhere

การประเมินการเปลี่ยนเส้นทางจะเกิดขึ้นจากซ้ายไปขวา

ดังนั้นมันเป็นครั้งแรกจะมีการประเมิน2>&1และทำให้ FIRST คัดลอกสิ่งที่ FD 1ใช้ในการชี้ไป (เช่นอธิบายไฟล์ของthe terminalมักจะ / dev / TTY) ลง 2FD

ดังนั้น ณ จุดนั้น2ตอนนี้fd ชี้ไปที่ที่ fd 1เคยชี้ไปที่ ( the terminal)

จากนั้นจะทำการประเมิน1>somewhereส่วนและจะคัดลอกไฟล์ descriptor ของsomewhereใน fd 1(ดังนั้น ณ จุดนั้น1ตอนนี้fd ชี้ไปที่somewhereและ fd 2ยังคงชี้ไปที่the terminal)

ดังนั้นมันจึงพิมพ์ 1 เป็น "ที่ไหนสักแห่ง" และ 2 ลงในเทอร์มินัลเนื่องจาก 2 ถูกทำซ้ำจาก 1 ก่อน 1 มีการเปลี่ยนแปลง

ลำดับอื่น ๆ :

command  1>somewhere 2>&1

FD แรกเปลี่ยนเส้นทางจะ1ไปsomewhereแล้วคัดลอกที่อ้างอิงเดียวกันเข้า FD 2 ดังนั้นในตอนท้าย 2 somewhereยังจุดที่จะต้อง แต่พวกเขาจะไม่ "เชื่อมโยง" จากนี้ไป แต่ละคนยังสามารถเปลี่ยนเส้นทางแยกกัน

อดีต:

command  1>somewhere 2>&1
exec 2>/dev/null

ในตอนท้ายของหนึ่ง fd 1ชี้ไปที่somewhereและ fd 2ถูกนำไป/dev/null

ชื่อปกติสำหรับ fd 1คือ STDOUT (เอาต์พุตมาตรฐาน) และชื่อปกติสำหรับ fd 2คือ STDERR (ข้อผิดพลาดมาตรฐานเนื่องจากมักใช้เพื่อแสดงข้อผิดพลาดโดยไม่รบกวน STDOUT)


@ Michael-mrozek: ขอบคุณสำหรับการแก้ไข แต่ฉันยืนยันในการพูดว่า "คัดลอก" แทน "ซ้ำ" เป็น "ซ้ำ" อาจนำไปสู่หนึ่งที่จะเชื่อว่าต่อจากนี้ไปทั้งสองเป็น "สิ่งเดียวกัน" ซึ่งไม่เป็นความจริง เช่น: cmd 1>somewhere 2>&1 ; exec 2>/dev/nullหลังจาก exec, มีเพียง 2 คนเท่านั้นที่ถูกเปลี่ยนเส้นทางไปยัง / dev / null (1 ยังคงเป็น "ที่ไหนสักแห่ง") ฉันต้องการความช่วยเหลือในการหาวิธีที่จะพูดว่า "อะไรคือ 1 คะแนนถึง" แทนที่จะเป็น "fd 1" อย่างไรก็ตาม ... เนื่องจากมันทำให้สับสน ...
Olivier Dulac

1
ฉันไม่แน่ใจว่าคุณหมายถึงอะไร คุณเป็นคนที่เปลี่ยนจาก "คัดลอก" เป็น "ซ้ำ" ทั้งหมดที่ฉันทำคือใช้ประโยชน์และจัดรูปแบบสิ่งต่าง ๆ ฉันไม่ได้เปลี่ยนคำใด ๆ
Michael Mrozek

อ๊ะ ... ^^ ขอโทษ และฉันแก้ไขอีกครั้งเพื่อปรับโครงสร้างให้แม่นยำยิ่งขึ้นสิ่งที่คัดลอกลงในสิ่งที่ ^^
Olivier Dulac

1

ฉันคิดว่าส่วนที่สับสนที่นี่คือความเข้าใจผิดที่เปลี่ยนเส้นทาง stderr ไปยัง stdout จริง ๆ แล้วเชื่อมต่อสตรีมทั้งสอง

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

ฉันคิดว่ามันใช้งานง่าย แต่นั่นก็เป็นวิธีการทำงาน ตั้งค่าตำแหน่งที่คุณต้องการเขียนถึงก่อนแล้วบอกทุกคนว่า "คัดลอกฉัน" หวังว่าชัดเจน ...


0

ทำสำเนา ...

มีความสำคัญ แต่ในแง่ที่ว่ามันเป็นแหล่งของความสับสนมาก มันค่อนข้างง่ายจริงๆ คำตอบนี้เป็นเพียงภาพประกอบ "รุนแรง"

คำตอบที่ยอมรับนั้นดี แต่ยาวเกินไปและจะเน้น "การทำซ้ำ"

คำถามอย่างชาญฉลาดลงท้ายด้วย:

การใช้คำซ้ำนี้ดูแปลกสำหรับฉันในบริบทนี้ บางทีนั่นอาจเป็นการขว้างฉัน

ฉันใช้เครื่องหมายทุบตีและกำหนดตัวแปร "หนึ่ง" และ "สอง" เป็น filehandles "1" และ "2" (การส่งออก) ผู้ประกอบการเปลี่ยนเส้นทางเป็นที่ได้รับมอบหมาย> และหมายถึง "คุณค่า" ของ=&$

ตัวอย่าง bash man (เพิ่มค่าเริ่มต้น "1")

ls 1>dirlist 2>&1      # both to dirlist
ls 2>&1 1>dirlist      # 1 to dirlist, 2 stays on tty/screen 

เป็น:

one=dirlist  two=$one

และ

two=$one   one=dirlist

และนี่ก็ไม่ใช่แบบอัตโนมัติสำหรับฉันและคนอื่น ๆ ที่ฉันเดา บรรทัดแรกจะพาคุณไปด้วย$oneและ$twoทั้งคู่มี "dirlist" แน่นอน.

บรรทัดที่สองเริ่มต้นด้วยการมอบหมายที่ไร้ประโยชน์ ทั้งสองเริ่มต้นด้วยคำจำกัดความด้วย "TTY" (สัญลักษณ์เป็นสัญลักษณ์) ตามทิศทาง ; ไม่มีการเปลี่ยนแปลงค่าโดยการมอบหมายนี้และด้วยตัวแปรเช่นเดียวกับ filehandles ไม่มีการเชื่อมโยงอย่างน่าอัศจรรย์ ตัวแปรที่ไม่ได้รับผลกระทบจากต่อไปนี้two one=dirlistไม่แน่นอน

Sombody ที่นี่ (6 ปีที่แล้ว) แนะนำ "ชี้ไปที่" แทนที่จะเป็น "คัดลอก" หรือ "ซ้ำ" แล้วก็ตระหนักว่านั่นอาจทำให้เกิดความสับสนได้เช่นกัน

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

ถ้า - และถ้า - คุณกำลังมองหาวิธีที่จะได้รับหมายเลขงานที่น่าแปลกใจบนคอนโซลของคุณแล้วข้อความ "เสร็จสิ้น" พร้อมโบนัสเป็นไฟล์ชื่อ "2" แล้วคุณจะไป:

ls 1>2& 2>/dev/null

มันอ่านตามธรรมชาติเป็น " คัดลอก" / "ทำซ้ำ" 1-2 และจากนั้นทั้งสองร่วมกันโมฆะ แต่ความคิดผิดและไวยากรณ์ (แต่ไม่มีข้อผิดพลาดทางไวยากรณ์มันถูกต้อง)

วิธีที่ถูกต้องในการวางแผนคือเปลี่ยนเส้นทางของทั้งสองเป็นโมฆะจากนั้นเปลี่ยนเส้นทาง OTHER ไปยังตำแหน่งเดิม:

ls 1>/dev/null 2>&1
# or 
ls 2>/dev/null 1>&2

(ส่วนที่เหลือของ "1" สามารถนำไปทิ้งได้)

(ตกลงตามมาตรฐาน A ไม่นานเกินไป แต่เป็นรายการมากเกินไป - หรือ: การสร้างภาพข้อมูลที่ดีมากไม่ใช่คำอธิบายที่ดีมาก)

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