ทำไม“ bash -x” ถึงทำลายสคริปต์นี้?


13

ฉันมีสคริปต์ที่วัดระยะเวลาที่คำสั่งดำเนินการ

มันต้องการtimeคำสั่ง"ของจริง" ความหมายไบนารีเป็นตัวอย่างใน/usr/bin/time(เนื่องจาก bash-built-in ไม่มี-fแฟล็ก)

ด้านล่างสคริปต์แบบง่ายซึ่งสามารถดีบั๊กได้:

#!/bin/bash

TIMESEC=$(echo blah | ( /usr/bin/time -f %e grep blah >/dev/null ) 2>&1 | awk -F. '{print $1}')

echo ABC--$TIMESEC--DEF

if [ "$TIMESEC" -eq 0 ] ; then
   echo "we are here!"
fi

บันทึกเป็น "test.sh" และดำเนินการ:

$ bash test.sh
ABC--0--DEF
we are here!

ดังนั้นมันจึงทำงานได้

ตอนนี้เรามาลองแก้ไขข้อบกพร่องโดยเพิ่ม "-x" ลงใน bash command line:

$ bash -x test.sh
++ echo blah
++ awk -F. '{print $1}'
+ TIMESEC='++ /usr/bin/time -f %e grep blah
0'
+ echo ABC--++ /usr/bin/time -f %e grep blah 0--DEF
ABC--++ /usr/bin/time -f %e grep blah 0--DEF
+ '[' '++ /usr/bin/time -f %e grep blah
0' -eq 0 ']'
test.sh: line 10: [: ++ /usr/bin/time -f %e grep blah
0: integer expression expected

ทำไมสคริปต์นี้แตกเมื่อเราใช้ "-x" และทำงานได้ดีหากไม่มีสคริปต์


1
หึ ดูเหมือนว่าจะ-xเปิดเมื่อการ$()สร้างจะได้รับการ-xส่งออกรวมเป็นส่วนหนึ่งของค่าผลลัพธ์ ไม่ทราบว่าเป็นพฤติกรรม "คาดหวัง" หรือข้อผิดพลาด แต่ ... หรืออาจเป็น subshell ()ภายในที่ให้-xผลผลิตจริง
Jeff Y

นอกเหนือ: การตั้งค่าBASH_XTRACEFDให้คุณเปลี่ยนเส้นทางของset -xเอาต์พุตไปยังที่อื่นซึ่งเป็นปัญหาน้อยกว่า
Charles Duffy

คำตอบ:


21

ปัญหาคือสายนี้:

TIMESEC=$(echo blah | ( /usr/bin/time -f %e grep blah >/dev/null ) 2>&1 | awk -F. '{print $1}')

เมื่อคุณเปลี่ยนเส้นทางข้อผิดพลาดมาตรฐานเพื่อให้ตรงกับเอาต์พุตมาตรฐาน bash กำลังเขียนข้อความติดตามไปยังข้อผิดพลาดมาตรฐานและเป็น (ตัวอย่าง) โดยใช้ built-in echoพร้อมกับเชลล์อื่น ๆ สร้างทั้งหมดในกระบวนการทุบตี

หากคุณเปลี่ยนไปเป็นอะไรที่ชอบ

TIMESEC=$(echo blah | sh -c "( /usr/bin/time -f %e grep blah >/dev/null )" 2>&1 | awk -F. '{print $1}')

มันจะแก้ไขปัญหานั้นและอาจเป็นปัญหาที่ยอมรับได้ระหว่างการติดตามและการทำงาน:

++ awk -F. '{print $1}'
++ sh -c '( /usr/bin/time -f %e grep blah >/dev/null )'
++ echo blah
+ TIMESEC=0                 
+ echo ABC--0--DEF
ABC--0--DEF
+ '[' 0 -eq 0 ']'
+ echo 'we are here!'
we are here!

7

คุณยังสามารถวาง subshell เห็นได้ชัดว่ามันคือเปลือกหอยซ้อนกันซึ่งทำให้เสียซึ่งกันและกัน:

TIMESEC=$(
    echo blah |
    /usr/bin/time -f %e grep blah 2>&1 >/dev/null |
    awk -F. '{print $1}'
)

ถ้าคุณทำ:


...| ( subshell ) 2>pipe | ...

... คุณปิดท้ายด้วย subshell ที่เปิดใช้เพื่อจัดการส่วนของไปป์ไลน์ที่โฮสต์ subshell ภายใน เนื่องจากเชลล์ไม่เปลี่ยนเส้นทางแม้แต่เอาต์พุต debug ของ subshell ภายใน(เช่นเดียวกับ{คำสั่งผสมอื่น ๆที่; } >redirectคุณอาจเลือกใช้) ไปยังส่วนของไปป์ไลน์ที่คุณปิดท้ายการผสมสตรีม มันเกี่ยวข้องกับลำดับการเปลี่ยนเส้นทาง

แต่ถ้าคุณเพียงแค่การเปลี่ยนเส้นทางแรกเพียงการส่งออกข้อผิดพลาดของคำสั่งที่คุณกำลังพยายามที่วัดและให้ผลผลิตที่ทำให้เปลือกของโฮสต์มัน stderr คุณไม่ไขลานที่มีปัญหาเดียวกัน

แล้ว ...


... | command 2>pipe 1>/dev/null | ...

... เชลล์โฮสต์มีอิสระที่จะเขียน stderr ของมันต่อไปในตำแหน่งที่มันจะพอใจในขณะที่เปลี่ยนทิศทางเอาต์พุตของคำสั่งที่เรียกใช้ไปยังไพพ์


bash -x time.sh
+++ echo blah
+++ /usr/bin/time -f %e grep blah
+++ awk -F. '{print $1}'
++ TIMESEC=0
++ echo ABC--0--DEF
ABC--0--DEF
++ '[' 0 -eq 0 ']'
++ echo 'we are here!'
we are here!

สำหรับเรื่องที่...


TIMESEC=$(
    echo blah |
    /usr/bin/time -f %e grep blah 2>&1 >/dev/null
)
printf %s\\n "ABC--${TIMESEC%%.*}--DEF"
if [ "${TIMESEC%%.*}" -eq 0 ] ; then
   echo "we are here!"
fi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.