Bash'teki bir komutun çıkışına nasıl değişken ayarlayabilirim?


1676

Aşağıdaki gibi bir şey oldukça basit bir komut dosyası var:

#!/bin/bash

VAR1="$1"
MOREF='sudo run command against $VAR1 | grep name | cut -c7-'

echo $MOREF

Bu komut dosyasını komut satırından çalıştırıp argümanları ilettiğimde, herhangi bir çıktı almıyorum. Ancak, $MOREFdeğişkenin içerdiği komutları çalıştırdığımda çıktı alabiliyorum.

Bir komut dosyasında çalıştırılması gereken bir komutun sonuçlarını nasıl alabilir, bir değişkene kaydedebilir ve ardından bu değişkeni ekranda çıktılayabilirsiniz?



40
Bir yana, all-caps değişkenleri POSIX tarafından işletim sistemi veya kabuğun kendisi için anlam taşıyan değişken isimleri için tanımlanırken , en az bir küçük harfli isimler uygulama kullanımı için ayrılmıştır. Bu nedenle, istenmeyen çakışmalardan kaçınmak için kendi kabuk değişkenleriniz için küçük harfli adlar kullanmayı düşünün (kabuk değişkeni ayarlamanın benzer adlandırılmış ortam değişkenlerinin üzerine yazılacağını unutmayın).
Charles Duffy

1
Bir değişkene bir kenara, yakalama çıkış olarak sadece bu yüzden daha sonra olabilir echodeğişken bir olduğu bir işe yaramaz kullanımı echo, ve değişkenlerin bir işe yaramaz kullanılması.
tripleee

1
Dahası, çıktıyı değişkenlerde depolamak genellikle gereksizdir. Küçük, kısa dizeler için programınızda birden çok kez başvurmanız gerekir, bu tamamen iyi ve tam olarak gitmek için bir yol; ancak önemsiz miktarda verilerin işlenmesi için, işleminizi bir ardışık düzende yeniden şekillendirmek veya geçici bir dosya kullanmak istersiniz.
Üçlü

Yanıtlar:


2375

Geri vuruşlara ek olarak `command`, komut değiştirme , $(command)veya "$(command)"okumayı daha kolay bulduğum ve iç içe geçmeye izin veren veya ile yapılabilir .

OUTPUT=$(ls -1)
echo "${OUTPUT}"

MULTILINE=$(ls \
   -1)
echo "${MULTILINE}"

Quoting ( ") çok satırlı değişken değerlerini korumak için önemlidir ; kelime ayırma yapılmadığından ödevin sağ tarafında isteğe bağlıdır , bu yüzden OUTPUT=$(ls -1)iyi çalışır.


59
Çok hatlı çıkış için ayırıcı sağlayabilir miyiz?
Aryan

20
Beyaz boşluk (veya boşluk olmaması) önemlidir
Ali

8
@ timhc22, kıvırcık parantez ilgisizdir; sadece önemli olan tırnak işaretleri: echoKomuta aktarılmadan önce genişletme sonuçlarının string-split ve glob-expanded olup olmadığı .
Charles Duffy

4
Ah teşekkürler! Kıvırcık parantezlere herhangi bir faydası var mı?
timhc22

14
Kıvrık ayraçlar, değişkeni hemen ardından değişken adının bir parçası olarak yorumlanabilecek daha fazla karakter izlediğinde kullanılabilir. örn ${OUTPUT}foo . Değişken üzerinde satır içi dize işlemleri gerçekleştirirken de gereklidirler, örneğin:${OUTPUT/foo/bar}
zengin hatırlatıcı

282

Doğru yol

$(sudo run command)

Bir kesme işareti kullanacaksanız, ihtiyacınız `değil '. Bu karaktere "backticks" (veya "mezar aksanı") denir.

Bunun gibi:

#!/bin/bash

VAR1="$1"
VAR2="$2"

MOREF=`sudo run command against "$VAR1" | grep name | cut -c7-`

echo "$MOREF"

31
Backtick sözdizimi eskimiş ve gerçekten değişken enterpolasyonun etrafına çift tırnak koymanız gerekiyor echo.
tripleee

10
Yukarıdaki atamada '=' etrafındaki boşluklara dikkat etmeniz gerektiğini ekliyorum. Sen boşluk var shouln't aksi takdirde yanlış bir atama alırsınız, orada
zbstof

4
tripleeee'nin yorumu doğru. Cygwin'de (Mayıs 2016), `` çalışırken $()işe yaramaz . Bu sayfayı görene kadar tamir edilemedi.
toddwz

2
Güncelleme (2018) ile ilgili bir örnek gibi detaylandırma takdir edilecektir.
Eduard

90

Komutlardan değişkenleri ayarlamak için kullandığım bazı Bash hileleri

2. Edit 2018-02-12: Farklı bir yol eklendi, bunun altında uzun süren görevler için arama yapın !

2018-01-25 Düzenleme: bir örnek işlev ekledi (disk kullanımı ile ilgili değişkenleri doldurmak için)

İlk basit, eski ve uyumlu yol

myPi=`echo '4*a(1)' | bc -l`
echo $myPi 
3.14159265358979323844

Çoğunlukla uyumlu, ikinci yol

Yuvalama ağırlaşabileceğinden, bunun için parantez uygulandı

myPi=$(bc -l <<<'4*a(1)')

İç içe örnek:

SysStarted=$(date -d "$(ps ho lstart 1)" +%s)
echo $SysStarted 
1480656334

Birden fazla değişken okuma ( Bashizmlerle )

df -k /
Filesystem     1K-blocks   Used Available Use% Mounted on
/dev/dm-0         999320 529020    401488  57% /

Ben sadece kullanılan bir değer istiyorsanız :

array=($(df -k /))

bir dizi değişkeni görebilirsiniz :

declare -p array
declare -a array='([0]="Filesystem" [1]="1K-blocks" [2]="Used" [3]="Available" [
4]="Use%" [5]="Mounted" [6]="on" [7]="/dev/dm-0" [8]="999320" [9]="529020" [10]=
"401488" [11]="57%" [12]="/")'

Sonra:

echo ${array[9]}
529020

Ama bunu tercih ederim:

{ read foo ; read filesystem size using avail prct mountpoint ; } < <(df -k /)
echo $using
529020

Birincisi read foosadece başlık satırını atlayacak , ancak sadece bir komutta 7 farklı değişken dolduracaksınız:

declare -p avail filesystem foo mountpoint prct size using
declare -- avail="401488"
declare -- filesystem="/dev/dm-0"
declare -- foo="Filesystem     1K-blocks   Used Available Use% Mounted on"
declare -- mountpoint="/"
declare -- prct="57%"
declare -- size="999320"
declare -- using="529020"

Ya da:

{ read foo ; read filesystem dsk[{6,2,9}] prct mountpoint ; } < <(df -k /)
declare -p mountpoint dsk
declare -- mountpoint="/"
declare -a dsk=([2]="529020" [6]="999320" [9]="401488")

... ilişkilendirilebilir dizilerle de çalışacaktır :read foo disk[total] disk[used] ...

Bazı değişkenleri doldurmak için örnek işlev:

#!/bin/bash

declare free=0 total=0 used=0

getDiskStat() {
    local foo
    {
        read foo
        read foo total used free foo
    } < <(
        df -k ${1:-/}
    )
}

getDiskStat $1
echo $total $used $free

Not: declaresadece okunabilirlik için satır gerekli değildir.

hakkında sudo cmd | grep ... | cut ...

shell=$(cat /etc/passwd | grep $USER | cut -d : -f 7)
echo $shell
/bin/bash

(Lütfen faydasız kaçının cat! Yani bu sadece bir çatal daha az:

shell=$(grep $USER </etc/passwd | cut -d : -f 7)

Tüm borular ( |) çatalları ifade eder. Başka bir işlemin çalıştırılması gereken yerlerde, diske erişme, kitaplık çağrıları vb.

Yani sedörnek için kullanmak , alt süreci sadece bir çatalla sınırlar :

shell=$(sed </etc/passwd "s/^$USER:.*://p;d")
echo $shell

Ve Bashizm'lerle :

Ancak, çoğunlukla küçük dosyalarda yapılan birçok eylem için Bash işi kendisi yapabilir:

while IFS=: read -a line ; do
    [ "$line" = "$USER" ] && shell=${line[6]}
  done </etc/passwd
echo $shell
/bin/bash

veya

while IFS=: read loginname encpass uid gid fullname home shell;do
    [ "$loginname" = "$USER" ] && break
  done </etc/passwd
echo $shell $loginname ...

Değişken bölme hakkında daha fazla bilgi ...

Bash'deki bir sınırlayıcıdaki bir dizeyi nasıl bölebilirim?

Alternatif: arka planda uzun süre çalışan görevleri kullanarak çatalları azaltmak

2. Düzenleme 2018-02-12:

Gibi birden fazla çatal önlemek için

myPi=$(bc -l <<<'4*a(1)'
myRay=12
myCirc=$(bc -l <<<" 2 * $myPi * $myRay ")

veya

myStarted=$(date -d "$(ps ho lstart 1)" +%s)
mySessStart=$(date -d "$(ps ho lstart $$)" +%s)

Bu işe yarar, ancak birçok çatal çalıştırmak ağır ve yavaştır.

Ve komutları satır satır gibi birçok işlem gibi dateve bcyapabilir !!

Görmek:

bc -l <<<$'3*4\n5*6'
12
30

date -f - +%s < <(ps ho lstart 1 $$)
1516030449
1517853288

Bu nedenle , her istek için yeni bir çatal başlatmak zorunda kalmadan birçok iş yapmak için uzun süren bir arka plan işlemi kullanabiliriz .

Bunu düzgün yapmak için sadece bazı dosya tanımlayıcılarına ve fifoslara ihtiyacımız var :

mkfifo /tmp/myFifoForBc
exec 5> >(bc -l >/tmp/myFifoForBc)
exec 6</tmp/myFifoForBc
rm /tmp/myFifoForBc

(Tabii ki, FD 5ve 6kullanılmamış olmalı!) ... Oradan, bu işlemi şu şekilde kullanabilirsiniz:

echo "3*4" >&5
read -u 6 foo
echo $foo
12

echo >&5 "pi=4*a(1)"
echo >&5 "2*pi*12"
read -u 6 foo
echo $foo
75.39822368615503772256

Bir işleve newConnector

Sen benim bulundu edebilir newConnectorüzerindeki fonksiyonunu GitHub.Com veya üzerinde kendi sitesinde (.: Orada sitemde Fonksiyonu iki dosyalardır ve demo kullanımı için kaynaklı ya da sadece demo için çalıştırılabilir bir dosya halinde paketlenmiştir GitHub'dan Not.)

Örneklem:

. shell_connector.sh

tty
/dev/pts/20

ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
  29019 pts/20   Ss     0:00 bash
  30745 pts/20   R+     0:00  \_ ps --tty pts/20 fw

newConnector /usr/bin/bc "-l" '3*4' 12

ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
  29019 pts/20   Ss     0:00 bash
  30944 pts/20   S      0:00  \_ /usr/bin/bc -l
  30952 pts/20   R+     0:00  \_ ps --tty pts/20 fw

declare -p PI
bash: declare: PI: not found

myBc '4*a(1)' PI
declare -p PI
declare -- PI="3.14159265358979323844"

İşlev myBc, arka plan görevini basit sözdizimi ile ve tarih için kullanmanıza izin verir:

newConnector /bin/date '-f - +%s' @0 0
myDate '2000-01-01'
  946681200
myDate "$(ps ho lstart 1)" boottime
myDate now now ; read utm idl </proc/uptime
myBc "$now-$boottime" uptime
printf "%s\n" ${utm%%.*} $uptime
  42134906
  42134906

ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
  29019 pts/20   Ss     0:00 bash
  30944 pts/20   S      0:00  \_ /usr/bin/bc -l
  32615 pts/20   S      0:00  \_ /bin/date -f - +%s
   3162 pts/20   R+     0:00  \_ ps --tty pts/20 fw

Oradan, arka plan işlemlerinden birini bitirmek istiyorsanız, sadece fd'sini kapatmanız gerekir :

eval "exec $DATEOUT>&-"
eval "exec $DATEIN>&-"
ps --tty pts/20 fw
    PID TTY      STAT   TIME COMMAND
   4936 pts/20   Ss     0:00 bash
   5256 pts/20   S      0:00  \_ /usr/bin/bc -l
   6358 pts/20   R+     0:00  \_ ps --tty pts/20 fw

bu gerekli değildir, çünkü ana işlem bittiğinde tüm fd kapanır.


Yukarıdaki iç içe örnek aradığım şeydi. Daha basit bir yol olabilir, ama aradığım şey, bir ortam değişkeninde adı verildiğinde bir docker konteynerinin zaten var olup olmadığını bulmanın yoluydu. Yani benim için: EXISTING_CONTAINER=$(docker ps -a | grep "$(echo $CONTAINER_NAME)")aradığım ifadeydi.
Capricorn1

2
@ capricorn1 Bu işe yaramaz bir kullanımecho ; sadece istiyorumgrep "$CONTAINER_NAME"
üçlü


Muhtemelen burada bir şey özledim: kubectl get ns | while read -r line; do echo $ line | grep Terim | cut -d '' -f1 ; doneher biri $lineiçin boş bir satır yazdırır ve sonra bash: xxxx: command not found. Ancak ben sadece baskı xxx
beklerdim

77

Size zaten belirttikleri gibi, 'backticks' kullanmalısınız.

Önerilen alternatif $(command)de işe yarıyor ve ayrıca okunması daha kolay, ancak sadece Bash veya KornShell (ve bunlardan türetilen kabuklar) için geçerli olduğunu unutmayın, bu nedenle komut dosyalarınızın çeşitli Unix sistemlerinde gerçekten taşınabilir olması gerekiyorsa, eski backticks gösterimi.


23
Açıkça temkinli davranıyorlar. Backticks POSIX tarafından uzun zaman önce kullanımdan kaldırıldı; bu milenyumdan gelen mermilerin çoğunda daha modern sözdizimi bulunmalıdır. ( Doksanların başlarında sıkıca sıkışmış HP-UX öksürüğü hala öksürük ortamlar var .)
üçlü

25
Yanlış. $()yirmi yıl önce standartlaştırıldığı gibi POSIX sh ile tamamen uyumludur.
Charles Duffy

3
O Not /bin/shSolaris 10 üzerinde hala tanımıyor $(…)çok Solaris 11 üzerinde doğru ve AFAIK -.
Jonathan Leffler

2
@JonathanLeffler Nerede /bin/sholduğu için Solaris 11 ile durum böyle değil ksh93.
jlliagre

2
@tripleee - Üç yıl geç cevap :-) ama $()son 10+ yıldır HP-UX üzerindeki POSIX kabuğunda kullandım .
Bob Jarvis - Monica

54

Bunu yapmanın üç yolunu biliyorum:

  1. İşlevler bu tür görevler için uygundur: **

    func (){
        ls -l
    }

    Bunu söyleyerek çağır func.

  2. Ayrıca başka bir uygun çözüm de değerlendirilebilir:

    var="ls -l"
    eval $var
  3. Üçüncüsü değişkenleri doğrudan kullanıyor:

    var=$(ls -l)
    
        OR
    
    var=`ls -l`

Üçüncü çözümün çıktısını iyi bir şekilde alabilirsiniz:

echo "$var"

Ve aynı zamanda kötü bir şekilde:

echo $var

1
İlk ikisi şu anda olduğu gibi soruya cevap vermiyor gibi görünüyor ve ikincisi genellikle şüpheli olarak tutuluyor.
tripleee

1
Bash için tamamen yeni olan biri olarak, neden "$var"iyi ve $varkötü?
Peter


30

Sadece farklı olmak için:

MOREF=$(sudo run command against $VAR1 | grep name | cut -c7-)

22

Değişken ayarlarken = işaretinden önce ve / veya sonra NO Spaces olduğundan emin olun . Kelimenin tam anlamıyla, her türlü çözümü denemek için bir saat harcadı! Bu hoş değil.

Doğru:

WTFF=`echo "stuff"`
echo "Example: $WTFF"

"Şeyler: bulunamadı" veya benzeri bir hata ile başarısız olur

WTFF= `echo "stuff"`
echo "Example: $WTFF"

2
Boşlukla versiyon farklı bir şey demektir : var=value somecommandishal somecommandile varçevresi değerine sahip olmanın value. Böylece, boş (sıfır bayt) bir değere sahip ortamda var= somecommandihracat varyapmaktadır somecommand.
Charles Duffy

Evet, bir Bash yakaladım.
Peter Mortensen

14

Bunu çok satırlı / çoklu komut (lar) ile yapmak istiyorsanız, bunu yapabilirsiniz:

output=$( bash <<EOF
# Multiline/multiple command/s
EOF
)

Veya:

output=$(
# Multiline/multiple command/s
)

Misal:

#!/bin/bash
output="$( bash <<EOF
echo first
echo second
echo third
EOF
)"
echo "$output"

Çıktı:

first
second
third

Yorumlu metin kullanarak , uzun tek satır kodunuzu çok satırlı bir bölüme ayırarak işleri kolayca basitleştirebilirsiniz. Başka bir örnek:

output="$( ssh -p $port $user@$domain <<EOF
# Breakdown your long ssh command into multiline here.
EOF
)"

2
bashKomut ikamesinin içindeki ikinci şey nedir ? Komut yerine koymanın kendisi tarafından zaten bir alt kabuk oluşturuyorsunuz. Birden fazla komut koymak istiyorsanız, bunları yeni satır veya noktalı virgülle ayırın. output=$(echo first; echo second; ...)
tripleee

O zaman da benzer 'bash -c "bash -c \"bash -c ...\""'şekilde "farklı" olur; ama bunun amacını görmüyorum.
tripleee

@tripleee heredoc bundan daha fazlası anlamına gelir. Aynısını ssh sudo -siçerideki mysql komutlarını yürütmek gibi diğer komutlarla da yapabilirsiniz , vb. (Bash yerine)
Jahid

1
Doğru iletişim kurduğumuzu düşünmüyorum. Ben üzerinden yararlılığını zorluyorum variable=$(bash -c 'echo "foo"; echo "bar"')üzerinde variable=$(echo "foo"; echo "bar")- burada belge sadece bir alıntı mekanizmadır ve gerçekten başka bir işe yaramaz komplikasyon dışında bir şey eklemez.
tripleee

2
Heredoc'u ssh ile kullandığımda, uyarıyı ssh -p $port $user@$domain /bin/bash <<EOFönlemek için çalıştırma komutunu kesinPseudo-terminal will not be allocated because stdin is not a terminal.
F. Hauri


6

Bu başka bir yoldur ve oluşturduğunuz her karmaşık kodu doğru şekilde vurgulayamayan bazı metin editörleriyle kullanmak iyidir:

read -r -d '' str < <(cat somefile.txt)
echo "${#str}"
echo "$str"

Bu OP'nin aslında süreç ikamesi değil, komut ikamesi ile ilgili sorusu ile ilgilenmez .
codeforester

6

Geri vuruşları (aksan mezarları olarak da bilinir) veya olarak kullanabilirsiniz $().

Sevmek:

OUTPUT=$(x+2);
OUTPUT=`x+2`;

Her ikisi de aynı etkiye sahiptir. Ancak OUTPUT = $ (x + 2) daha okunabilir ve en yenisi.


2
Yuvalamaya izin vermek için parantez uygulandı.
F. Hauri

5

Yürütmeye çalıştığınız komut başarısız olursa, çıktıyı hata akışına yazar ve sonra konsola yazdırılır.

Bundan kaçınmak için hata akışını yeniden yönlendirmelisiniz:

result=$(ls -l something_that_does_not_exist 2>&1)

4

Bazıları bunu faydalı bulabilir. Hile $(())çift ​​parantez kullanan değişken ikamedeki tamsayı değerleri :

N=3
M=3
COUNT=$N-1
ARR[0]=3
ARR[1]=2
ARR[2]=4
ARR[3]=1

while (( COUNT < ${#ARR[@]} ))
do
  ARR[$COUNT]=$((ARR[COUNT]*M))
  (( COUNT=$COUNT+$N ))
done

1
Bunun bu soru ile ilgisi yok gibi görünüyor. Birisi bir dizideki bir sayıyı sabit bir faktörle nasıl çarpacağını sormak için makul bir cevap olurdu, ancak bunu soran kimseyi görmediğimi hatırlamıyorum (ve sonra bir for ((...))döngü döngü değişkeni için daha iyi bir eşleşme gibi görünüyor) ). Ayrıca, özel değişkenleriniz için büyük harf kullanmamalısınız.
tripleee

"Alaka düzeyi" kısmına katılmıyorum. Soru açıkça şunu okuyor: Bash'teki bir komutun çıktısına eşit bir değişken nasıl ayarlanır? Ve bu yanıtı bir tamamlayıcı olarak ekledim çünkü burada daha sonra yayınladığım kodla bana yardımcı olan bir çözüm arıyorum. Büyük harflerle ilgili olarak, bunun için teşekkürler.
Gus

1
Bu yazılabilir ARR=(3 2 4 1);for((N=3,M=3,COUNT=N-1;COUNT < ${#ARR[@]};ARR[COUNT]*=M,COUNT+=N)){ :;}ama @tripleee ile aynı fikirdeyim: Bunu ne yapacağımı anlamıyorum.
F. Hauri

@ F.Hauri ... bash perl gibi gittikçe derinleşiyor!
roblogic

4

İşte iki yol daha:

Bash'te alanın çok önemli olduğunu lütfen unutmayın. Yani, komutunuzun çalışmasını istiyorsanız, daha fazla boşluk girmeden olduğu gibi kullanın.

  1. Devrettikleri aşağıdaki harshiliçin Ldaha sonra ve onu yazdırır

    L=$"harshil"
    echo "$L"
  2. Aşağıdaki komutun çıktısını L2'ye atar tr. trbaşka bir değişken olan L1 üzerinde çalıştırılmaktadır.

    L2=$(echo "$L1" | tr [:upper:] [:lower:])

4
1. $"..."muhtemelen ne düşündüğünü yapmaz . 2. Bu zaten Andy Lester'ın cevabında verilmiştir.
gniourf_gniourf

@gniourf_gniourf haklıdır: bkz. bash yerelleştirmesi çok satırlılarla çalışmaz . Ama bash altında , küçük echo ${L1,,}harfle veya küçük echo ${L1^^}harfle kullanabilirsiniz.
F. Hauri
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.