Bir değişkende saklanan bir komutu nasıl çalıştırabiliriz?


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

Yukarıdaki komutu çalıştırmak için aşağıdaki yolların neden başarısız olduğunu ya da başarılı olduğunu merak ediyordum.

$ 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


Yanıtlar:


54

Bu, unix hakkındaki bir takım sorularda tartışıldı. SE, burada bulabildiğim tüm sorunları toplamaya çalışacağım. Sonunda referanslar.


Neden başarısız

Bu problemlerle yüzleşmenizin sebebi kelime bölmek ve değişkenlerden genişletilmiş alıntıların tırnak işareti olarak görülmemesi, sadece sıradan karakterler olması.

Soruda sunulan durumlar:

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

Burada, $abcbölünmüş ve lsiki argüman alır "/tmp/test/myve dir"(birinci ve ikinci sıranın arkasındaki tırnak ile):

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

Burada, genişleme alıntı, bu yüzden tek bir kelime olarak tutulur. Kabuk ls -l "/tmp/test/my dir", boşluklar ve alıntılar dahil bir program bulmaya çalışır .

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

Ve burada, sadece ilk kelime veya $abcargüman olarak kabul edilir -c, bu yüzden Bash sadece lsgeçerli dizinde çalışır . Başka bir deyişle bash argümanlar vardır ve doldurmak için kullanılır $0, $1vb

$ bash -c $abc
'my dir'

Ve bash -c "$abc", ve eval "$abc", tırnakların çalışmasını sağlayan ek bir kabuk işleme adımı vardır, ancak aynı zamanda tüm kabuk genişletmelerin yeniden işlenmesine neden olur , bu nedenle, siz değilseniz, kullanıcı tarafından sağlanan verilerden yanlışlıkla bir komut genişletme çalıştırma riski vardır. alıntı yapmak konusunda dikkatli olun.


Bunu yapmanın daha iyi yolları

Bir komutu kaydetmenin daha iyi iki yolu a) bunun yerine bir işlevi kullanmak, b) bir dizi değişkenini (veya konumsal parametreleri) kullanmaktır.

Bir işlev kullanma:

İçindeki komutla bir fonksiyon tanımlamanız ve fonksiyonu bir komutmuş gibi çalıştırmanız yeterlidir. İşlev içindeki komutlardaki genişlemeler, yalnızca komut çalıştığında yapılır, tanımlandığında değil ve ayrı ayrı komut vermeniz gerekmez.

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

# run it
myls

Bir dizi kullanarak:

Diziler, tek tek kelimelerin beyaz boşluk içerdiği çok kelimeli değişkenler oluşturmaya izin verir. Burada, tek tek kelimeler farklı dizi öğeleri olarak depolanır ve "${array[@]}"genişleme her bir öğeyi ayrı bir kabuk sözcük olarak genişletir:

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

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

Sözdizimi biraz korkunç, ancak diziler komut satırını parça parça yapmanıza da izin veriyor. Örneğin:

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

"${mycmd[@]}"

veya komut satırının bölümlerini sabit tutun ve dizinin sadece bir kısmını, seçenekleri veya dosya adlarını doldurmasını kullanın:

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

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

Dizilerin dezavantajı, standart bir özellik olmaması, dolayısıyla düz POSIX mermileri ( Debian / Ubuntu'daki dashvarsayılan /bin/sh) onları desteklemiyor (aşağıya bakınız). Ancak Bash, ksh ve zsh bunu yapar, bu nedenle sisteminizde dizileri destekleyen bir kabuk vardır.

kullanma "$@"

Adlandırılmış dizileri desteklemeyen kabuklarda "$@", bir komutun argümanlarını tutmak için konumsal parametreleri (sözde dizi ) kullanabilirsiniz.

Aşağıdaki, önceki bölümdeki kod bitlerinin eşdeğerini yapan taşınabilir komut dosyası bitleri olmalıdır. Dizi, "$@"konumsal parametrelerin listesi ile değiştirilir . Ayar "$@"ile yapılır setve etrafındaki çift tırnak "$@"önemlidir (bunlar listedeki öğelerin tek tek alıntılanmasına neden olur).

Öncelikle, sadece argümanları içeren bir komutu saklamak "$@"ve çalıştırmak

set -- ls -l "/tmp/test/my dir"
"$@"

Bir komut için komut satırı seçeneklerinin bölümlerini koşullu olarak ayarlama:

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

"$@"

Sadece "$@"seçenekler ve işlenenler için kullanma :

set -- -x -v
set -- "$@" file1 "file name with whitespace"
set -- "$@" /somedir

transmutate "$@"

(Elbette "$@", genellikle betiğin kendisinin argümanları ile doldurulur, bu nedenle yeniden işlemden önce onları bir yere kaydetmeniz gerekir "$@".)


Dikkatli ol eval!

As evalalıntı ve genişleme işleme tanıtır ek seviyede, kullanıcı girişi dikkatli olmak gerekir. Örneğin, kullanıcı herhangi bir tekli tırnak işareti yazmadığı sürece çalışır:

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

Ancak giriş '$(uname)'.txtyaparlarsa, komut dosyanız mutlu bir şekilde komut değiştirmeyi çalıştırır.

Dizileri olan bir sürüm, kelimeler tüm zaman için ayrı tutulduğundan, içeriğinin alıntı veya başka işlemlerinin olmadığı bağışıklık kazanır filename.

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

Referanslar


2
Değerlendirmeden alıntı yaparak bir şey yaparak alabilirsiniz cmd="ls -l $(printf "%q" "$filename")". hoş değil, ancak kullanıcı bir an kullanmaya ayarlanmışsa, evalyardımcı olur. Aynı zamanda gibi benzer şeyler olsa komutu göndermek için çok yararlıdır ssh foohost "ls -l $(printf "%q" "$filename")", ya bu soruya ruhuna uygun olarak: ssh foohost "$cmd".
Patrick

Doğrudan ilişkili değil, ancak dizini zor kodladınız mı? Bu durumda, takma isme bakmak isteyebilirsiniz. Gibi bir şey:$ alias abc='ls -l "/tmp/test/my dir"'
Hopping Bunny

4

(Önemsiz) bir komutu çalıştırmanın en güvenli yolu eval. Daha sonra komutu komut satırında yazdığınız gibi yazabilirsiniz ve tam olarak girmişsiniz gibi çalıştırılır. Ama her şeyi teklif etmelisin.

Basit vaka:

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

çok basit bir durum değil:

# command: awk '! a[$0]++ { print "foo: " $0; }' inputfile
abc='awk '\''! a[$0]++ { print "foo: " $0; }'\'' inputfile'
eval "$abc"

3

İkinci teklif işareti komutu keser.

Koşarken:

abc="ls -l '/home/wattana/Desktop'"
$abc

Bana bir hata verdi.

Ama kaçtığımda

abc="ls -l /home/wattana/Desktop"
$abc

Hiç hata yok

Bunu düzeltmenin bir yolu yoktur (benim için), ancak dizin adında boşluk kalmadan hatayı önleyebilirsiniz.

Bu cevap , eval komutunun bunu düzeltmek için kullanılabileceğini, ancak benim için işe yaramadığını söyledi :(


1
Evet, örneğin gömülü boşluklu dosya isimlerine (veya glob karakterleri içerenlere) gerek olmadıkça çalışır.
ilkkachu

0

Bu işe yaramazsa '', kullanmanız gerekir ``:

abc=`ls -l /tmp/test/my\ dir/`

Daha iyi bir şekilde güncelleyin:

abc=$(ls -l /tmp/test/my\ dir/)

Bu komutun sonucunu değişkende saklar. OP (garip bir şekilde) komutun kendisini bir değişkende saklamak istiyor. Oh, ve gerçekten geri $( command... )tepmeler yerine kullanmaya başlamalısın .
roaima

Açıklamalar ve ipuçları için çok teşekkür ederiz!
balon

0

Bir python3 bir liner hakkında nasıl?

bash-4.4# pwd
/home
bash-4.4# CUSTOM="ls -altr"
bash-4.4# python3 -c "import subprocess; subprocess.call(\"$CUSTOM\", shell=True)"
total 8
drwxr-xr-x    2 root     root          4096 Mar  4  2019 .
drwxr-xr-x    1 root     root          4096 Nov 27 16:42 ..
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.