Değişkenler yürütüldüğünde alıntılanmalıdır mı?


18

Kabuk komut dosyalamasında genel kural, zorlayıcı bir neden olmadığı sürece değişkenlerin her zaman alıntılanması gerektiğidir. Muhtemelen bilmek istediğinizden daha fazla ayrıntı için şu harika Soru ve Cevaplara bir göz atın: bash / POSIX mermilerinde bir değişkeni alıntılamayı unutmanın güvenlik sonuçları .

Bununla birlikte, aşağıdaki gibi bir işlevi düşünün:

run_this(){
    $@
}

Meli $@var ya da yok verilemeyecek? Onunla biraz oynadım ve tırnak eksikliğinin bir soruna neden olduğu bir durum bulamadım. Öte yandan, tırnak işareti kullanılması, tırnak içine alınmış değişken olarak boşluklar içeren bir komut iletildiğinde kırılmasını sağlar:

#!/usr/bin/sh
set -x
run_this(){
    $@
}
run_that(){
    "$@"
}
comm="ls -l"
run_this "$comm"
run_that "$comm"

Yukarıdaki komut dosyasını çalıştırmak şunu döndürür:

$ a.sh
+ comm='ls -l'
+ run_this 'ls -l'
+ ls -l
total 8
-rw-r--r-- 1 terdon users  0 Dec 22 12:58 da
-rw-r--r-- 1 terdon users 45 Dec 22 13:33 file
-rw-r--r-- 1 terdon users 43 Dec 22 12:38 file~
+ run_that 'ls -l'
+ 'ls -l'
/home/terdon/scripts/a.sh: line 7: ls -l: command not found

Bunun run_that $commyerine kullanırsam bunu başarabilirim run_that "$comm", ancak run_this(tırnaksız) işlev her ikisiyle de çalıştığından, daha güvenli bir bahis gibi görünüyor.

Peki, $@işi $@komut olarak yürütmek olan bir işlevde özel olarak kullanıldığında $@alıntı yapılmalı mıdır? Lütfen neden alıntılanması / olmaması gerektiğini açıklayın ve onu bozabilecek verilere bir örnek verin.


6
run_that'nin davranışı kesinlikle beklediğim şeydir (ya komuta giden yolda bir boşluk varsa?). Diğer davranışı isteseydiniz , verinin ne olduğunu bildiğiniz call -site'den kesinlikle vazgeçer misiniz? Bu işlevi run_that ls -lher iki sürümde de aynı şekilde çalışmasını beklerim. Farklı şekilde beklemenizi sağlayan bir vaka var mı?
Michael Homer

@MichaelHomer Sanırım burada yaptığım düzenleme bunu istedi
muru

@MichaelHomer bir nedenden dolayı (muhtemelen hala ikinci fincan kahvem olmadığından) Komutun argümanlarında veya yolunda boşluklar düşünmemiştim, sadece komutun kendisinde (seçenekler). Çoğu zaman olduğu gibi, bu retrospektte çok açıktır.
terdon

Kabukların komutları bir diziye doldurup yerine çalıştırmak yerine işlevleri desteklemelerinin bir nedeni vardır ${mycmd[@]}.
chepner

Yanıtlar:


20

Sorun, komutun işleve nasıl iletildiği ile ilgilidir:

$ run_this ls -l Untitled\ Document.pdf 
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_that ls -l Untitled\ Document.pdf 
-rw------- 1 muru muru 33879 Dec 20 11:09 Untitled Document.pdf

"$@"run_thisişlevinizin normal olarak yazılmış bir komutun önüne getirildiği genel durumda kullanılmalıdır . run_thisalıntı cehenneme yol açar:

$ run_this 'ls -l Untitled\ Document.pdf'
ls: cannot access Untitled\: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_this 'ls -l "Untitled\ Document.pdf"'
ls: cannot access "Untitled\: No such file or directory
ls: cannot access Document.pdf": No such file or directory
$ run_this 'ls -l Untitled Document.pdf'
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_this 'ls -l' 'Untitled Document.pdf'
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory

Boşluklu bir dosya adını nasıl iletmem gerektiğinden emin değilim run_this.


1
Gerçekten bunu düzenleyen sizin düzenlemenizdi. Bazı nedenlerden dolayı boşluklu bir dosya adı ile test etmek bana gelmedi. Neden olmasın kesinlikle hiçbir fikrim yok, ama işte gidiyorsunuz. Elbette oldukça haklısın, bunu da doğru bir şekilde yapmanın bir yolunu göremiyorum run_this.
terdon

@terdon alıntı yapmak o kadar alışkanlık haline geldi ki, yanlışlıkla alıntı yapmadan bıraktığınızı varsaydım $@. Bir örnek bırakmalıydım. : D
muru

2
Hayır, gerçekten o kadar alışkanlık ki, onu (yanlış) test ettim ve "ha, belki de bunun tırnaklara ihtiyacı olmadığı" sonucuna vardım. Genellikle beyin zarı olarak bilinen bir prosedür.
terdon

1
Boşlukları olan bir dosya adı geçiremezsiniz run_this. Bu temelde, Bash FAQ 050'de tartışıldığı gibi, dizelere karmaşık komutları doldururken karşılaştığınız problemle aynıdır .
Etan Reisner

9

O da:

interpret_this_shell_code() {
  eval "$1"
}

Veya:

interpret_the_shell_code_resulting_from_the_concatenation_of_those_strings_with_spaces() {
  eval "$@"
}

veya:

execute_this_simple_command_with_these_arguments() {
  "$@"
}

Fakat:

execute_the_simple_command_with_the_arguments_resulting_from_split+glob_applied_to_these_strings() {
  $@
}

Çok mantıklı değil.

ls -lKomutu yürütmek istiyorsanız ( bağımsız değişken olarak ve bağımsız değişken olarak lskomut ), şunları yaparsınız:ls-l

interpret_this_shell_code '"ls -l"'
execute_this_simple_command_with_these_arguments 'ls -l'

Ancak (büyük olasılıkla), bağımsız değişkenlerle lsbirlikte lsve komut -lolarak komut buysa, şunu çalıştırırsınız:

interpret_this_shell_code 'ls -l'
execute_this_simple_command_with_these_arguments ls -l

Şimdi, yürütmek istediğiniz basit bir komuttan daha fazlası varsa, değişken atamaları, yeniden yönlendirmeler, kanallar ... yapmak istiyorsanız, sadece interpret_this_shell_codeşunları yapar:

interpret_this_shell_code 'ls -l 2> /dev/null'

tabii ki her zaman şunları yapabilirsiniz:

execute_this_simple_command_with_these_arguments eval '
  ls -l 2> /dev/null'

5

Bash / ksh / zsh perspektiften bakarsak, $*ve $@genel dizi genişleme özel bir durum vardır. Dizi genişletmeleri normal değişken genişletmelere benzemez:

$ a=("a b c" "d e" f)
$ printf ' -> %s\n' "${a[*]}"
 -> a b c d e f
$ printf ' -> %s\n' "${a[@]}"
-> a b c
-> d e
-> f
$ printf ' -> %s\n' ${a[*]}
 -> a
 -> b
 -> c
 -> d
 -> e
 -> f
$ printf ' -> %s\n' ${a[@]}
 -> a
 -> b
 -> c
 -> d
 -> e
 -> f

İle $*/ ${a[*]}açılımları sen ilk değerle katıldı dizisini almakIFS uzay varsayılan-içine dev bir dize ile -ki olduğunu. Alıntı yapmazsanız, normal bir dize gibi bölünür.

İle $@/ ${a[@]}açılımları, davranış olmadığına bağlıdır $@/ ${a[@]}genişleme alıntı ya da olmasın:

  1. alıntılanmışsa ( "$@"veya "${a[@]}"), "$1" "$2" "$3" #... veya"${a[1]}" "${a[2]}" "${a[3]}" # ...
  2. eğer alıntılanmamışsa ( $@veya ${a[@]}) $1 $2 $3 #... veya${a[1]} ${a[2]} ${a[3]} # ...

Kaydırma komutları için kesinlikle alıntılanan @ genişletmeleri (1.) istersiniz .


Bash (ve bash benzeri) diziler hakkında daha fazla bilgi: https://lukeshu.com/blog/bash-arrays.html


1
Vader maskesi takarken Luke ile başlayan bir bağlantıdan bahsettiğimi fark ettim. Bu yazı ile kuvvet güçlü.
PSkocik

4

$@Alıntıyı iki katına çıkarmadığınızda , bağlantıdaki tüm zorlayıcı sorunları bıraktınız , işlevinize verdiğiniz bıraktınız.

Adlı bir komutu nasıl çalıştırabilirsiniz *? Aşağıdakilerle yapamazsınız run_this:

$ ls
1 2
$ run_this '*'
dash: 2: 1: not found
$ run_that '*'
dash: 3: *: not found

Görüyorsunuz, hata oluşsa bile, run_that size daha anlamlı bir mesaj verdi.

Tek $@tek kelimelere genişletmenin tek yolu çift ​​tırnaktır. Komut olarak çalıştırmak istiyorsanız, komutu iletmeniz ve parametreleri ayrı sözcükler olarak vermeniz gerekir. Arayan tarafında yaptığınız şey, fonksiyonunuzun içinde değil.

$ cmd=ls
$ param1=-l
$ run_that "$cmd" "$param1"
total 0
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 1
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 2

daha iyi bir seçimdir. Veya kabuk destek dizileriniz varsa:

$ cmd=(ls -l)
$ run_that "${cmd[@]}"
total 0
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 1
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 2

Kabuk diziyi hiç desteklemiyor olsa bile, onu kullanarak yine de oynayabilirsiniz"$@" .


3

Değişkenleri yürütmek bashbaşarısızlığa eğilimli bir tekniktir. run_thisTüm kenar durumlarını doğru şekilde işleyen bir işlev yazmak imkansızdır , örneğin:

  • boru hatları (ör. ls | grep filename)
  • giriş / çıkış yönlendirmeleri (ör. ls > /dev/null)
  • if whilevb. gibi kabuk ifadeleri

Tek yapmanız gereken kod tekrarından kaçınmaksa, fonksiyonları kullanmaktan daha iyidir. Örneğin:

run_this(){
    "$@"
}
command="ls -l"
...
run_this "$command"

Yazmalısın

command() {
    ls -l
}
...
command

Komutlar yalnızca çalışma zamanında kullanılabiliyorsa eval, run_thisbaşarısız olacak tüm tuhaflıkları işlemek için özel olarak tasarlanmış olan kullanmalısınız :

command="ls -l | grep filename > /dev/null"
...
eval "$command"

Not evalolduğu bilinen güvenlik sorunlarına ilişkin, ancak güvenilir olmayan kaynaklardan gelen değişkenleri geçmesi halinde run_this, sadece de keyfi kod yürütülmesine karşı karşıya gelecek.


1

Seçim senin. Eğer alıntı yoksa $@kendi değerlerinden herhangi bir ek genişleme ve yorumunu uğrarlar. Alıntı yaparsanız, iletilen tüm bağımsız değişkenler işlev genişletme kelimesi kelimesinde çoğaltılır. Sen güvenilir gibi kabuk sözdizimi belirteçleri üstesinden asla &>|vb zaten argümanlar kendiniz ayrıştırma olmadan her iki şekilde - ve ya da fonksiyon birini devretmeyi de daha makul seçenek kalır geriye böylece:

  1. Tam olarak kullanılan kelimelerle tek bir basit komutun yürütülmesinde "$@".

...veya...

  1. Yalnızca daha sonra birlikte basit bir komut olarak uygulanan bağımsız değişkenlerin daha genişletilmiş ve yorumlanmış bir sürümü $@.

Eğer kasıtlıysa ve seçtiğiniz şeyin etkileri iyi anlaşılmışsa hiçbir şekilde yanlış değildir. Her iki yolun da diğerine göre avantajları vardır, ancak ikincisinin avantajlarının nadiren özellikle yararlı olması muhtemeldir. Hala ...

(run_this(){ $@; }; IFS=@ run_this 'ls@-dl@/tmp')

drwxrwxrwt 22 root root 660 Dec 28 19:58 /tmp

... değil mi yararsız , sadece nadiren muhtemelen çok olması kullanım . Ve bir bashkabukta, çünkü söz konusu tanım özel bir yerleşimin veya bir işlevin komut satırına eklenmiş olsa bile bashvarsayılan olarak ortamına değişken bir tanım yapıştırmaz , global değeri $IFSetkilenmez ve bildirimi yereldir sadecerun_this() çağrı.

Benzer şekilde:

(run_this(){ $@; }; set -f; run_this ls -l \*)

ls: cannot access *: No such file or directory

... globbing de yapılandırılabilir. Alıntılar bir amaca hizmet eder - hiçbir şey için değildir. Bunlar olmadan kabuk genişleme ekstra yorumunu geçmesi - yapılandırılabilir yorumunu. Eskiden - bazı çok ile eski - kabukları $IFSedildi küresel uygulanan tüm giriş ve sadece açılımları. Aslında, adı geçen mermiler run_this(), tüm giriş kelimelerini değerinde kırdıkları için olduğu gibi davranıyordu $IFS. Ne arıyorsanız o çok eski kabuk davranışı ise Ve böylece, o zaman gerektiğini kullanmakrun_this() .

Onu aramıyorum ve şu anda bunun için yararlı bir örnek bulmak için oldukça zorlandım. Genelde kabuğumun çalıştırdığı komutları yazdığım komutlar olarak tercih ederim. Ve böylece, seçim göz önüne alındığında, neredeyse her zaman olurdu run_that(). Bunun haricinde...

(run_that(){ "$@"; }; IFS=l run_that 'ls' '-ld' '/tmp')

drwxrwxrwt 22 root root 660 Dec 28 19:58 /tmp

Hemen hemen her şey alıntılanabilir. Komutlar alıntılanır. Komut gerçekten çalıştığında, tüm girdi sözcükleri zaten kabuğun girdi yorumlama işleminin son aşaması olan alıntı kaldırma işleminden geçmiştir. Yani arasındaki fark 'ls've lscan yalnızca madde kabuk yorumlarken - ve en niçin alıntı olduğunu lsherhangi olmasını sağlar takma adlı lsbenim alıntılanan yerine edilmez lskomut kelimesi. Bunun dışında, alıntı yapan tek şey kelimelerin sınırlandırılmasıdır (değişken / girdi-boşluk alıntılarının nasıl ve neden çalıştığıdır) ve metakarakterlerin ve ayrılmış kelimelerin yorumlanmasıdır.

Yani:

'for' f in ...
 do   :
 done

bash: for: command not found
bash:  do: unexpected token 'do'
bash:  do: unexpected token 'done'

Bunu asla run_this()veya ile yapamazsınız.run_that() .

Ancak işlev adları ya da $PATH'd komutları ya da yerleşikler iyi alıntılanmış ya da sıralanmamış yürütülür ve ilk etapta tam olarak nasıl çalışır run_this()ve run_that()çalışır. $<>|&(){}Bunlardan hiçbiriyle yararlı bir şey yapamazsınız . Kısa eval, öyle.

(run_that(){ "$@"; }; run_that eval printf '"%s\n"' '"$@"')

eval
printf
"%s\n"
"$@"

Ancak onsuz, kullandığınız alıntılar sayesinde basit bir komutun sınırlarıyla sınırlandırılırsınız (komutlar meta karakterler için ayrıştırıldığında işlemin başında bir teklif gibi davranır çünkü bunu yapmazsanız bile $@) . Aynı kısıtlama, işlevin komut satırı ile sınırlı olan komut satırı atamaları ve yeniden yönlendirmeleri için de geçerlidir. Ama bu çok önemli değil:

(run_that(){ "$@";}; echo hey | run_that cat)

hey

Kolayca <yeniden yönlendirilebilen bir giriş ya da>Boruyu çıkışa .

Her neyse, yuvarlak bir şekilde, burada doğru veya yanlış bir yol yoktur - her yolun kullanımları vardır. Sadece kullanmak istediğiniz gibi yazmanız ve ne yapmak istediğinizi bilmeniz gerekir. Aksi orada olmaz - atlayarak tırnak bir amaç olabilir olmak hiç tırnak - ama amaç alakalı olmayan nedenlerden dolayı onları atlarsanız, sadece kötü kod yazıyoruz. Ne demek istediğini yap; Her neyse deniyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.