Bagaimana saya bisa lulus subkulit melalui 'waktu' bersyarat?

9

Saya memiliki skrip pengaturan untuk kotak Vagrant tempat saya biasa mengukur langkah tunggal time. Sekarang saya ingin mengaktifkan atau menonaktifkan pengukuran waktu secara kondisional.

Misalnya, sebelumnya sebuah garis akan terlihat seperti:

time (apt-get update > /tmp/last.log 2>&1)

Sekarang saya pikir saya bisa melakukan sesuatu seperti ini:

MEASURE_TIME=true
[[ $MEASURE_TIME = true ]] && TIME="time --format=%e" || TIME=""

$TIME (apt-get update > /tmp/last.log 2>&1)

Tetapi ini tidak akan berhasil:

syntax error near unexpected token `apt-get'
`$TIME (apt-get update > /tmp/last.log 2>&1)'

Apa masalahnya di sini?

Der Hochstapler
sumber
3
Anda hanya perlu mencapai kecepatan target agar kapasitor fluks berfungsi;)
goldilocks
2
@goldilocks Maksudmu magnet ? ;)
CVn

Jawaban:

13

Untuk dapat menghitung waktu subkulit, Anda perlu time kata kunci , bukan perintah.

Kata timekunci, bagian dari bahasa, hanya dikenali seperti itu ketika dimasukkan secara harfiah dan sebagai kata pertama dari suatu perintah (dan dalam kasus ksh93, maka token berikutnya tidak dimulai dengan a -). Bahkan masuk "time"tidak akan berhasil apalagi $TIME(dan akan dianggap sebagai panggilan untuk timeperintah).

Anda dapat menggunakan alias di sini yang diperluas sebelum putaran parsing dilakukan (jadi biarkan shell mengenali timekata kunci itu):

shopt -s expand_aliases
alias time_or_not=
TIMEFORMAT=%E

MEASURE_TIME=true
[[ $MEASURE_TIME = true ]] && alias time_or_not=time

time_or_not (apt-get update > /tmp/last.log 2>&1)

Kata time kunci tidak mengambil opsi (kecuali untuk -pdalam bash), tetapi format dapat diatur dengan TIMEFORMATvariabel dalam bash. ( shoptbagian ini juga bashkhusus, shell lain umumnya tidak membutuhkan itu).

Stéphane Chazelas
sumber
Anda berkata "Untuk dapat menghitung waktu subkulit, Anda memerlukan kata kunci waktu". Saya ingin tahu perilaku ini didokumentasikan di mana?
cuonglm
@cuonglm, lihatinfo -f bash --index-search=time
Stéphane Chazelas
3

Sementara an aliasadalah salah satu cara untuk melakukannya, ini dapat dilakukan dengan evaljuga - hanya saja Anda tidak begitu ingin evaleksekusi perintah seperti yang Anda inginkan evalpada deklarasi perintah .

Saya suka aliases - saya menggunakan mereka sepanjang waktu, tapi saya suka fungsi lebih baik - terutama kemampuan mereka untuk menangani parameter dan bahwa mereka tidak perlu diperluas dalam posisi perintah seperti yang diperlukan untuk aliases.

Jadi saya pikir mungkin Anda ingin mencoba ini juga:

_time() if   set -- "${IFS+IFS=\$2;}" "$IFS" "$@" && IFS='
';      then set -- "$1" "$2" "$*"; unset IFS
             eval "$1 $TIME ${3#"$1"?"$2"?}"
        fi

The $IFSbit terutama tentang $*. Sangat penting bahwa ( subshell bit )ini juga merupakan hasil dari ekspansi shell dan jadi untuk memperluas argumen menjadi string yang dapat diuraikan yang saya gunakan "$*" (jangan eval "$@", omong-omong, kecuali jika Anda yakin semua argumen dapat digabungkan pada spasi) . Pemisah pemisah antara args in "$*"adalah byte pertama $IFS, dan karenanya bisa berbahaya untuk melanjutkan tanpa memastikan nilainya. Jadi fungsinya menyimpan $IFS, menetapkannya ke \nbaris baru cukup lama set ... "$*"menjadi "$3", unsets itu, lalu mengatur ulang nilainya jika sebelumnya memiliki satu.

Ini sedikit demo:

TIME='set -x; time'
_time \( 'echo "$(echo any number of subshells)"' \
         'command -V time'                        \
         'hash time'                              \
      \) 'set +x'

Anda lihat saya meletakkan dua perintah di nilai $TIMEsana - nomor berapa pun baik - bahkan tidak ada - tapi pastikan itu lolos dan dikutip dengan benar - dan hal yang sama berlaku untuk argumen _time(). Mereka semua akan digabungkan menjadi string perintah tunggal ketika dieksekusi - tetapi masing-masing arg mendapatkan \newline sendiri dan sehingga mereka dapat disebarkan dengan relatif mudah. Atau Anda bisa mengelompokkannya menjadi satu, jika mau, dan memisahkannya di \newline atau semi-titik dua atau apa pun yang Anda miliki. Pastikan saja satu argumen mewakili perintah yang akan membuat Anda merasa nyaman untuk meletakkan barisnya sendiri dalam sebuah skrip ketika Anda menyebutnya. \(, misalnya baik-baik saja, asalkan pada akhirnya diikuti dengan \). Pada dasarnya hal-hal normal.

Ketika evalmendapat potongan di atas, sepertinya:

+ eval 'IFS=$2;set -x; time (
echo "$(echo any number of subshells)"
command -V time
hash time
)
set +x'

Dan, hasilnya terlihat seperti ...

KELUARAN

+++ echo any number of subshells
++ echo 'any number of subshells'
any number of subshells
++ command -V time
time is a shell keyword
++ hash time
bash: hash: time: not found

real    0m0.003s
user    0m0.000s
sys     0m0.000s
++ set +x

The hashkesalahan menunjukkan bahwa saya tidak memiliki /usr/bin/timediinstal (karena saya tidak) dan commandmari kita tahu mana waktu berjalan. Trailing set +xadalah perintah lain yang dieksekusi setelah time (yang mungkin) - penting untuk berhati-hati dengan perintah input saat memasukkan evalapa pun.

mikeserv
sumber
Ada alasan untuk tidak membuatnya _time() { eval "$TIME $@"; }? Menggunakan fungsi memiliki kelemahan dalam memperkenalkan ruang lingkup yang berbeda untuk variabel (bukan masalah untuk subkulit)
Stéphane Chazelas
@ StéphaneChazelas - karena tanda kutip dalam "$@"ekspansi. Saya suka satu arg untuk eval- itu menakutkan kalau tidak. Ummm .... bagaimana maksudmu tentang ruang lingkup yang berbeda? Saya pikir itu hanya functionberfungsi ...
mikeserv
evalbergabung dengan argumennya sebelum mengeksekusi yang merupakan apa yang Anda coba lakukan dengan cara yang sangat berbelit-belit. Aku berarti bahwa _time 'local var=1; blah'akan membuat varlokal untuk itu _time, atau bahwa _time 'echo "$#"'akan mencetak $#itu _timefungsi, tidak pemanggil.
Stéphane Chazelas
@ StéphaneChazelas - Saya punya dua tab yang lengkap untuk Anda di sini. Benar - evalmenyuarakan argumennya pada spasi - yang, seperti yang Anda katakan, persis apa yang saya lakukan di sini - meskipun itu bukan niat awal. Saya akan memperbaikinya. evalYang "$*"bertentangan "$@"adalah kebiasaan yang saya ambil setelah banyak command not foundpertengkaran dengan ketika args bergabung di tempat yang salah. Bagaimanapun, meskipun argumen akhirnya dieksekusi dalam fungsi, itu cukup sederhana untuk memperluas mereka pada doa, saya pikir. Lagipula itu yang akan saya lakukan "$#".
mikeserv
+1 untuk potongan
hackery yang bagus