Jalankan perintah yang disimpan dalam variabel

9

Saya memiliki perintah yang disimpan dalam variabel. Mari kita anggap variabel $imemiliki nilai:

cat -nT index.php |grep 'someregex'

Ketika saya mencoba menjalankan variabel di atas dengan mengetiknya $igagal karena shell mencoba mengeksekusi seluruh variabel sebagai satu perintah. Saya juga mencoba menggunakan eval($i)dan memasukkan $ibackticks.

Bagaimana saya bisa membuat shell mengeksekusi $iseolah-olah itu adalah perintah? Dan mengapa itu tidak bekerja sama dengan.

$i='echo hi'; $i

Apakah karena saya harus memilah-milah dalam tanda kutip tunggal? (Karena Anda tidak dapat membuat sarang mereka.) Saat ini solusi saya adalah

echo $i > /foo;  . /foo

Tapi saya tidak ingin membuat file hanya untuk ini, hanya untuk menghapusnya nanti.

Yang saya maksud dengan "Saya meretas dalam tanda kutip tunggal adalah yang saya lakukan:

$i='cat index.php | grep -P '"'"'MYREGEXHERE'"'"
Leathan
sumber
4
Pertanyaannya adalah, mengapa perintah disimpan dalam variabel di tempat pertama? Anda akan lebih baik merakit perintah ketika Anda menjalankannya, menyimpan argumen opsi dalam variabel atau dalam array. Bisakah Anda menunjukkan seluruh skrip yang Anda miliki?
slhck
Penyebab root yang sama: superuser.com/questions/360966/…
Ciro Santilli 新疆 改造 中心 996ICU 六四 事件

Jawaban:

16

Jawaban singkat: lihat BashAQ # 50: Saya mencoba untuk menempatkan perintah dalam variabel, tetapi kasus kompleks selalu gagal! .

Jawaban panjang: itu karena urutan di mana bash mem-parsing baris perintah. Secara khusus, ini mencari hal-hal seperti pipa dan pengalihan sebelum memperluas nilai variabel, dan tidak kembali dan mencari kembali pipa dll dalam nilai yang diperluas. Pada dasarnya, variabel bisa diganti sekitar setengah jalan melalui proses penguraian, sehingga nilainya hanya akan diurai setengah sebelum dieksekusi.

Untuk mengatasi ini, Anda perlu menjawab pertanyaan @ slhck: mengapa perintah disimpan dalam variabel? Apa masalah sebenarnya yang Anda coba selesaikan? Bergantung pada tujuan sebenarnya, ada sejumlah solusi yang mungkin:

  • Jangan menyimpannya dalam variabel, jalankan saja secara langsung. Menyimpan perintah untuk digunakan nanti itu rumit, dan jika Anda tidak benar-benar perlu, jangan lakukan.

  • Gunakan fungsi alih-alih variabel. Lagipula itulah gunanya:

    i() { cat -nT index.php |grep 'someregex'; }
    i
    

    Kerugian utama dari ini adalah bahwa Anda tidak dapat membuat fungsi secara dinamis - Anda tidak dapat memasukkan atau mengecualikan kode dari fungsi secara kondisional (meskipun fungsi itu sendiri dapat memiliki elemen kondisional yang dipilih saat dijalankan).

  • Gunakan eval. Ini harus dianggap sebagai upaya terakhir, karena mudah untuk mendapatkan perilaku yang tidak terduga. Ini pada dasarnya menjalankan perintah melalui pass parsing penuh lain, sehingga semua pipa dll mendapatkan makna penuh - tetapi itu juga berarti bahwa bagian dari perintah yang Anda pikir hanya data juga akan diurai dan mungkin dieksekusi. Nama file yang mengandung karakter meta shell (pipa, titik koma, kutipan / apostrof, dll) dapat memiliki efek aneh dan terkadang berbahaya. Jika Anda menggunakannya eval, setidaknya kutip dua kali string, jika tidak, isinya pada dasarnya diurai satu setengah kali, bahkan dengan hasil yang lebih aneh.

    i="cat -nT index.php |grep 'someregex'"
    eval "$i"
    

EDIT: Pendekatan store-a-command-in-a-variable standar lainnya adalah menggunakan array alih-alih variabel sederhana. Ini memungkinkan Anda menyimpan perintah dengan argumen kompleks (mis. Berisi spasi) tanpa masalah, tetapi tidak akan menyimpan hal-hal seperti pipa dan arahan ulang. Jadi, pendekatan array tidak akan berguna dalam kasus khusus ini.

Gordon Davisson
sumber
+1 untuk evalmetode ini. Ini menghentikan saya dari mengajukan pertanyaan yang sama :). Saya memiliki sesuatu seperti sudo rsync -aAHXi -n --delete-excluded --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/lost+found","/mnt/DATA/*","/var/log/*","/var/swap","/var/cache/apt/archives/*.deb"} -e ssh root@piac_wireless:/ /home/mrx/Docs/RPi/backup/piac/piac_usb-rootitu yang tidak mengeksekusi pengecualian dengan benar.
dentex
@dentex Saya sangat merekomendasikan untuk tidak menggunakan evalini - ada terlalu banyak cara untuk salah. Array akan menjadi cara yang lebih baik untuk melakukan ini. Lihat di sini , di sini , dan di sini untuk contoh.
Gordon Davisson
Terima kasih untuk sarannya. Saya akan mencoba menerapkan solusi dengan array, untuk meneruskan daftar pengecualian ke rsync.
dentex
4

Ini seharusnya bekerja:

a="cat -nT index.php |grep 'someregex'"
eval "$a"

Waspadalah yang evalmemperkenalkan potensi kerentanan dan situasi perilaku yang tidak terduga sehingga harus digunakan, jika pernah, dengan perhatian ekstra.

Jlliagre
sumber
Jika Anda menggunakannya eval, Anda benar-benar perlu menggunakan tanda kutip ganda ( eval "$i") untuk menghindari efek ekstra-ekstra-aneh. Misalnya, coba ini dengan a="cat -nT index.php |grep ' .* '"(yaitu regex harus mencocokkan dua spasi dengan urutan karakter di antara mereka) dan lihat apa yang sebenarnya terjadi. Untuk kredit tambahan, jelaskan mengapa itu terjadi.
Gordon Davisson
@GordonDavisson kutip ganda ditambahkan.
jlliagre
3

Saat mendeklarasikan variabel, Anda tidak harus menggunakan tanda dolar sebagai awalan.

Coba ini:

i='echo hi'; $i
miq
sumber
Itu benar, tapi itu bukan jawaban untuk pertanyaan OP, sungguh.
slhck
bagi saya
SANGAT NYATA