Apakah aman untuk eval $ BASH_COMMAND?

11

Saya sedang mengerjakan skrip shell yang membangun perintah kompleks dari variabel, misalnya seperti ini (dengan teknik yang saya pelajari dari Bash FAQ ):

#!/bin/bash

SOME_ARG="abc"
ANOTHER_ARG="def"

some_complex_command \
  ${SOME_ARG:+--do-something "$SOME_ARG"} \
  ${ANOTHER_ARG:+--with "$ANOTHER_ARG"}

Script ini secara dinamis menambahkan parameter --do-something "$SOME_ARG"dan --with "$ANOTHER_ARG"untuk some_complex_commandjika variabel ini didefinisikan. Sejauh ini ini berfungsi dengan baik.

Tetapi sekarang saya juga ingin dapat mencetak atau mencatat perintah ketika saya menjalankannya, misalnya ketika skrip saya dijalankan dalam mode debug. Jadi ketika skrip saya berjalan some_complex_command --do-something abc --with def, saya juga ingin memiliki perintah ini di dalam suatu variabel sehingga saya dapat misalnya log ke syslog.

Bash FAQ menunjukkan teknik untuk menggunakan DEBUGjebakan dan $BASH_COMMANDvariabel (misalnya untuk tujuan debugging) untuk tujuan ini. Saya sudah mencobanya dengan kode berikut:

#!/bin/bash

ARG="test string"

trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG"

echo "Command was: ${COMMAND}"

Ini berfungsi, tetapi itu tidak memperluas variabel dalam perintah:

host ~ # ./test.sh
test string
Command was: echo "$ARG"

Saya kira saya harus menggunakan eval untuk memperluas echo "$ARG"ke echo test string(setidaknya saya belum menemukan cara tanpa evalbelum). Berikut ini berfungsi:

eval echo "Command was: ${COMMAND}"

Ini menghasilkan output berikut:

host ~ # ./test.sh
test string
Command was: echo "$ARG"
Command was: echo test string

Tapi saya tidak begitu yakin apakah saya bisa menggunakannya evaldengan aman seperti ini. Saya gagal mencoba mengeksploitasi beberapa hal:

#!/bin/bash

ARG="test string; touch /x"
DANGER='$(touch /y; cat /etc/shadow)'

trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG" $DANGER

echo "Command was: ${COMMAND}"
eval echo "Command was: ${COMMAND}"

Tampaknya untuk menangani ini dengan baik, tetapi saya ingin tahu apakah ada orang lain melihat masalah yang saya lewatkan.

Martin von Wittich
sumber
Ini adalah apa yang saya gunakan selama bertahun-tahun dalam bungkus sudo, saya tidak pernah melihat ada masalah.
w00t

Jawaban:

7

Salah satu kemungkinan adalah membuat fungsi pembungkus yang pada saat yang sama akan mencetak perintah dan menjalankannya, sebagai berikut:

debug() {
    # This function prints (to stdout) its arguments and executes them
    local args=() idx=0 IFS=' ' c
    for c; do printf -v args[idx++] '%q' "$c"; done
    printf "%s\n" "${args[*]}"
    # Execute!
    "$@"
}

Sehingga dalam skrip Anda dapat Anda lakukan:

debug echo "$ARG"

Tidak perlu mengutak-atik perangkap. Kekurangannya adalah ia menambahkan beberapa debugkata kunci di seluruh kode Anda (tapi itu harus baik-baik saja, itu umum untuk memiliki hal-hal seperti itu, seperti menegaskan, dll).

Anda bahkan dapat menambahkan variabel global DEBUGdan memodifikasi debugfungsi seperti:

debug() {
    # This function prints (to stdout) its arguments if DEBUG is non-null
    # and executes them
    if [[ $DEBUG ]]; then
        local args=() idx=0 IFS=' ' c
        for c; do printf -v args[idx++] '%q' "$c"; done
        printf "%s\n" "${args[*]}"
    fi
    # Execute!
    "$@"
}

Kemudian Anda dapat memanggil skrip Anda sebagai:

$ DEBUG=yes ./myscript

atau

$ DEBUG= ./myscript

atau hanya

$ ./myscript

tergantung apakah Anda ingin memiliki informasi debug atau tidak.

Saya mengkapitalkan DEBUGvariabel karena harus diperlakukan sebagai variabel lingkungan. DEBUGadalah nama yang sepele dan umum, jadi ini mungkin berbenturan dengan perintah lain. Mungkin menyebutnya GNIOURF_DEBUGatau MARTIN_VON_WITTICH_DEBUGatau UNICORN_DEBUGjika Anda suka unicorn (dan kemudian Anda mungkin suka kuda juga).

Catatan. Dalam debugfungsi ini, saya dengan hati-hati memformat setiap argumen dengan printf '%q'sehingga output akan keluar dengan benar dan dikutip sehingga dapat digunakan kembali kata demi kata dengan copy dan paste langsung. Ini juga akan menunjukkan dengan tepat apa yang dilihat shell karena Anda akan dapat mengetahui setiap argumen (jika spasi atau simbol lucu lainnya). Fungsi ini juga menggunakan penugasan langsung dengan -vsaklar printfuntuk menghindari subkulit yang tidak perlu.

gniourf_gniourf
sumber
1
Fungsi ini berfungsi dengan baik, tetapi sayangnya perintah saya berisi pengalihan dan operator kontrol "excute in background" &. Saya harus memindahkannya ke fungsi agar berfungsi - tidak terlalu bagus, tapi saya rasa tidak ada cara yang lebih baik. Tapi tidak lebih eval, jadi saya punya itu untuk saya, yang bagus :)
Martin von Wittich
5

eval "$BASH_COMMAND" menjalankan perintah.

printf '%s\n' "$BASH_COMMAND" mencetak perintah yang ditentukan dengan tepat, ditambah baris baru.

Jika perintah berisi variabel (yaitu jika itu seperti cat "$foo"), maka mencetak perintah akan mencetak teks variabel. Tidak mungkin untuk mencetak nilai variabel tanpa menjalankan perintah - pikirkan perintah seperti variable=$(some_function) other_variable=$variable.

Cara paling sederhana untuk mendapatkan jejak dari menjalankan skrip shell adalah dengan mengatur xtraceopsi shell dengan menjalankan skrip sebagai bash -x /path/to/scriptatau memanggil set -xdi dalam shell. Jejak dicetak ke kesalahan standar.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
1
Saya tahu xtrace, tapi itu tidak memberi saya banyak kendali. Saya mencoba "set -x; ...; set + x", tetapi: 1) perintah "set + x" untuk menonaktifkan xtrace dicetak juga 2) Saya tidak dapat mengawali output misalnya dengan stempel waktu 3) Saya bisa dapat login ke syslog.
Martin von Wittich
2
"Tidak mungkin untuk mencetak nilai variabel tanpa menjalankan perintah" - contoh yang baik, saya belum mempertimbangkan kasus seperti itu. Akan lebih baik jika bash memiliki variabel tambahan di sebelahnya BASH_COMMANDyang berisi perintah yang diperluas, karena pada titik tertentu tetap harus melakukan ekspansi variabel pada perintah ketika menjalankannya :)
Martin von Wittich