Komut değiştirme ile başka bir komuta nasıl argüman oluşturabilirim


11

Bunu takiben: Kabuk komutu yerine koymada beklenmedik davranış

Bazıları meşru bir şekilde boşluklar (ve muhtemelen başka şeyler) içerebilir argümanların büyük bir listesini alabilir bir komut var

Tırnaklar ile benim için bu argümanları üretebilecek bir senaryo yazdım, ancak çıktıyı kopyalayıp yapıştırmalıyım.

./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>

Bunu basitçe yaparak

./othercommand $(./somecommand)

ve yukarıda bahsedilen beklenmedik davranışlarla karşılaştı. Asıl soru şu: Komut ikamesi othercommand, bazı argümanların alıntı gerektirdiği ve bunun değiştirilemeyeceği düşünüldüğünde argümanları oluşturmak için güvenilir bir şekilde kullanılabilir mi?


"Güvenilir" ile ne demek istediğinize bağlıdır. Bir sonraki komutun çıktıyı tam olarak ekranda göründüğü gibi alıp kabuk kurallarını uygularsanız, belki evalkullanılabilir, ancak genellikle önerilmez. xargsde dikkate alınması gereken bir şey
Sergiy Kolodyazhnyy

somecommandDüzenli kabuk ayrıştırma
işleminden

Cevabımda söylediğim gibi, alan ayırma (başka) için başka bir karakter kullanın :... karakterin çıktıda güvenilir olmayacağını varsayarsak .
Olorin

Ama bu doğru değil çünkü alıntı kurallarına uymuyor, bu da
bununla

2
Gerçek dünyadan bir örnek gönderebilir misiniz? Yani, ilk komutun gerçek çıktısı ve ikinci komutla nasıl etkileşime girmesini istediğiniz.
nxnev

Yanıtlar:


10

Bu argümanları benim için tırnak içine alabilecek bir senaryo yazdım

Çıktı kabuk için uygun şekilde alıntılanmışsa ve çıktıya güveniyorsanız , o zaman evalüzerinde çalışabilirsiniz .

Dizileri destekleyen bir kabuğunuz olduğunu varsayarsak, aldığınız argümanları saklamak için bir kabuk kullanmak en iyisidir.

Eğer ./gen_args.shçıktıyı gibi üretir 'foo bar' '*' asdf, o zaman çalıştırabilir eval "args=( $(./gen_args.sh) )"adında bir dizi doldurmak için argssonuçlar. Yani üç unsurun olacağını foo bar, *, asdf.

"${args[@]}"Dizi öğelerini ayrı ayrı genişletmek için her zamanki gibi kullanabiliriz :

$ eval "args=( $(./gen_args.sh) )"
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

(Tırnaklara dikkat edin. "${array[@]}"Değiştirilmemiş bağımsız bağımsız değişkenler olarak tüm öğelere genişler. Tırnaklar olmadan dizi öğeleri sözcük bölünmesine tabidir. Örneğin, BashGuide'daki Diziler sayfasına bakın .)

Ancak , evalherhangi bir kabuk ikamesini mutlu bir şekilde çalıştıracaktır, bu nedenle $HOMEçıktıda ana dizininize genişleyecek ve bir komut ikamesi gerçekten de kabuk çalışmasında bir komut çalıştıracaktır eval. Çıktısı, "$(date >&2)"tek bir boş dizi öğesi oluşturur ve geçerli tarihi stdout'a yazdırır. gen_args.shVerileri ağ üzerindeki başka bir ana bilgisayar gibi diğer kullanıcılar tarafından oluşturulan dosya adları gibi güvenilir olmayan bir kaynaktan alırsa bu bir endişe kaynağıdır. Çıktı rastgele komutlar içerebilir. ( get_args.shKendisi kötü niyetli olsaydı, hiçbir şey çıktılaması gerekmez, kötü amaçlı komutları doğrudan çalıştırabilir.)


Değerlendirme olmadan ayrıştırılması zor olan kabuk alıntılamaya bir alternatif, komut dosyanızın çıktısında ayırıcı olarak başka bir karakter kullanmak olacaktır. Asıl argümanlarda gerekli olmayan birini seçmeniz gerekir.

Seçelim #ve script çıktısını alalım foo bar#*#asdf. Şimdi komutun çıktısını bağımsız değişkenlere bölmek için sıralanmamış komut genişletmesini kullanabiliriz .

$ IFS='#'                          # split on '#' signs
$ set -f                           # disable globbing
$ args=( $( ./gen_args3.sh ) )     # assign the values to the arrayfor var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

IFSKomut dosyasında başka bir yerde sözcük bölmeye bağlıysanız ( unset IFSvarsayılan yapmak için çalışmalısınız) daha sonra tekrar ayarlamanız ve set +fdaha sonra globbing kullanmak istiyorsanız da kullanmanız gerekir.

Bash veya dizileri olan başka bir kabuk kullanmıyorsanız bunun için konum parametrelerini kullanabilirsiniz. Değiştir args=( $(...) )ile set -- $(./gen_args.sh)ve kullanımı "$@"yerine "${args[@]}"daha sonra. (Burada da tırnak işaretleri kullanmanız gerekir "$@", aksi takdirde konumsal parametreler kelime ayırmaya tabidir.)


Her iki dünyanın en iyisi!
Olorin

Alıntı ${args[@]}
yapmanın

@ user1207217, evet, haklısın. Dizilerle "${array[@]}"olduğu gibi "$@". Her ikisinin de alıntılanması gerekir, aksi takdirde kelime bölme dizi öğelerini parçalara böler.
ilkkachu

6

Sorun, somecommandbetiğinizin seçenekleri çıkardıktan sonra othercommand, seçeneklerin yalnızca metin ve kabuğun standart ayrıştırmasının merhametinde olmasıdır (ne $IFSolursa olsun ve hangi kabuk seçeneklerinin etkilendiğinden etkilenir ; üzerinde kontrol altında olmak).

Bunun yerine kullanmanın somecommandiçin çıktı seçeneklerini, bu kolay, daha güvenli ve bunu kullanmak daha sağlam olacaktır diyoruz othercommand . somecommandDaha sonra komut dosyası bir olurdu sarıcı betik etrafında othercommandEğer komut satırında bir parçası olarak bazı özel bir şekilde aramak için hatırlamak zorunda olacağı yerine yardımcı senaryo çeşit otherscript. Wrapper komut dosyaları, başka bir benzer araçla başka benzer bir aracı çağıran bir araç sağlamanın çok yaygın bir yoludur (sadece filehangi komutların /usr/binaslında kabuk komut dosyası sarmalayıcıları olduğunu kontrol edin ).

Olarak bash, kshya zsh, kolayca bir dizi kullanan bir sarıcı betik için bireysel seçenekleri tutmak için olabilir othercommandşöyle:

options=( "hi there" "nice weather" "here's a star" "*" )
options+=( "bonus bumblebee!" )  # add additional option

Sonra arayın othercommand(hala sarmalayıcı betiğinin içindedir):

othercommand "${options[@]}"

Genişlemesi "${options[@]}", optionsdizinin her öğesinin ayrı ayrı alıntılanmasını ve othercommandayrı bağımsız değişkenler olarak sunulmasını sağlar .

Sargının kullanıcı aslında aradığını aslında kayıtsız olurdu othercommandolurdu, bir şey değil komut yerine ilişkin komut satırı seçenekleri oluşturulan eğer doğru othercommandçıktı olarak.

İçinde /bin/sh, $@seçenekleri tutmak için kullanın :

set -- "hi there" "nice weather" "here's a star" "*"
set -- "$@" "bonus bumblebee!"  # add additional option

othercommand "$@"

( setKonumsal parametre belirlemek için kullanılır komuttur $1, $2, $3vb Bunlar neyi diziyi oluşturan vardır $@başlangıçtaki standart POSIX kabukta. --İçin sinyal etmektir setverilen hiçbir seçenek, sadece argümanlar vardır. --Edilmektedir gerçekten sadece gerekli ilk değer ile başlayan bir şey olur -).

Etrafında çift tırnak olduğunu Not $@ve ${options[@]}unsurları ayrı ayrı kelime doğru ayrılmış (ve dosya adı globbed) olmadığını garanti eder.


açıklayabilir set --misin
user1207217

@ user1207217 Cevaplamak için açıklama eklendi.
Kusalananda

4

Eğer somecommandçıkış güvenilir iyi kabuk sözdiziminde, sen kullanabilirsiniz eval:

$ eval sh test.sh $(echo '"hello " "hi and bye"')
hello 
hi and bye

Ancak çıktının geçerli alıntıya sahip olduğundan emin olmalısınız, aksi takdirde komut dosyasının dışındaki komutları da çalıştırabilirsiniz:

$ cat test.sh 
for var in "$@"
do
    echo "|$var|"
done
$ ls
bar  baz  test.sh
$ eval sh test.sh $(echo '"hello " "hi and bye"; echo rm *')
|hello |
|hi and bye|
rm bar baz test.sh

Bunun echo rm bar baz test.shkomut dosyasına iletilmediğini (nedeniyle ;) ve ayrı bir komut olarak çalıştırıldığını unutmayın. Bunu vurgulamak için |etrafını ekledim $var.


Genel olarak, çıktısına tamamen güvenemezseniz, çıktısını somecommandbir komut dizesi oluşturmak için güvenilir bir şekilde kullanmak mümkün değildir.

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.