Kabuk betiğindeki bir değişkende bir komut nasıl saklanır?


113

Daha sonraki bir dönemde bir değişkende kullanmak için bir komutu depolamak istiyorum (komutun çıktısı değil, komutun kendisi)

Aşağıdaki gibi basit bir senaryom var:

command="ls";
echo "Command: $command"; #Output is: Command: ls

b=`$command`;
echo $b; #Output is: public_html REV test... (command worked successfully)

Ancak biraz daha karmaşık bir şey denediğimde başarısız oluyor. Örneğin, yaparsam

command="ls | grep -c '^'";

Çıktı:

Command: ls | grep -c '^'
ls: cannot access |: No such file or directory
ls: cannot access grep: No such file or directory
ls: cannot access '^': No such file or directory

Daha sonra kullanmak için böyle bir komutu (borular / çoklu komutlarla) bir değişkende nasıl saklayabileceğim hakkında bir fikriniz var mı?


10
Bir işlev kullanın!
gniourf_gniourf

Yanıtlar:


146

Eval kullanın:

x="ls | wc"
eval "$x"
y=$(eval "$x")
echo "$y"

27
Artık yerine $ (...) önerilmektedir backticks. y = $ (x $ değerini değerlendirin
James Broadhead

14
evalsadece değişkenlerinizin içeriğine güveniyorsanız kabul edilebilir bir uygulamadır . Diyelim ki x="ls $name | wc"(veya hatta x="ls '$name' | wc") çalıştırıyorsanız, bu kod, daha az ayrıcalığa sahip biri tarafından ayarlanabiliyorsa, bu kod enjeksiyon veya ayrıcalık yükseltme güvenlik açıkları için hızlı bir yoldur. ( /tmpÖrneğin, içindeki tüm alt dizinleri yinelemek ? Sistemdeki her bir kullanıcının birini aramayacağına güvenseniz iyi olur $'/tmp/evil-$(rm -rf $HOME)\'$(rm -rf $HOME)\'/').
Charles Duffy

9
evalbeklenmedik ayrıştırma davranışı riski hakkında bir uyarı olmadan asla önerilmemesi gereken devasa bir böcek mıknatısıdır (CharlesDuffy'nin örneğinde olduğu gibi kötü niyetli dizeler olmasa bile). Örneğin, deneyin x='echo $(( 6 * 7 ))'ve sonra eval $x. Bunun "42" yazmasını bekleyebilirsiniz, ancak muhtemelen olmayacak. Neden işe yaramadığını açıklayabilir misin? Neden "muhtemelen" dediğimi açıklayabilir misin? Bu soruların cevapları size açık değilse, asla dokunmamalısınız eval.
Gordon Davisson

1
@Öğrenci, çalıştırılan set -xkomutları günlüğe kaydetmek için önceden koşmayı deneyin , bu da neler olduğunu görmeyi kolaylaştıracaktır.
Charles Duffy

1
@Öğrenci ayrıca genel hatalara (ve almamalısınız kötü alışkanlıklara) işaret ettiği için shellcheck.net'i öneririm .
Gordon Davisson

41

Do not kullanmak eval! Keyfi kod çalıştırma getirme riski büyüktür.

BashFAQ-50 - Bir değişkene bir komut koymaya çalışıyorum, ancak karmaşık durumlar her zaman başarısız oluyor.

Bir dizide koyun ve çift tırnak tüm kelimeleri genişletmek "${arr[@]}"için değil izin IFSbölünmüş nedeniyle kelimeleri Kelime Yarma .

cmdArgs=()
cmdArgs=('date' '+%H:%M:%S')

ve içindeki dizinin içeriğini görün. declare -pAyrı endeksleri, her komut parametresi dizi iç içeriğini bkz sağlar. Böyle bir bağımsız değişken boşluk içeriyorsa, diziye eklerken içeriden alıntı yapmak, Sözcük Bölme nedeniyle bölünmesini önleyecektir.

declare -p cmdArgs
declare -a cmdArgs='([0]="date" [1]="+%H:%M:%S")'

ve komutları şu şekilde yürütün

"${cmdArgs[@]}"
23:15:18

(veya) bashkomutu çalıştırmak için tamamen bir işlev kullanın,

cmd() {
   date '+%H:%M:%S'
}

ve işlevi sadece

cmd

POSIX shdizisine sahip değildir, bu nedenle en yakın gelebileceğiniz şey, konumsal parametrelerde bir öğe listesi oluşturmaktır. İşte shbir posta programını çalıştırmanın POSIX yolu

# POSIX sh
# Usage: sendto subject address [address ...]
sendto() {
    subject=$1
    shift
    first=1
    for addr; do
        if [ "$first" = 1 ]; then set --; first=0; fi
        set -- "$@" --recipient="$addr"
    done
    if [ "$first" = 1 ]; then
        echo "usage: sendto subject address [address ...]"
        return 1
    fi
    MailTool --subject="$subject" "$@"
}

Bu yaklaşımın, yeniden yönlendirme olmaksızın yalnızca basit komutları işleyebileceğini unutmayın. Yönlendirmeleri, ardışık düzenleri, for / while döngülerini, if ifadelerini vb. İşleyemez.

Diğer bir yaygın kullanım durumu, curlbirden çok başlık alanı ve yük ile çalıştırılmasıdır . Her zaman aşağıdaki gibi bağımsız değişkenler tanımlayabilir curlve genişletilmiş dizi içeriğine başvurabilirsiniz.

curlArgs=('-H' "keyheader: value" '-H' "2ndkeyheader: 2ndvalue")
curl "${curlArgs[@]}"

Başka bir örnek,

payload='{}'
hostURL='http://google.com'
authToken='someToken'
authHeader='Authorization:Bearer "'"$authToken"'"'

şimdi değişkenler tanımlandığına göre, komut değişkenlerinizi saklamak için bir dizi kullanın

curlCMD=(-X POST "$hostURL" --data "$payload" -H "Content-Type:application/json" -H "$authHeader")

ve şimdi uygun şekilde alıntılanmış bir genişletme yapın

curl "${curlCMD[@]}"

Bu benim için çalışmıyor, denedim Command=('echo aaa | grep a')ve "${Command[@]}"kelimenin tam anlamıyla komutu çalıştırmasını umuyorum echo aaa | grep a. Öyle değil. Değiştirmenin güvenli bir yolu olup olmadığını merak ediyorum eval, ancak görünüşe göre aynı güce sahip her çözüm evaltehlikeli olabilir. Değil mi?
Öğrenci

Kısaca, orijinal dize bir boru '|' içeriyorsa bu nasıl çalışır?
Öğrenci

@ Öğrenci, orijinal dizeniz bir boru içeriyorsa, bu dizenin kod olarak çalıştırılması için bash ayrıştırıcısının güvenli olmayan bölümlerinden geçmesi gerekir. Bu durumda dize kullanmayın; bunun yerine bir işlev kullanın: Command() { echo aaa | grep a; }- daha sonra çalıştırabilir Commandveya result=$(Command)veya benzerlerini yapabilirsiniz.
Charles Duffy

1
@ Öğrenci, doğru; ama bu kasıtlı olarak başarısız olur , çünkü yapmak istediğiniz şey doğası gereği güvensizdir .
Charles Duffy

1
@Öğrenci: Sonunda, belirli koşullar altında çalışmadığını belirten bir not ekledim
Inian

25
var=$(echo "asdf")
echo $var
# => asdf

Bu yöntem kullanılarak komut hemen değerlendirilir ve dönüş değeri saklanır.

stored_date=$(date)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015

Ters işaret ile aynı

stored_date=`date`
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015

Eval içinde kullanmak, $(...)daha sonra değerlendirilmesini sağlamaz

stored_date=$(eval "date")
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015

Eval kullanılarak, ne zaman evalkullanıldığında değerlendirilir

stored_date="date" # < storing the command itself
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:05 EST 2015
# (wait a few seconds)
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:16 EST 2015
#                     ^^ Time changed

Yukarıdaki örnekte, bağımsız değişkenlerle bir komut çalıştırmanız gerekiyorsa, bunları depoladığınız dizeye koyun.

stored_date="date -u"
# ...

Bash betikleri için bu nadiren alakalı, ancak son bir not. Dikkatli olun eval. Yalnızca sizin kontrol ettiğiniz dizeleri değerlendirin, asla güvenilmeyen bir kullanıcıdan gelmeyen veya güvenilmeyen kullanıcı girdileriyle oluşturulan dizeleri değerlendirin.

  • Komuttan alıntı yapmamı hatırlattığı için @CharlesDuffy'ye teşekkürler!

Bu, komutun bir boru '|' içerdiği orijinal sorunu çözmez.
Öğrenci

@Nate, bunun yalnızca içerdiğinde eval $stored_dateyeterince iyi olabileceğini ancak çok daha güvenilir olduğunu unutmayın. Bir örnek için finali tırnak işaretleri ile ve olmadan çalıştırın . :)stored_datedateeval "$stored_date"str=$'printf \' * %s\\n\' *'; eval "$str""$str"
Charles Duffy

@CharlesDuffy Teşekkürler, alıntı yapmayı unuttum. Bahse girerim, linterim onu ​​çalıştırmak için zahmet etseydim şikayet ederdi.
Nate

1

Bash için komutunuzu şu şekilde saklayın:

command="ls | grep -c '^'"

Komutunuzu şu şekilde çalıştırın:

echo $command | bash

1
Emin değilim ama belki de komutu bu şekilde çalıştırmanın, "eval" kullanımının sahip olduğu risklerle aynıdır.
Derek Hazell

0

Çeşitli farklı yöntemler denedim:

printexec() {
  printf -- "\033[1;37m$\033[0m"
  printf -- " %q" "$@"
  printf -- "\n"
  eval -- "$@"
  eval -- "$*"
  "$@"
  "$*"
}

Çıktı:

$ printexec echo  -e "foo\n" bar
$ echo -e foo\\n bar
foon bar
foon bar
foo
 bar
bash: echo -e foo\n bar: command not found

Gördüğünüz gibi, yalnızca üçüncüsü "$@"doğru sonucu verdi.


0

Aşağıdakilerle bir sipariş kaydederken dikkatli olun: X=$(Command)

Bu hala çağrılmadan önce idam ediliyor. Bunu kontrol etmek ve onaylamak için şunları yapabilirsiniz:

echo test;
X=$(for ((c=0; c<=5; c++)); do
sleep 2;
done);
echo note the 5 seconds elapsed

-1
#!/bin/bash
#Note: this script works only when u use Bash. So, don't remove the first line.

TUNECOUNT=$(ifconfig |grep -c -o tune0) #Some command with "Grep".
echo $TUNECOUNT                         #This will return 0 
                                    #if you don't have tune0 interface.
                                    #Or count of installed tune0 interfaces.

-8

Daha sonra kullanmanız gerekse bile komutları değişkenler halinde saklamak gerekli değildir. sadece normal şekilde çalıştırın. Değişkende depolarsanız, eval"değişkeninizi çalıştırmak" için bir tür ifadeye ihtiyacınız olacak veya bazı gereksiz kabuk işlemlerini başlatacaksınız .


1
Depolayacağım komut, gönderdiğim seçeneklere bağlı olacaktır, bu nedenle programımın büyük bir bölümünde tonlarca koşullu ifade kullanmak yerine, daha sonra kullanmak üzere ihtiyacım olan komutu saklamak çok daha kolay.
Benjamin

1
@Benjamin, sonra en azından seçenekleri komut olarak değil değişkenler olarak saklayın. egvar='*.txt'; find . -name "$var"
kurumi
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.