Mengapa "bash -x" memecah script ini?


13

Saya memiliki skrip yang mengukur berapa lama beberapa perintah dijalankan.

Dibutuhkan perintah "real" time, artinya, biner misalnya dalam /usr/bin/time(karena bash-built-in tidak memiliki -fflag).

Di bawah ini, skrip yang disederhanakan yang dapat di-debug:

#!/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

Simpan sebagai "test.sh" dan jalankan:

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

Jadi itu berhasil.

Sekarang, mari kita coba debug ini dengan menambahkan "-x" ke 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

Mengapa skrip ini rusak ketika kita menggunakan "-x", dan berfungsi dengan baik tanpa itu?


1
Heh. Sepertinya dengan -xpada, bahwa $()konstruksinya mendapatkan -xoutput termasuk sebagai bagian dari nilai yang dihasilkan. Tidak tahu apakah itu perilaku "yang diharapkan" atau bug ... Atau mungkin itu adalah subkulit ()di dalam yang sebenarnya memberikan -xoutput.
Jeff Y

Selain itu: Pengaturan BASH_XTRACEFDmemungkinkan Anda mengarahkan set -xoutput ke suatu tempat yang lebih mudah.
Charles Duffy

Jawaban:


21

Masalahnya adalah baris ini:

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

di mana Anda mengarahkan kesalahan standar agar sesuai dengan output standar. bash sedang menulis pesan jejaknya ke kesalahan standar, dan (misalnya) menggunakan built-in echobersama dengan shell konstruksi lainnya semua dalam proses bash.

Jika Anda mengubahnya menjadi sesuatu seperti

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

itu akan mengatasi masalah itu, dan mungkin kompromi yang dapat diterima antara melacak dan bekerja:

++ 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

Anda juga bisa langsung melepas subshell. rupanya itu adalah cangkang bersarang yang akhirnya mengecewakan satu sama lain:

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

Jika kamu melakukan:


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

... Anda berakhir dengan subkulit yang diluncurkan untuk menangani bagian pipa yang menampung subkulit di dalamnya. Karena shell tanpa arahan ulang bahkan hasil debug dari subkulit di dalam (seperti yang juga akan dilakukan untuk semua jenis {perintah majemuk ; } >redirectyang mungkin Anda pilih untuk digunakan) ke bagian pipeline Anda berakhir mencampur aliran. Itu ada hubungannya dengan urutan pengalihan.

Sebaliknya, jika Anda hanya mengarahkan ulang hanya output kesalahan dari perintah yang Anda coba ukur, dan biarkan output shell host membuatnya menjadi stderr, Anda tidak berakhir dengan masalah yang sama.

dan sebagainya...


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

... shell host bebas untuk terus menulis stderrnya di tempat yang diinginkannya sementara hanya mengarahkan output dari perintah yang dipanggilnya ke dalam pipa.


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!

Untuk hal tersebut...


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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.