Bagaimana saya bisa memperluas variabel yang dikutip menjadi nol jika kosong?

21

Katakanlah saya memiliki skrip melakukan:

some-command "$var1" "$var2" ...

Dan, dalam hal yang var1kosong, saya lebih suka bahwa itu diganti dengan apa pun selain string kosong, sehingga perintah yang dijalankan adalah:

some-command "$var2" ...

dan tidak:

some-command '' "$var2" ...

Apakah ada cara yang lebih sederhana daripada menguji variabel dan memasukkannya secara kondisional?

if [ -n "$1" ]; then
    some-command "$var1" "$var2" ...
    # or some variant using arrays to build the command
    # args+=("$var1")
else
    some-command "$var2" ...
fi

Apakah ada substitusi parameter daripada yang bisa diperluas ke bash, zsh, atau sejenisnya? Saya mungkin masih ingin menggunakan globbing di sisa argumen, jadi menonaktifkan itu dan menghapus tanda kutip variabel bukanlah pilihan.

muru
sumber
Saya tahu saya telah melihat dan mungkin menggunakan ini sebelumnya, tetapi ternyata sulit untuk dicari. Sekarang setelah Michael menunjukkan sintaksnya, saya ingat di mana saya pertama kali melihatnya dengan cukup cepat: unix.stackexchange.com/a/269549/70524 , unix.stackexchange.com/q/68484/70524
muru
Jika Anda tahu itu semacam substitusi parameter, mengapa Anda tidak melihat bagian ekspansi parameterman halaman? (-;
Philippos
1
@ Pilipos Saya tidak tahu apa itu pada waktu itu, hanya saja saya pernah melihat atau menggunakannya sebelumnya. Dikenal dan tidak dikenal. :(
muru
1
poin cookie tambahan untuk menyebutkan menggunakan array untuk menahan argumen dalam pertanyaan itu sendiri.
ilkkachu

Jawaban:

29

Kerang dan Bash yang sesuai dengan posix memiliki${parameter:+word} :

Jika parameter tidak disetel atau nol, null harus diganti; jika tidak, perluasan kata (atau string kosong jika kata dihilangkan) harus diganti.

Jadi Anda bisa melakukan:

${var1:+"$var1"}

dan telah var1diperiksa, dan "$var1"digunakan jika disetel dan tidak kosong (dengan aturan kutip ganda biasa). Kalau tidak, itu tidak akan berkembang. Perhatikan bahwa hanya bagian dalam yang dikutip di sini, bukan semuanya.

Hal yang sama juga berfungsi di zsh. Anda harus mengulangi variabelnya, jadi itu tidak ideal, tetapi berfungsi persis seperti yang Anda inginkan.

Jika Anda ingin variabel set-tetapi-kosong diperluas ke argumen kosong, gunakan ${var1+"$var1"}sebaliknya.

Michael Homer
sumber
1
Cukup baik untukku. Jadi, dalam hal ini, semuanya tidak dikutip, hanya wordbagiannya saja.
muru
1
Ketika pertanyaan diajukan untuk "bash, zsh, atau sejenisnya" (dan untuk arsip Tanya Jawab), saya diedit untuk mencerminkan bahwa ini adalah fitur posix. Bahkan, jika Anda menambahkan tag / bash ke pertanyaan setelah komentar saya. (-;
Philippos
2
Saya mengambil kebebasan mengedit perbedaan antara :+dan +.
ilkkachu
Terima kasih banyak untuk solusi yang sesuai dengan POSIX! Saya mencoba menggunakan sh/ dashuntuk skrip chokepoint potensial, jadi saya selalu menghargainya ketika seseorang menunjukkan bagaimana sesuatu mungkin terjadi tanpa menggunakan Bash.
JamesTheAwesomeDude
Saya sedang mencari efek sebaliknya (Perluas ke sesuatu yang lain jika dimulai sebagai nol). Ternyata logika ini dapat dibalik dengan mengubah tanda + ke - seperti pada: $ {blank_var: -lokasi}
Alex Jansen
5

Itu yang zshdilakukan secara default ketika Anda menghilangkan tanda kutip:

some-command $var1 $var2

Sebenarnya, satu-satunya alasan mengapa Anda masih membutuhkan tanda kutip di zsh di sekitar ekspansi parameter adalah untuk menghindari perilaku itu (penghapusan kosong) karena zshtidak memiliki masalah lain yang memengaruhi shell lain ketika Anda tidak mengutip ekspansi parameter (pemisahan implisit + glob) .

Anda dapat melakukan hal yang sama dengan cangkang mirip POSIX lain jika Anda menonaktifkan split dan glob:

(IFS=; set -o noglob; some-command $var1 $var2)

Sekarang, saya berpendapat bahwa jika variabel Anda dapat memiliki nilai 0 atau 1, itu harus berupa array dan bukan variabel skalar dan gunakan:

some-command "${var1[@]}" "${var2[@]}"

Dan gunakan var1=(value)kapan var1mengandung satu nilai, var1=('')kapan itu mengandung satu nilai kosong, dan var1=()kapan itu tidak mengandung nilai.

Stéphane Chazelas
sumber
0

Saya mengalami ini menggunakan rsync dalam skrip bash yang memulai perintah dengan atau tanpa -nuntuk beralih kering. Ternyata rsync dan sejumlah perintah gnu mengambil ''sebagai argumen pertama yang valid dan bertindak berbeda daripada jika tidak ada.

Butuh beberapa saat untuk debug karena parameter nol hampir tidak terlihat.

Seseorang di daftar rsync menunjukkan kepada saya cara untuk menghindari masalah ini sementara juga sangat menyederhanakan pengkodean saya. Jika saya memahaminya dengan benar, ini adalah variasi dari saran terakhir @ Stéphane Chazelas.

Bangun argumen perintah Anda di sejumlah variabel terpisah. Ini dapat diatur dalam urutan atau logika apa pun yang sesuai dengan masalah.

Kemudian, pada akhirnya, gunakan variabel untuk membangun array dengan semua yang ada di tempatnya dan gunakan itu sebagai argumen untuk perintah yang sebenarnya.

Dengan cara ini, perintah hanya dikeluarkan di satu tempat dalam kode alih-alih diulang untuk setiap variasi argumen.

Variabel apa pun yang kosong hilang begitu saja menggunakan metode ini.

Saya tahu menggunakan eval sangat disukai. Saya tidak ingat semua detailnya, tetapi saya tampaknya membutuhkannya untuk menyelesaikan pekerjaan dengan cara ini - ada hubungannya dengan menangani parameter dengan ruang putih tertanam.

Contoh:

dry_run=''
if [[ it is a test run ]]
then
  dry_run='-n'
fi
...
rsync_options=(
  ${dry_run}
  -avushi
  ${delete}
  ${excludes}
  --stats
  --progress
)
...
eval rsync "${rsync_options[@]}" ...
Joe
sumber
Itu cara yang lebih buruk untuk melakukan apa yang saya jelaskan dalam pertanyaan (membangun sejumlah argumen secara kondisional). Dengan membiarkan variabel tidak dikutip, siapa yang tahu masalah apa yang Anda biarkan terbuka bagi diri Anda.
muru
@uru Anda benar, tetapi saya masih membutuhkan sesuatu seperti ini. Saya tidak begitu mengerti bagaimana cara memperbaikinya menggunakan teknik dari jawaban lain. Akan bagus untuk melihat fragmen kode dilakukan dengan cara yang benar yang menyatukan semuanya. Saya akan bermain dengan opsi terakhir Stephane nanti karena itu mungkin melakukan apa yang saya inginkan.
Joe
Baru saja kembali ke ini. Terima kasih untuk contohnya. Masuk akal. Saya akan bekerja dengannya.
Joe
Saya memiliki kasus penggunaan serupa di sini, tetapi Anda dapat melakukan sesuatu seperti ini: if ["$ dry" == "true"]; lalu keringkan = "- n"; fi ... jadi jika variabel kering benar, Anda membuatnya menjadi -n, dan kemudian Anda membangun perintah rsync Anda seperti biasa dan memilikinya seperti ini: rsync $ {dry1: + "$ dry1"} ... jika itu nol tidak ada yang akan terjadi, jika tidak maka akan menjadi -n dalam perintah Anda
Freedo