วิธีบันทึก / dev / stdout ตำแหน่งเป้าหมายในสคริปต์ทุบตี?


12

ฉันมีสคริปต์ทุบตีบางอย่างซึ่งต้องการรักษา/dev/stdoutตำแหน่งเดิมไว้ก่อนที่จะแทนที่ไฟล์ descriptor ตัวที่หนึ่งด้วยตำแหน่งอื่น

ดังนั้นโดยธรรมชาติฉันเขียนอะไรบางอย่างเช่น

old_stdout=$(readlink -f /dev/stdout)

และมันไม่ทำงาน อย่างรวดเร็วฉันเข้าใจว่าปัญหาคืออะไร:

test@ubuntu:~$ echo $(readlink -f /dev/stdout)
/proc/5175/fd/pipe:[31764]
test@ubuntu:~$ readlink -f /dev/stdout
/dev/pts/18

Obvioulsly $()ทำงานใน subshell ซึ่งถูกไพพ์ไปยังเชลล์พาเรนต์

ดังนั้นคำถามคือ: มีวิธีที่เชื่อถือได้ (กำหนดขอบเขตการพกพาระหว่างการแจกแจง Linux) เพื่อบันทึก/dev/stdoutตำแหน่งเป็นสตริงในสคริปต์ทุบตีหรือไม่?


ฟังดูเหมือนปัญหา XYเล็กน้อย ปัญหาพื้นฐานคืออะไร?
Kusalananda

ปัญหาพื้นฐานคือสคริปต์การติดตั้งบางตัวที่ทำงานในสองโหมด - เงียบซึ่งมันบันทึกเอาท์พุททั้งหมดไปยังไฟล์และ verbose ซึ่งไม่เพียง แต่บันทึกลงในไฟล์เท่านั้น แต่ยังพิมพ์ทุกอย่างไปยังเทอร์มินัลด้วย แต่ในทั้งสองโหมดสคริปต์ต้องการโต้ตอบกับผู้ใช้เช่นพิมพ์ไปยังเทอร์มินัลและอ่านการตอบสนองของผู้ใช้ ดังนั้นฉันคิดว่าการประหยัด/dev/stdoutจะช่วยแก้ปัญหาด้วยการพิมพ์ข้อความในโหมดเงียบ ทางเลือกคือการเปลี่ยนเส้นทางทุกการกระทำอื่น ๆ ที่สร้างผลลัพธ์และมีจำนวนค่อนข้างมาก มากกว่า 100 ข้อความโต้ตอบของผู้ใช้ประมาณ 100 ครั้ง
alexey.e.egorov

stderrวิธีมาตรฐานของการมีปฏิสัมพันธ์กับผู้ใช้คือการพิมพ์ไป ยกตัวอย่างเช่นนี่คือสาเหตุที่การแจ้งเตือนเป็นไปstderrตามค่าเริ่มต้น
Kusalananda

น่าเสียดายที่stderrต้องมีการเปลี่ยนเส้นทางและบันทึกเนื่องจากสคริปต์เรียกโปรแกรมภายนอกจำนวนหนึ่งและข้อความข้อผิดพลาดที่เป็นไปได้ทั้งหมดจะถูกรวบรวมและบันทึกไว้
alexey.e.egorov

คำตอบ:


14

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

ในการทำซ้ำไฟล์ descriptor ไปยังอีกอันหนึ่งด้วย Bourne-like shell ไวยากรณ์คือ:

exec 3>&1

ด้านบน fd 1 ถูกทำซ้ำไปยัง fd 3

สิ่งที่ fd 3 เปิดอยู่ก่อนหน้านี้จะถูกปิด แต่โปรดทราบว่า fds 3 ถึง 9 (โดยปกติจะมากถึง 99 ด้วยyash) สงวนไว้สำหรับจุดประสงค์นั้น (และไม่มีความหมายพิเศษที่ตรงกันข้ามกับ 0, 1 หรือ 2) เชลล์รู้ว่าจะไม่ใช้สิ่งเหล่านี้เพื่อธุรกิจภายในของตัวเอง เหตุผลเดียวที่ fd 3 น่าจะเปิดไว้ล่วงหน้าก็คือเพราะคุณทำมันในสคริปต์1หรือผู้โทรรั่วไหลออกมา

จากนั้นคุณสามารถเปลี่ยน stdout เป็นอย่างอื่น:

exec > /dev/null

และในภายหลังเพื่อเรียกคืน stdout:

exec >&3 3>&-

( 3>&-กำลังจะปิดตัวอธิบายไฟล์ซึ่งเราไม่ต้องการอีกต่อไป)

ตอนนี้ปัญหาที่เกิดขึ้นนั้นยกเว้นใน ksh ทุกคำสั่งที่คุณใช้หลังจากนั้นexec 3>&1จะสืบทอด fd 3 นั่นคือการรั่วไหลของ fd โดยทั่วไปไม่ใช่เรื่องใหญ่ แต่อาจทำให้เกิดปัญหา

kshตั้งค่าแฟล็กclose-on-execบน fds เหล่านั้น (สำหรับ fds มากกว่า 2) แต่เชลล์อื่นและเชลล์อื่นไม่มีวิธีการตั้งค่าแฟล็กนั้นด้วยตนเอง

การทำงานกับเชลล์อื่น ๆ คือการปิด fd 3 สำหรับแต่ละคำสั่งทุกคำสั่งเช่น:

exec 3>&-

exec > file.log

ls 3>&-
uname 3>&-

exec >&3 3>&-

ยุ่งยาก. นี่คือวิธีที่ดีที่สุดที่จะไม่ใช้execเลย แต่เปลี่ยนเส้นทางกลุ่มคำสั่ง:

{
  ls
  uname
} > file.log

ที่นั่นมันเป็นเชลล์ที่ระมัดระวังในการบันทึก stdout และเรียกคืนมันในภายหลัง (และมันทำภายในโดยการทำซ้ำบน fd (เหนือ 9, สูงกว่า 99 สำหรับyash) ด้วยชุดแฟล็ก close-on-exec

หมายเหตุ1

ตอนนี้การจัดการ fds 3 ถึง 9 อาจยุ่งยากและมีปัญหาหากคุณใช้อย่างกว้างขวางหรือในฟังก์ชั่นโดยเฉพาะอย่างยิ่งถ้าสคริปต์ของคุณใช้รหัสบุคคลที่สามซึ่งอาจใช้ fds เหล่านั้น

เปลือกบาง ( zsh, bash, ksh93ทั้งหมดเพิ่มคุณลักษณะ ( แนะนำโดยโอลิเวอร์ Kiddle ของzsh ) รอบเวลาเดียวกันในปี 2005 หลังจากที่มันถูกกล่าวถึงในหมู่นักพัฒนาของพวกเขา) มีไวยากรณ์ทางเลือกในการกำหนด FD ฟรีครั้งแรกสูงกว่า 10 แทนซึ่งจะช่วยในกรณีนี้:

myfunction() {
  local fd
  exec {fd}>&1
  # stdout was duplicated onto a new fd above 10, whose actual value
  # is stored in the fd variable
  ...
  # it should even be safe to re-enter the function here
  ...
  exec >&"$fd" {fd}>&-
}

นอกจากนี้โค้ดของคุณผิดในแง่ที่ว่า fd 3 อาจถูกใช้ไปแล้วเนื่องจากมันเกิดขึ้นเมื่อ scrip เรียกใช้จากrc.localบริการเช่นคุณจะได้ใช้สิ่งที่ชอบexec {FD}>&1หรือบางสิ่งบางอย่าง แต่นี่รองรับเฉพาะใน bash 4 ซึ่งน่าเศร้าจริงๆ ดังนั้นนี่ไม่ใช่แบบพกพาจริงๆ
alexey.e.egorov

@ alexey.e.egorov ดูการแก้ไข
Stéphane Chazelas

Bash 3. * ไม่รองรับคุณสมบัตินี้และเวอร์ชั่นนี้ใช้ใน Centos 5 ซึ่งยังคงรองรับและใช้งานอยู่ และการหาคำอธิบายฟรีแล้วeval "exec $i>&1"ก็เป็นสิ่งที่ฉันต้องการหลีกเลี่ยงเพราะมันยุ่งยาก ฉันสามารถจริงๆพึ่งพาที่ FDS เหนือ 9 จะเป็นอิสระแล้ว?
alexey.e.egorov

@ alexey.e.egorov ไม่คุณกำลังมองย้อนกลับ fds 3 ถึง 9 มีอิสระที่จะใช้ (และมันก็ขึ้นอยู่กับคุณที่จะจัดการพวกเขาตามที่คุณต้องการ) และมีไว้สำหรับวัตถุประสงค์ดังกล่าว fds ด้านบน 9 อาจถูกใช้โดยเชลล์ภายในและการปิดมันอาจส่งผลร้าย กระสุนส่วนใหญ่จะไม่ยอมให้คุณใช้ bashจะให้คุณยิงตัวเองที่เท้า
Stéphane Chazelas

2
@ alexey.e.egorov หากสคริปต์เริ่มต้นคุณมี fds เป็น (3..9) เปิดนั่นเป็นเพราะผู้โทรของคุณลืมที่จะปิดพวกเขาหรือตั้งค่าสถานะ close-on-exec บนพวกเขา นั่นคือสิ่งที่ฉันเรียกว่าการรั่วไหลของ fd ทีนี้ผู้โทรที่ตั้งใจส่งผ่าน fds เหล่านั้นไปให้คุณดังนั้นคุณสามารถอ่านและ / หรือเขียนข้อมูลจาก / ถึงพวกเขาได้ แต่จากนั้นคุณก็รู้เกี่ยวกับมัน หากคุณไม่ทราบเกี่ยวกับพวกเขาคุณไม่สนใจคุณสามารถปิดพวกเขาได้อย่างอิสระ (โปรดทราบว่ามันเพิ่งปิดกระบวนการของสคริปต์ของคุณไม่ใช่ผู้โทรของคุณ)
Stéphane Chazelas

3

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

ทางออกที่ง่ายที่สุดคือการใช้ sub-shell เพื่อรันสิ่งที่คุณต้องการเปลี่ยนเส้นทางเพื่อให้การประมวลผลสามารถเปลี่ยนกลับไปที่ top-shell ซึ่งมี I / O มาตรฐานเหมือนเดิม

โซลูชันสำรองจะใช้ttyเพื่อระบุอุปกรณ์ TTY และควบคุม I / O ในสคริปต์ของคุณ ตัวอย่างเช่น:

dev=$(tty)

และจากนั้นคุณสามารถ ..

echo message > $dev

> โซลูชันสำรองคือใช้ tty เพื่อระบุอุปกรณ์ TTY และควบคุม I / O ในสคริปต์ของคุณ วิธีนี้ทำได้อย่างไร
alexey.e.egorov

1
ฉันเพิ่งรวมตัวอย่างในคำตอบของฉัน
Julie Pelletier

1

$$ จะช่วยให้คุณได้รับ PID กระบวนการปัจจุบันในกรณีที่เชลล์โต้ตอบหรือสคริปต์เชลล์ PID ที่เกี่ยวข้อง

ดังนั้นคุณสามารถใช้:

readlink -f /proc/$$/fd/1

ตัวอย่าง:

% readlink -f /proc/$$/fd/1
/dev/pts/33

% var=$(readlink -f /proc/$$/fd/1)

% echo $var                       
/dev/pts/33

1
ในขณะที่มันใช้งานได้การใช้/procโครงสร้างที่เฉพาะเจาะจงทำให้เกิดปัญหาการพกพาเช่นเดียวกับที่ใช้/dev/stdoutในคำถาม
Julie Pelletier

1
@JuliePelletier อาศัย/procโครงสร้างspecif หรือไม่ มันจะทำงานกับลีนุกซ์ใด ๆ ที่มีprocfs..
heemayl

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