Bagaimana saya bisa menghasilkan argumen ke perintah lain melalui substitusi perintah

11

Berikut dari dari: perilaku tak terduga dalam substitusi perintah shell

Saya memiliki perintah yang dapat mengambil daftar besar argumen, beberapa di antaranya dapat berisi ruang secara sah (dan mungkin hal-hal lain)

Saya menulis sebuah skrip yang dapat menghasilkan argumen untuk saya, dengan tanda kutip, tetapi saya harus menyalin dan menempelkan hasilnya misalnya

./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>

Saya mencoba merampingkan ini hanya dengan melakukan

./othercommand $(./somecommand)

dan bertemu dengan perilaku tak terduga yang disebutkan dalam pertanyaan di atas. Pertanyaannya adalah - dapatkah substitusi perintah secara andal digunakan untuk menghasilkan argumen untuk othercommanddiberikan bahwa beberapa argumen memerlukan penawaran dan ini tidak dapat diubah?

pengguna1207217
sumber
Tergantung pada apa yang Anda maksud dengan "andal". Jika Anda ingin perintah berikutnya mengambil output persis seperti yang muncul di layar dan menerapkan aturan shell untuk itu, maka mungkin evaldapat digunakan, tetapi umumnya tidak disarankan. xargsadalah sesuatu yang perlu dipertimbangkan juga
Sergiy Kolodyazhnyy
Saya ingin (mengharapkan) keluaran dari somecommanduntuk menjalani parsing shell biasa
user1207217
Seperti yang saya katakan dalam jawaban saya, gunakan beberapa karakter lain untuk pemisahan bidang (seperti :) ... dengan asumsi karakter yang andal tidak akan ada dalam output.
Olorin
Tapi itu tidak
sepenuhnya
2
Bisakah Anda memposting contoh dunia nyata? Maksud saya, hasil aktual dari perintah pertama dan bagaimana Anda ingin berinteraksi dengan perintah kedua.
nxnev

Jawaban:

10

Saya menulis skrip yang dapat menghasilkan argumen untuk saya, dengan kutipan

Jika output dikutip dengan benar untuk shell, dan Anda mempercayai output , maka Anda bisa menjalankannya eval.

Dengan anggapan Anda memiliki shell yang mendukung array, sebaiknya gunakan satu untuk menyimpan argumen yang Anda dapatkan.

Jika ./gen_args.shmenghasilkan keluaran seperti 'foo bar' '*' asdf, maka kita bisa menjalankan eval "args=( $(./gen_args.sh) )"untuk mengisi array yang disebut argsdengan hasilnya. Itu akan menjadi tiga unsur foo bar, *, asdf.

Kita dapat menggunakan "${args[@]}"seperti biasa untuk memperluas elemen array secara individual:

$ eval "args=( $(./gen_args.sh) )"
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

(Perhatikan tanda kutip. "${array[@]}"Perluas ke semua elemen sebagai argumen berbeda yang tidak dimodifikasi. Tanpa tanda kutip, elemen array dapat mengalami pemisahan kata. Lihat misalnya halaman Array di BashGuide .)

Namun , evaldengan senang hati akan menjalankan penggantian shell, jadi $HOMEdi output akan diperluas ke direktori home Anda, dan substitusi perintah akan benar-benar menjalankan perintah di shell yang sedang berjalan eval. Output dari "$(date >&2)"akan membuat elemen array kosong tunggal dan mencetak tanggal saat ini di stdout. Ini menjadi masalah jika gen_args.shmendapat data dari beberapa sumber yang tidak dipercaya, seperti host lain melalui jaringan, nama file yang dibuat oleh pengguna lain. Outputnya bisa termasuk perintah sewenang-wenang. (Jika get_args.shitu sendiri berbahaya, tidak perlu mengeluarkan apa pun, itu bisa langsung menjalankan perintah jahat.)


Alternatif untuk mengutip shell, yang sulit diurai tanpa eval, adalah menggunakan beberapa karakter lain sebagai pemisah dalam output skrip Anda. Anda harus memilih satu yang tidak diperlukan dalam argumen yang sebenarnya.

Mari kita pilih #, dan hasilkan skripnya foo bar#*#asdf. Sekarang kita dapat menggunakan ekspansi perintah yang tidak dikutip untuk membagi output dari perintah ke argumen.

$ IFS='#'                          # split on '#' signs
$ set -f                           # disable globbing
$ args=( $( ./gen_args3.sh ) )     # assign the values to the arrayfor var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

Anda harus mengatur IFSkembali nanti jika Anda bergantung pada pemisahan kata di tempat lain dalam skrip ( unset IFSharus berfungsi untuk menjadikannya default), dan juga gunakan set +fjika Anda ingin menggunakan globbing nanti.

Jika Anda tidak menggunakan Bash atau shell lain yang memiliki array, Anda bisa menggunakan parameter posisi untuk itu. Ganti args=( $(...) )dengan set -- $(./gen_args.sh)dan gunakan "$@"sebagai gantinya "${args[@]}". (Di sini, Anda juga perlu mengutip "$@", jika tidak, parameter posisi tunduk pada pemisahan kata.)

ilkkachu
sumber
Terbaik dari kedua dunia!
Olorin
Apakah Anda menambahkan komentar yang menunjukkan pentingnya mengutip ${args[@]}- ini tidak bekerja untuk saya sebaliknya
user1207217
@ user1207217, ya, Anda benar. Ini hal yang sama dengan array dan "${array[@]}"juga dengan "$@". Keduanya perlu dikutip, atau pemisahan kata memecah elemen array menjadi beberapa bagian.
ilkkachu
6

Masalahnya adalah bahwa begitu somecommandskrip Anda menampilkan opsi untuk othercommand, opsinya benar-benar hanya teks dan pada belas kasihan standar parsing shell (dipengaruhi oleh apa pun yang $IFSterjadi dan apa opsi shell berlaku, yang Anda dalam kasus umum tidak akan mengendalikan).

Alih-alih menggunakan somecommanduntuk menampilkan opsi, itu akan lebih mudah, lebih aman, dan lebih kuat untuk menggunakannya untuk menelepon othercommand . The somecommandScript kemudian akan menjadi skrip wrapper sekitar othercommandbukannya semacam script pembantu yang Anda harus ingat untuk memanggil dalam beberapa cara khusus sebagai bagian dari baris perintah dari otherscript. Skrip wrapper adalah cara yang sangat umum untuk menyediakan alat yang hanya memanggil beberapa alat serupa lainnya dengan serangkaian opsi lain (cukup periksa dengan fileperintah apa /usr/binyang sebenarnya adalah pembungkus skrip shell).

Di bash, kshatau zsh, Anda bisa dengan mudah skrip pembungkus yang menggunakan array untuk menampung opsi individual othercommandseperti:

options=( "hi there" "nice weather" "here's a star" "*" )
options+=( "bonus bumblebee!" )  # add additional option

Kemudian panggil othercommand(masih dalam skrip pembungkus):

othercommand "${options[@]}"

Perluasan "${options[@]}"akan memastikan bahwa setiap elemen optionsarray dikutip secara individual dan disajikan othercommandsebagai argumen terpisah.

Pengguna wrapper tidak menyadari fakta bahwa itu benar-benar memanggil othercommand, sesuatu yang tidak akan benar jika skrip malah hanya menghasilkan opsi baris perintah othercommandsebagai output.

Di /bin/sh, gunakan $@untuk menahan opsi:

set -- "hi there" "nice weather" "here's a star" "*"
set -- "$@" "bonus bumblebee!"  # add additional option

othercommand "$@"

( setAdalah perintah yang digunakan untuk pengaturan parameter posisi $1, $2, $3dll Ini adalah apa yang membuat array $@dalam standar POSIX shell. Awal --adalah sinyal untuk setbahwa tidak ada opsi yang diberikan, hanya argumen. The --adalah benar-benar hanya diperlukan jika Nilai pertama kebetulan sesuatu yang dimulai dengan -).

Perhatikan bahwa ini adalah tanda kutip ganda di sekitar $@dan ${options[@]}yang memastikan bahwa elemen-elemen tersebut tidak terpisah kata-kata (dan nama file di-globbed).

Kusalananda
sumber
bisakah kamu menjelaskan set --?
user1207217
@ user1207217 Menambahkan penjelasan untuk dijawab.
Kusalananda
4

Jika somecommandoutput dalam sintaks shell yang andal bagus, Anda dapat menggunakan eval:

$ eval sh test.sh $(echo '"hello " "hi and bye"')
hello 
hi and bye

Tetapi Anda harus yakin bahwa output memiliki kutipan yang valid dan semacamnya, jika tidak, Anda mungkin juga menjalankan perintah di luar skrip:

$ cat test.sh 
for var in "$@"
do
    echo "|$var|"
done
$ ls
bar  baz  test.sh
$ eval sh test.sh $(echo '"hello " "hi and bye"; echo rm *')
|hello |
|hi and bye|
rm bar baz test.sh

Catatan yang echo rm bar baz test.shtidak diteruskan ke skrip (karena ;) dan dijalankan sebagai perintah terpisah. Saya menambahkan |sekitar $varuntuk menyoroti ini.


Secara umum, kecuali Anda benar-benar dapat mempercayai output somecommand, tidak mungkin untuk menggunakan outputnya untuk membangun string perintah secara andal.

Olorin
sumber