Bisakah skrip shell mencetak argumennya, yang dikutip seperti yang Anda tulis di shell prompt?

9

Dalam skrip shell, pemahaman saya adalah yang "$@"memperluas argumen skrip, mengutipnya sesuai kebutuhan. Misalnya ini meneruskan argumen skrip ke gcc:

gcc -fPIC "$@"

Saat menggunakan sintaks bash pass-to-stdin <<<, "@$"tidak berfungsi seperti yang saya harapkan.

#!/bin/bash
cat <<< "$@"

Memanggil script as ./test.sh foo "bar baz"give

foo bar baz

Saya harapkan

foo "bar baz"

Apakah ada cara untuk menulis skrip shell yang mencetak argumennya seperti yang Anda tulis pada prompt shell? Misalnya: petunjuk tentang perintah apa yang akan digunakan berikutnya, termasuk argumen skrip dalam petunjuk tersebut.

Alex Jasmin
sumber

Jawaban:

5

Nah, "$@"perluas daftar parameter posisi, satu argumen per parameter posisi.

Saat kamu melakukan:

set '' 'foo bar' $'blah\nblah'
cmd "$@"

cmdsedang dipanggil dengan 3 argumen: string kosong, foo bardan blah<newline>blah. Shell akan memanggil execve()system call dengan sesuatu seperti:

execve("/path/to/cmd", ["cmd", "", "foo bar", "blah\nblah"], [envvars...]);

Jika Anda ingin merekonstruksi baris perintah shell (yaitu kode dalam bahasa shell) yang akan mereproduksi permintaan yang sama, Anda bisa melakukan sesuatu seperti:

awk -v q="'" '
  function shellquote(s) {
    gsub(q, q "\\" q q, s)
    return q s q
  }
  BEGIN {
    for (i = 1; i < ARGC; i++) {
      printf "%s", sep shellquote(ARGV[i])
      sep = " "
    }
    printf "\n"
  }' cmd "$@"

Atau dengan zsh, menanyakan berbagai jenis kutipan:

set '' 'foo bar' $'blah\nblah'
$ print -r -- cmd "${(q)@}"
cmd '' foo\ bar blah$'\n'blah
$ print -r -- cmd "${(qq)@}"
cmd '' 'foo bar' 'blah
blah'
$ print -r -- cmd "${(qqq)@}"
cmd "" "foo bar" "blah
blah"
$ print -r -- cmd "${(qqqq)@}"
cmd $'' $'foo bar' $'blah\nblah'

Atau dengan zsh, bashatau ksh93(di sini untuk bash, YMMV dengan cangkang lain):

$ set '' 'foo bar' $'blah\nblah'
$ printf cmd; printf ' %q' "$@"; printf '\n'
cmd '' foo\ bar $'blah\nblah'

Anda juga bisa menggunakan opsi xtrace shell yang menyebabkan shell untuk mencetak apa yang akan dieksekusi:

(PS4=; set -x; : cmd "$@")
: cmd '' 'foo bar' 'blah
blah'

Di atas, kami menjalankan perintah :no-op dengan cmddan parameter posisi sebagai argumen. Cangkang saya mencetaknya dengan cara yang bagus dan cocok untuk dimasukkan kembali ke dalam cangkang. Tidak semua kerang melakukan itu.

Stéphane Chazelas
sumber
5
`"$@"` expands to the script arguments, quoting them as needed

Tidak, ini bukan yang terjadi. Memanggil suatu program membutuhkan daftar argumen, setiap argumen menjadi string. Ketika Anda menjalankan program shell ./test.sh foo "bar baz", ini membangun panggilan dengan tiga argumen: ./test.sh, foo, dan bar baz. (Argumen nol adalah nama program; ini memungkinkan program untuk mengetahui dengan nama apa mereka dipanggil.) Mengutip adalah fitur dari shell, bukan fitur panggilan program. Shell membangun daftar ini ketika membuat panggilan.

"$@"secara langsung menyalin daftar argumen yang diteruskan ke skrip atau fungsi ke daftar argumen dalam panggilan di mana ia digunakan. Tidak ada kutipan yang terlibat karena tidak ada penguraian shell yang dilakukan pada daftar tersebut.

Di cat <<< "$@", Anda menggunakan "$@"dalam konteks di mana satu string diperlukan. The <<<operator` membutuhkan string, bukan daftar string. Dalam konteks ini, bash mengambil elemen daftar dan menggabungkannya dengan spasi di antaranya.

Untuk debugging skrip, jika Anda menjalankan set -x( set +xuntuk mematikan), itu mengaktifkan mode jejak di mana setiap perintah dicetak sebelum dieksekusi. Dalam bash, jejak itu memiliki tanda kutip yang memungkinkan untuk menempelkan perintah kembali ke shell (ini tidak berlaku untuk setiap shimplementasi).

Jika Anda memiliki string dan ingin mengubahnya menjadi sintaksis sumber shell yang mem-parsing kembali ke string asli, Anda dapat mengelilinginya dengan tanda kutip tunggal, dan mengganti setiap kutipan di dalam string dengan '\''.

for x do
  printf %s "'${x//\'/\'\\\'\'}' "
done
echo

Sintaks penggantian string khusus ksh93 / bash / zsh / mksh. Dalam sh biasa, Anda harus mengulangi string.

for raw do
  quoted=
  while case "$raw" in *\'*) true;; *) false;; esac; do
    quoted="$quoted'\\''${raw%%\'*}"
    raw="${raw#*\'}"
  done
  printf %s "'$quoted$raw' "
done
echo
Gilles 'SANGAT berhenti menjadi jahat'
sumber
2

"$@" memperluas argumen skrip, mengutipnya sesuai kebutuhan

Yah, semacam itu. Untuk tujuan praktis yang harus cukup dekat, dan manual referensi memang mengatakan itu"$@" is equivalent to "$1" "$2" ...

Jadi, dengan dua parameter foodan bar baz, ini akan sama:

echo "$@"
echo "$1" "$2"
echo "foo" "bar baz"

(Kecuali jika parameter berisi karakter khusus alih-alih hanya string biasa, mereka tidak akan diperluas lagi setelah berkembang $@dan $1...)

Tetapi bahkan jika kita mempertimbangkan untuk $@mengganti dengan parameter dalam tanda kutip, tanda kutip tidak akan ada untuk echodilihat, sama seperti yang gcctidak mendapatkan tanda kutip juga.

<<<adalah sedikit pengecualian untuk aturan "$@"== "$1" "$2" ..., itu secara eksplisit disebutkan bahwa The result is supplied as a single string to the command on its standard inputsetelah melalui parameter dan variabel ekspansi dan penghapusan kutipan antara lain. Jadi seperti biasa, <<< "foo"beri saja foosebagai masukan, dengan cara yang sama somecmd "foo"hanya memberi foosebagai argumen.

Memanggil script sebagai ./test.sh foo "bar baz"[...] Saya harapkan foo "bar baz"

Jika tanda kutip tetap, itu masih harus "foo" "bar baz". Shell atau perintah yang sedang berjalan tidak tahu apa yang mengutip ketika perintah itu dijalankan. Atau jika ada kutipan untuk dibicarakan, pemanggilan sistem hanya menerima daftar string yang diakhiri dengan nol dan kutipan hanyalah fitur dari bahasa shell. Bahasa lain mungkin memiliki konvensi lain.

ilkkachu
sumber
0

Solusi alternatif untuk bash

q='"'; t=( "${@/#/$q}" ); u=( "${t[@]/%/$q}" ); echo ${u[@]}

Bash tidak mendukung substitusi bersarang, jadi terima kasih kepada /programming/12303974/assign-array-to-variable#12304017 untuk menunjukkan cara menetapkan ulang array. Lihat man bash( https://linux.die.net/man/1/bash ) untuk detail tentang array, ekspansi, dan substitusi pola (di bawah parameter ekspansi).

Analisis

Bash menempatkan parameter baris perintah sebagai array $@

q memegang karakter kutipan.

Kutipan ganda di sekitar ekspansi parameter ${ ... }mempertahankan parameter individual sebagai elemen yang berbeda dan membungkusnya ( )memungkinkan Anda untuk menetapkannya sebagai array ke suatu variabel.

/#/$qdalam ekspansi parameter menggantikan awal pola (seperti regex ^) dengan tanda kutip.

/%/$qdalam ekspansi parameter menggantikan akhir pola (seperti regex $) dengan tanda kutip.

Use case: meminta MySQL untuk daftar alamat email dari baris perintah

Ada beberapa perubahan pada pernyataan di atas untuk menggunakan tanda kutip yang berbeda, tambahkan koma di antara parameter, dan hapus koma terakhir. Dan tentu saja saya bersikap buruk dengan memasukkan kata sandi dalam doa mysql. Jadi tuntut aku.

q="'"; t=( "${@/#/$q}" ); u="${t[@]/%/$q,}"; v="u.email in( ${u%,} )"
mysql -uprod_program -h10.90.2.11 -pxxxxxxxxxxxx my_database <<END
select ...
from users u
join ....
where $v # <<<<<<<<<<<<<<<<<< here is where all the hard work pays off :-)
group by user_id, prog_id
;
END
Jeff
sumber
Hanya perhatikan bahwa mengutip dengan tanda kutip ganda tidak sangat membantu dalam melindungi garis miring terbalik, $-perbaikan dan tanda kutip ganda lainnya. Itu sebabnya jawaban lain menggunakan tanda kutip tunggal sambil berusaha keras untuk menangani tanda kutip tunggal di dalam string, atau menggunakan fitur shell sendiri untuk menghasilkan salinan string yang dikutip.
ilkkachu
@ilkkachu Telah dicatat! Itu sebabnya (di antara alasan lain) saya membatalkan semua jawaban sebelumnya. Juga mengapa saya menambahkan use case ini. Semoga yang sempurna bukanlah musuh yang baik.
Jeff