GNU/Linux >> Belajar Linux >  >> Linux

Bagaimana Kami Menjalankan Perintah yang Disimpan Dalam Variabel?

$ ls -l /tmp/test/my dir/
total 0

Saya bertanya-tanya mengapa cara menjalankan perintah di atas berikut ini gagal atau berhasil?

$ abc='ls -l "/tmp/test/my dir"'

$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory

$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory

$ bash -c $abc
'my dir'

$ bash -c "$abc"
total 0

$ eval $abc
total 0

$ eval "$abc"
total 0

Jawaban yang Diterima:

Ini telah dibahas dalam sejumlah pertanyaan di unix.SE, saya akan mencoba mengumpulkan semua masalah yang dapat saya kemukakan di sini. Referensi di bagian akhir.

Mengapa gagal

Alasan Anda menghadapi masalah tersebut adalah pemisahan kata dan fakta bahwa kutipan yang diperluas dari variabel tidak berfungsi sebagai tanda kutip, tetapi hanya karakter biasa.

Kasus yang disajikan dalam pertanyaan:

Penugasan di sini menetapkan string tunggal ls -l "/tmp/test/my dir" ke abc :

$ abc='ls -l "/tmp/test/my dir"'

Di bawah, $abc dipisahkan pada spasi putih, dan ls dapatkan tiga argumen -l , "/tmp/test/my dan dir" (dengan kutipan di depan kutipan kedua dan kutipan lainnya di belakang kutipan ketiga). Opsi ini berfungsi, tetapi jalurnya salah diproses:

$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory

Di sini, ekspansi dikutip, jadi disimpan sebagai satu kata. Shell mencoba menemukan program yang secara harfiah disebut ls -l "/tmp/test/my dir" , spasi dan kutipan disertakan.

$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory

Dan di sini, $abc dipecah, dan hanya kata hasil pertama yang diambil sebagai argumen ke -c , jadi Bash hanya menjalankan ls di direktori saat ini. Kata lainnya adalah argumen untuk bash, dan digunakan untuk mengisi $0 , $1 , dll.

$ bash -c $abc
'my dir'

Dengan bash -c "$abc" , dan eval "$abc" , ada langkah pemrosesan shell tambahan, yang membuat tanda kutip berfungsi, tetapi juga menyebabkan semua ekspansi shell diproses lagi , jadi ada risiko tidak sengaja berjalan mis. substitusi perintah dari data yang disediakan pengguna, kecuali jika Anda sangat berhati-hati dalam mengutip.

Cara yang lebih baik untuk melakukannya

Dua cara yang lebih baik untuk menyimpan perintah adalah a) menggunakan fungsi sebagai gantinya, b) menggunakan variabel array (atau parameter posisi).

Menggunakan fungsi:

Cukup nyatakan fungsi dengan perintah di dalamnya, dan jalankan fungsi seolah-olah itu adalah perintah. Ekspansi dalam perintah dalam fungsi hanya diproses saat perintah dijalankan, bukan saat didefinisikan, dan Anda tidak perlu mengutip setiap perintah.

# define it
myls() {
    ls -l "/tmp/test/my dir"
}

# run it
myls

Menggunakan larik:

Array memungkinkan pembuatan variabel multi-kata di mana masing-masing kata berisi spasi. Di sini, kata-kata individual disimpan sebagai elemen larik yang berbeda, dan "${array[@]}" ekspansi memperluas setiap elemen sebagai kata-kata shell yang terpisah:

# define the array
mycmd=(ls -l "/tmp/test/my dir")

# run the command
"${mycmd[@]}"

Sintaksnya sedikit mengerikan, tetapi array juga memungkinkan Anda membuat baris perintah sepotong demi sepotong. Misalnya:

mycmd=(ls)               # initial command
if [ "$want_detail" = 1 ]; then
    mycmd+=(-l)          # optional flag
fi
mycmd+=("$targetdir")    # the filename

"${mycmd[@]}"

atau pertahankan bagian dari baris perintah konstan dan gunakan array, isi hanya sebagian saja, seperti opsi atau nama file:

options=(-x -v)
files=(file1 "file name with whitespace")
target=/somedir

transmutate "${options[@]}" "${files[@]}" "$target"

Kelemahan dari array adalah mereka bukan fitur standar, jadi shell POSIX biasa (seperti dash , default /bin/sh di Debian/Ubuntu) tidak mendukungnya (tetapi lihat di bawah). Bash, ksh dan zsh melakukannya, jadi kemungkinan sistem Anda memiliki beberapa shell yang mendukung array.

Terkait:perintah "eval" di bash?

Menggunakan "[email protected]"

Dalam shell tanpa dukungan untuk array bernama, seseorang masih dapat menggunakan parameter posisi (array semu "[email protected]" ) untuk menampung argumen dari suatu perintah.

Berikut ini harus berupa bit skrip portabel yang setara dengan bit kode di bagian sebelumnya. Array diganti dengan "[email protected]" , daftar parameter posisi. Menyetel "[email protected]" dilakukan dengan set , dan tanda kutip ganda di sekitar "[email protected]" penting (ini menyebabkan elemen daftar dikutip satu per satu).

Pertama, cukup simpan perintah dengan argumen di "[email protected]" dan menjalankannya:

set -- ls -l "/tmp/test/my dir"
"[email protected]"

Menyetel bagian opsi baris perintah secara kondisional untuk sebuah perintah:

set -- ls
if [ "$want_detail" = 1 ]; then
    set -- "[email protected]" -l
fi
set -- "[email protected]" "$targetdir"

"[email protected]"

Hanya menggunakan "[email protected]" untuk opsi dan operan:

set -- -x -v
set -- "[email protected]" file1 "file name with whitespace"
set -- "[email protected]" /somedir

transmutate "[email protected]"

(Tentu saja, "[email protected]" biasanya diisi dengan argumen ke skrip itu sendiri, jadi Anda harus menyimpannya di suatu tempat sebelum menggunakan kembali "[email protected]" .)

Menggunakan eval (hati-hati di sini!)

eval mengambil string dan menjalankannya sebagai perintah, sama seperti jika dimasukkan pada baris perintah shell. Ini termasuk semua proses kutipan dan perluasan, yang berguna dan berbahaya.

Dalam kasus sederhana, ini memungkinkan melakukan apa yang kita inginkan:

cmd='ls -l "/tmp/test/my dir"'
eval "$cmd"

Dengan eval , tanda kutip diproses, jadi ls akhirnya hanya melihat dua argumen -l dan /tmp/test/my dir , seperti yang kita inginkan. eval juga cukup pintar untuk menggabungkan argumen apa pun yang didapatnya, jadi eval $cmd juga dapat berfungsi dalam beberapa kasus, tetapi mis. semua spasi putih akan diubah menjadi spasi tunggal. Masih lebih baik untuk mengutip variabel di sana karena itu akan memastikannya tidak dimodifikasi menjadi eval .

Namun, berbahaya menyertakan input pengguna dalam string perintah ke eval . Misalnya, ini sepertinya berhasil:

read -r filename
cmd="ls -ld '$filename'"
eval "$cmd";

Tetapi jika pengguna memberikan masukan yang berisi tanda kutip tunggal, mereka dapat keluar dari tanda kutip dan menjalankan perintah arbitrer! Misalnya. dengan masukan '$(whatever)'.txt , skrip Anda dengan senang hati menjalankan substitusi perintah. Bahwa itu bisa saja rm -rf (atau lebih buruk) sebagai gantinya.

Masalahnya adalah nilai $filename tertanam di baris perintah yang eval berjalan. Itu diperluas sebelum eval , yang melihat mis. perintah ls -l ''$(whatever)'.txt' . Anda perlu memproses input terlebih dahulu agar aman.

Jika kita melakukannya dengan cara lain, menjaga nama file dalam variabel, dan membiarkan eval perintah perluas, lebih aman lagi:

read -r filename
cmd='ls -ld "$filename"'
eval "$cmd";

Perhatikan tanda kutip luar sekarang adalah tanda kutip tunggal, jadi ekspansi di dalam tidak terjadi. Oleh karena itu, eval melihat perintah ls -l "$filename" dan memperluas nama file itu sendiri dengan aman.

Tapi itu tidak jauh berbeda dengan hanya menyimpan perintah dalam sebuah fungsi atau array. Dengan fungsi atau array, tidak ada masalah seperti itu karena kata-kata disimpan terpisah sepanjang waktu, dan tidak ada kutipan atau pemrosesan lain untuk konten filename .

read -r filename
cmd=(ls -ld -- "$filename")
"${cmd[@]}"

Cukup banyak satu-satunya alasan untuk menggunakan eval adalah salah satu di mana bagian yang bervariasi melibatkan elemen sintaks shell yang tidak dapat dibawa melalui variabel (pipa, pengalihan, dll.). Meski begitu, pastikan untuk tidak menyematkan input dari pengguna ke eval perintah!

Terkait:Menambahkan jalur lingkungan ke prompt perintah Visual studio?

Referensi

  • Pemisahan Kata di BashGuide
  • BashFAQ/050 atau “Saya mencoba memasukkan perintah ke dalam variabel, tetapi kasus kompleks selalu gagal!”
  • Pertanyaan Mengapa skrip shell saya tersedak spasi atau karakter khusus lainnya?, yang membahas sejumlah masalah terkait dengan kutipan dan spasi, termasuk menyimpan perintah.

Linux
  1. Bagaimana Cara Menetapkan Output Perintah Ke Variabel Shell?

  2. Bagaimana cara menjalankan perintah Vim dari shell?

  3. Bagaimana saya bisa menjalankan perintah setelah boot?

  1. Cara Menetapkan Output dari Perintah Linux ke Variabel

  2. Bagaimana Menjalankan Perintah Sebagai Administrator Sistem (root)?

  3. Bagaimana cara menjalankan perintah teratas dengan benar melalui SSH?

  1. Bagaimana Cara Menjalankan Perintah Di Dalam Wadah Systemd yang Berjalan?

  2. Bagaimana Cara Menjalankan Perintah Tanpa Properti Root?

  3. Cara Menjalankan Perintah di Running Docker Container