การใช้งานในทางปฏิบัติสำหรับการย้ายไฟล์อธิบาย


16

ตามหน้าคนทุบตี:

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

   [n]<&digit-

ย้าย file descriptor digitไปที่ file descriptor nหรืออินพุตมาตรฐาน (file descriptor 0) หากnไม่ได้ระบุไว้ digitถูกปิดหลังจากการพิมพ์nซ้ำ

การย้าย "file descriptor" ไปยังอีกอันหนึ่งหมายความว่าอะไร? สถานการณ์ทั่วไปสำหรับการฝึกเช่นนี้มีอะไรบ้าง

คำตอบ:


14

3>&4-เป็นส่วนขยาย ksh93 ที่สนับสนุนโดย bash และสั้นสำหรับ3>&4 4>&-นั่นคือ 3 ตอนนี้ชี้ไปที่ที่ 4 เคยเป็นและ 4 ถูกปิดแล้วดังนั้นสิ่งที่ชี้ไปที่ 4 ได้ย้ายไปที่ 3

การใช้งานทั่วไปจะเป็นในกรณีที่คุณทำซ้ำstdinหรือstdoutบันทึกสำเนาและต้องการคืนค่าเช่นใน:

สมมติว่าคุณต้องการจับ stderr ของคำสั่ง (และ stderr เท่านั้น) ในขณะที่ปล่อย stdout ไว้คนเดียวในตัวแปร

การทดแทนคำสั่งvar=$(cmd)สร้างไปป์ ปลายการเขียนของไปป์กลายเป็นcmdstdout (file descriptor 1) และส่วนอื่น ๆ ถูกอ่านโดยเชลล์เพื่อเติมตัวแปร

ตอนนี้ถ้าคุณต้องการที่จะไปให้กับตัวแปรที่คุณสามารถทำ:stderr var=$(cmd 2>&1)ตอนนี้ทั้ง fd 1 (stdout) และ 2 (stderr) ไปที่ pipe (และในที่สุดก็ถึงตัวแปร) ซึ่งเป็นเพียงครึ่งหนึ่งของสิ่งที่เราต้องการ

ถ้าเราทำvar=$(cmd 2>&1-)(ย่อvar=$(cmd 2>&1 >&-) ตอนนี้cmdstderr เท่านั้นไปที่ไปป์ แต่ fd 1 ถูกปิด หากcmdพยายามที่จะเขียนเอาต์พุตใด ๆ ที่จะกลับมาพร้อมกับEBADFข้อผิดพลาดหากมันเปิดไฟล์มันจะได้รับ fd ฟรีครั้งแรกและไฟล์ที่เปิดจะได้รับมอบหมายให้stdoutเว้นแต่ว่าคำสั่งปกป้อง! ไม่ใช่สิ่งที่เราต้องการเช่นกัน

หากเราต้องการให้ stdout cmdถูกทิ้งไว้ตามลำพังนั่นคือชี้ไปที่ทรัพยากรเดียวกับที่ชี้ไปนอกการทดแทนคำสั่งดังนั้นเราจำเป็นต้องนำทรัพยากรนั้นไปใช้ในการทดแทนคำสั่ง เพื่อที่เราจะสามารถทำสำเนาstdout นอกการทดแทนคำสั่งเพื่อนำเข้าไปข้างใน

{
  var=$(cmd)
} 3>&1

ซึ่งเป็นวิธีที่สะอาดกว่าการเขียน:

exec 3>&1
var=$(cmd)
exec 3>&-

(ซึ่งมีประโยชน์ในการกู้คืน fd 3 แทนที่จะปิดในตอนท้าย)

จากนั้นเมื่อถึง{(หรือexec 3>&1) และสูงสุด}ทั้ง fd 1 และ 3 ชี้ไปที่ทรัพยากรเดียวกัน fd 1 ชี้ไปที่เริ่มต้น fd 3 จะชี้ไปที่ทรัพยากรภายในการทดแทนคำสั่ง (การทดแทนคำสั่งจะเปลี่ยนเส้นทาง fd 1, stdout เท่านั้น) ดังนั้นสำหรับcmdเรามี fds 1, 2, 3:

  1. ไปป์ที่ var
  2. มิได้ถูกแตะต้อง
  3. เช่นเดียวกับสิ่งที่ 1 คะแนนไปนอกการทดแทนคำสั่ง

หากเราเปลี่ยนเป็น:

{
  var=$(cmd 2>&1 >&3)
} 3>&1-

จากนั้นมันจะกลายเป็น:

  1. เช่นเดียวกับสิ่งที่ 1 คะแนนไปนอกการทดแทนคำสั่ง
  2. ไปป์ที่ var
  3. เช่นเดียวกับสิ่งที่ 1 คะแนนไปนอกการทดแทนคำสั่ง

ตอนนี้เรามีสิ่งที่เราต้องการ: stderr ไปที่ pipe และ stdout นั้นไม่ถูกแตะต้อง อย่างไรก็ตามเรากำลังรั่วไหลที่ fd cmd3

ในขณะที่คำสั่ง (ตามแบบแผน) ถือว่า fds 0 ถึง 2 เป็นแบบเปิดและเป็นอินพุตมาตรฐานเอาต์พุตและข้อผิดพลาด แต่จะไม่ถือว่า fds อื่น ๆ ส่วนใหญ่แล้วพวกเขาจะออกจาก fd 3 ที่ไม่มีใครแตะต้อง หากพวกเขาต้องการตัวอธิบายไฟล์อื่นพวกเขาจะทำสิ่งopen()/dup()/socket()...ที่จะส่งกลับไฟล์อธิบายครั้งแรกที่มีอยู่ หาก (เช่นเชลล์สคริปต์ที่ทำexec 3>&1) พวกเขาจำเป็นต้องใช้สิ่งนั้นfdโดยเฉพาะพวกเขาจะกำหนดให้กับบางสิ่ง (และในกระบวนการนั้นทรัพยากรที่ถือโดย fd 3 ของเราจะถูกปล่อยโดยกระบวนการนั้น)

มันเป็นวิธีที่ดีที่ใกล้ fd 3 ตั้งแต่cmdไม่ได้ทำให้การใช้งานของมัน cmdแต่มันไม่ใช่เรื่องใหญ่ถ้าเราปล่อยให้มันได้รับมอบหมายก่อนที่เราเรียกว่า ปัญหาอาจเกิดขึ้น: ที่cmd(และกระบวนการอื่น ๆ ที่อาจเกิดขึ้น) นั้นมี fd น้อยลงหนึ่งตัว ปัญหาร้ายแรงที่อาจเกิดขึ้นคือถ้าทรัพยากรที่ fd ชี้ไปนั้นอาจจบลงด้วยกระบวนการที่เกิดcmdในพื้นหลัง อาจเป็นเรื่องที่น่ากังวลหากทรัพยากรนั้นเป็นช่องทางหรือช่องทางการสื่อสารระหว่างกระบวนการอื่น ๆ (เช่นเมื่อสคริปต์ของคุณถูกเรียกใช้script_output=$(your-script)) เนื่องจากจะหมายถึงกระบวนการที่อ่านจากปลายอีกด้านหนึ่งจะไม่เห็นจุดสิ้นสุดของไฟล์จนกระทั่ง กระบวนการพื้นหลังยุติลง

ดังนั้นที่นี่จะดีกว่าที่จะเขียน:

{
  var=$(cmd 2>&1 >&3 3>&-)
} 3>&1

ซึ่งด้วยbashสามารถย่อให้:

{
  var=$(cmd 2>&1 >&3-)
} 3>&1

เพื่อสรุปสาเหตุที่ใช้บ่อย:

  1. มันไม่ได้มาตรฐานและแค่น้ำตาลซินแทคติค คุณต้องสมดุลการบันทึกการกดแป้นบางครั้งด้วยการทำให้สคริปต์ของคุณพกพาได้น้อยลงและเห็นได้ชัดน้อยลงสำหรับคนที่ไม่คุ้นเคยกับคุณสมบัติที่ผิดปกตินั้น
  2. จำเป็นที่จะต้องปิด FD เดิมหลังจากการทำซ้ำมันก็มักจะมองข้ามเพราะส่วนใหญ่เวลาที่เราจะไม่ต้องทนทุกข์ทรมานจากผลที่ตามมาเพื่อให้เราเพียงแค่ทำ>&3แทนหรือ>&3->&3 3>&-

หลักฐานว่ามันไม่ค่อยได้ใช้ตามที่คุณพบคือว่ามันเป็นของปลอมในทุบตี ในทุบตีcompound-command 3>&4-หรือany-builtin 3>&4-leaves fd 4 ปิดแม้หลังจากcompound-commandหรือany-builtinกลับมาแล้ว มีโปรแกรมแก้ไขเพื่อแก้ไขปัญหาในขณะนี้ (2013-02-19)


ขอบคุณตอนนี้ฉันรู้ว่าการเคลื่อนไหวของ fd คืออะไร ฉันมีคำถามพื้นฐาน 4 ข้อเกี่ยวกับตัวอย่างที่สอง (และโดยทั่วไป fds): 1) ใน cmd1 คุณทำให้ 2 (stderr) เป็นสำเนา 3 ถ้าคำสั่งจะใช้ 3 fd นี้ภายในหรือไม่ 2) เหตุใด 3> & 1 และ 4> & 1 จึงทำงาน การทำซ้ำ 3 และ 4 จะมีผลเฉพาะในสอง cmds เปลือกปัจจุบันจะได้รับผลกระทบด้วยหรือไม่ 3) ทำไมคุณปิด 4 ใน cmd1 และ 3 ใน cmd2 คำสั่งเหล่านั้นไม่ได้ใช้ fds ที่กล่าวถึงใช่ไหม? 4) ในข้อมูลโค้ดสุดท้ายจะเกิดอะไรขึ้นถ้า fd ซ้ำกับสิ่งที่ไม่มีอยู่ (ใน cmd1 และ cmd2) ฉันหมายถึง 3 และ 4 ตามลำดับ
เควนติน

@ เคว็นตินฉันใช้ตัวอย่างที่ง่ายขึ้นและอธิบายอีกเล็กน้อยโดยหวังว่าตอนนี้จะทำให้เกิดคำถามน้อยลงกว่าที่จะตอบ หากคุณยังมีคำถามที่ไม่เกี่ยวข้องโดยตรงกับไวยากรณ์การเคลื่อนที่ fd ฉันขอแนะนำให้คุณถามคำถามแยกต่างหาก
Stéphane Chazelas

{ var=$(cmd 2>&1 >&3) ; } 3>&1-นี่ไม่ใช่การพิมพ์ผิดในการปิด 1
Quentin

@Quentin มันพิมพ์ผิดในที่ฉันไม่คิดว่าฉันตั้งใจที่จะรวมไว้ แต่มันทำให้ไม่แตกต่างอะไรตั้งแต่ภายในใช้เครื่องหมายวงเล็บที่ fd 1 (มันเป็นจุดรวมของการทำซ้ำมัน fd 3: เพราะเดิม 1 มิฉะนั้นจะไม่สามารถเข้าถึงได้ภายใน$(...))
Stéphane Chazelas

1
@Quentin เมื่อเข้าสู่{...}fd 3 คะแนนจากสิ่งที่ fd 1 ใช้เพื่อชี้และ fd 1 ถูกปิดจากนั้นเมื่อเข้าสู่$(...) fd 1 จะถูกตั้งค่าไปยังไปป์ที่ดึงข้อมูล$varจากนั้นcmd2 ต่อไปเช่นกันและ 1 ถึง 3 คะแนน ถึงนั่นคือด้านนอก 1 ข้อเท็จจริงที่ว่า 1 ยังคงปิดหลังจากนั้นเป็นข้อผิดพลาดในทุบตีฉันจะรายงานมัน ksh93 คุณลักษณะที่มานั้นไม่มีข้อบกพร่องนั้น
Stéphane Chazelas

4

มันหมายถึงการทำให้มันชี้ไปที่เดียวกับที่ไฟล์ descriptor อื่นทำ คุณจำเป็นต้องทำเช่นนี้มากไม่ค่อยนอกเหนือจากการจัดการที่แยกเป็นสัดส่วนที่ชัดเจนของการให้คำอธิบายถึงข้อผิดพลาดมาตรฐาน ( stderr, fd 2, /dev/stderr -> /proc/self/fd/2) มันมีประโยชน์ในบางกรณีที่ซับซ้อน

คู่มือการสคริปต์การทุบตีขั้นสูงมีตัวอย่างระดับการบันทึกที่ยาวกว่านี้และตัวอย่างข้อมูลนี้:

# Redirecting only stderr to a pipe.
exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script.

ในเวทมนต์ของ Source Mage เรายกตัวอย่างเช่นใช้มันเพื่อแยกแยะผลลัพธ์ที่แตกต่างจากบล็อกรหัสเดียวกัน:

  (
    # everything is set, so run the actual build infrastructure
    run_build
  ) 3> >(tee -a $C_LOG >> /dev/stdout) \
    2> >(tee -a $C_LOG 1>&2 > $VOYEUR_STDERR) \
     > >(tee -a $C_LOG > $VOYEUR_STDOUT)

แต่ก็มีการเปลี่ยนตัวกระบวนการเสริมท้ายด้วยเหตุผลการเข้าสู่ระบบ (Voyeur ตัดสินใจว่าข้อมูลที่ควรจะแสดงบนหน้าจอหรือเพียงแค่เข้าสู่ระบบ) แต่บางข้อความต้องเสมอนำเสนอ เพื่อให้บรรลุผลนั้นเราจะพิมพ์มันลงในไฟล์ descriptor 3 แล้วจัดการมันเป็นพิเศษ


0

ใน Unix ไฟล์ได้รับการจัดการโดย file descriptor (จำนวนเต็มเล็กน้อยเช่นอินพุตมาตรฐานคือ 0 เอาต์พุตมาตรฐานคือ 1 ข้อผิดพลาดมาตรฐานคือ 2 เมื่อคุณเปิดไฟล์อื่น ๆ พวกเขามักจะได้รับ descriptor ที่ไม่ได้ใช้น้อยที่สุด) ดังนั้นหากคุณรู้จัก inards ของโปรแกรมและคุณต้องการส่งเอาต์พุตที่ไปยัง file descriptor 5 ไปยังเอาต์พุตมาตรฐานคุณจะย้าย descriptor 5 ไปที่ 1 นั่นคือที่2> errorsมาจากและสิ่งปลูกสร้างต้องการ2>&1ทำข้อผิดพลาดซ้ำ ๆ กระแสเอาท์พุท

ดังนั้นแทบจะไม่เคยใช้เลย (ฉันจำไม่ได้ว่าใช้ครั้งเดียวหรือสองครั้งด้วยความโกรธในช่วงอายุ 25 ปีของการใช้ Unix แบบเอกสิทธิ์เฉพาะบุคคล) แต่เมื่อจำเป็นอย่างยิ่ง


แต่ทำไมไม่ทำซ้ำ file descriptor 1 ด้วยวิธีต่อไปนี้: 5> & 1? ฉันไม่เข้าใจว่าการใช้ FD เคลื่อนที่อย่างไรเนื่องจากเคอร์เนลกำลังจะปิดทันทีหลังจากนั้น ...
Quentin

นั่นไม่ได้ซ้ำกัน 5 เป็น 1 แต่จะส่ง 5 ไปยังตำแหน่งที่ 1 ไป และก่อนที่คุณจะถาม; ใช่มีสัญลักษณ์ที่แตกต่างกันหลายรูปซึ่งหยิบมาจากเปลือกของสารตั้งต้นที่หลากหลาย
vonbrand

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