Bagaimana cara menetapkan variabel ke output dari perintah di Bash?

1679

Saya memiliki skrip yang cukup sederhana yaitu seperti berikut:

#!/bin/bash

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

echo $MOREF

Ketika saya menjalankan skrip ini dari baris perintah dan memberikan argumen, saya tidak mendapatkan hasil apa pun. Namun, ketika saya menjalankan perintah yang terkandung dalam $MOREFvariabel, saya bisa mendapatkan output.

Bagaimana seseorang dapat mengambil hasil dari perintah yang perlu dijalankan dalam skrip, menyimpannya ke variabel, dan kemudian menampilkan variabel itu di layar?

John
sumber
1
Sebuah pertanyaan terkait stackoverflow.com/questions/25116521/…
Sandeepan Nath
40
Selain itu, variabel semua huruf didefinisikan oleh POSIX untuk nama variabel dengan makna pada sistem operasi atau shell itu sendiri, sedangkan nama dengan setidaknya satu karakter huruf kecil dicadangkan untuk penggunaan aplikasi. Jadi, pertimbangkan untuk menggunakan nama huruf kecil untuk variabel shell Anda sendiri untuk menghindari konflik yang tidak disengaja (perlu diingat bahwa pengaturan variabel shell akan menimpa variabel lingkungan yang dinamai seperti).
Charles Duffy
1
Sebagai tambahan, menangkap output ke dalam variabel agar Anda dapat echomenggunakan variabel tersebut sebagai penggunaan yang tidak berguna echo, dan penggunaan variabel yang tidak berguna.
tripleee
1
Sebagai tambahan, menyimpan output dalam variabel seringkali tidak perlu. Untuk string kecil dan pendek, Anda perlu referensi beberapa kali dalam program Anda, ini benar-benar baik, dan cara yang tepat untuk pergi; tetapi untuk memproses jumlah data nontrivial, Anda ingin mengubah bentuk proses Anda menjadi pipa, atau menggunakan file sementara.
tripleee

Jawaban:

2378

Selain backticks `command`, substitusi perintah dapat dilakukan dengan $(command)atau "$(command)", yang menurut saya lebih mudah dibaca, dan memungkinkan untuk bersarang.

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

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

Mengutip ( ") penting untuk mempertahankan nilai variabel multi-line ; ini opsional di sisi kanan tugas, karena pemisahan kata tidak dilakukan , jadi OUTPUT=$(ls -1)akan berfungsi dengan baik.

Andy Lester
sumber
59
Bisakah kita menyediakan pemisah untuk output multi-jalur?
Aryan
20
Ruang putih (atau kurangnya ruang putih) penting
Ali
8
@ timhc22, kurung kurawal tidak relevan; itu hanya kutipan yang penting kembali: apakah hasil ekspansi adalah string-split dan glob-diperluas sebelum diteruskan ke echoperintah.
Charles Duffy
4
Ah terima kasih! Jadi apakah ada manfaatnya bagi kawat gigi keriting?
timhc22
14
Kurung kurawal dapat digunakan ketika variabel segera diikuti oleh lebih banyak karakter yang dapat diartikan sebagai bagian dari nama variabel. mis ${OUTPUT}foo . Mereka juga diperlukan saat melakukan operasi string sebaris pada variabel, seperti${OUTPUT/foo/bar}
kaya remer
282

Cara yang benar adalah

$(sudo run command)

Jika Anda akan menggunakan tanda kutip, Anda perlu `, tidak '. Karakter ini disebut "backticks" (atau "aksen kubur").

Seperti ini:

#!/bin/bash

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

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

echo "$MOREF"
Ilya Kogan
sumber
31
Sintaks backtick sudah usang, dan Anda benar-benar perlu menempatkan tanda kutip ganda di sekitar interpolasi variabel di echo.
tripleee
10
Saya ingin menambahkan bahwa Anda harus berhati-hati dengan spasi di sekitar '=' dalam tugas di atas. Anda tidak akan memiliki ruang di sana, jika tidak, Anda akan mendapatkan tugas yang salah
zbstof
4
komentar tripleeee benar. Dalam cygwin (Mei 2016), `` tidak bekerja sambil $()bekerja. Tidak dapat memperbaiki sampai saya melihat halaman ini.
toddwz
2
Elaborasi seperti contoh tentang Pembaruan (2018) akan dihargai.
Eduard
90

Beberapa trik Bash yang saya gunakan untuk mengatur variabel dari perintah

Sunting ke-2 2018-02-12: Menambahkan cara yang berbeda, cari di bagian bawah ini untuk tugas yang sudah berjalan lama !

Edit 2018-01-25: menambahkan fungsi sampel (untuk mengisi variabel tentang penggunaan disk)

Pertama sederhana, lama, dan cara yang kompatibel

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

Paling kompatibel, cara kedua

Karena bersarang bisa menjadi berat, kurung diimplementasikan untuk ini

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

Sampel bersarang:

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

Membaca lebih dari satu variabel (dengan Bashisms )

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

Jika saya hanya ingin nilai yang digunakan :

array=($(df -k /))

Anda bisa melihat variabel array :

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]="/")'

Kemudian:

echo ${array[9]}
529020

Tapi saya lebih suka ini:

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

Yang pertama read foohanya akan melewati baris tajuk, tetapi hanya dalam satu perintah, Anda akan mengisi 7 variabel yang berbeda :

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"

Atau bahkan:

{ 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")

... akan bekerja dengan array asosiatif juga:read foo disk[total] disk[used] ...

Fungsi sampel untuk mengisi beberapa variabel:

#!/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

Catatan: declarebaris tidak diperlukan, hanya agar mudah dibaca.

Tentang sudo cmd | grep ... | cut ...

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

(Tolong hindari yang tidak berguna cat! Jadi ini hanya satu fork less:

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

Semua pipa ( |) menyiratkan garpu. Di mana proses lain harus dijalankan, mengakses disk, pustaka panggilan, dan sebagainya.

Jadi gunakan seduntuk sampel, akan membatasi subproses hanya satu garpu :

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

Dan dengan Bashisme :

Tetapi untuk banyak tindakan, kebanyakan pada file kecil, Bash dapat melakukan pekerjaan itu sendiri:

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

atau

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

Lebih jauh tentang pemisahan variabel ...

Lihat jawaban saya untuk Bagaimana cara membagi string pada pembatas di Bash?

Alternatif: mengurangi garpu dengan menggunakan tugas yang sudah berjalan di latar belakang

Edit ke-2 2018-02-12:

Untuk mencegah beberapa garpu suka

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

atau

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

Ini berfungsi dengan baik, tetapi menjalankan banyak garpu berat dan lambat.

Dan perintah suka datedan bcbisa membuat banyak operasi, baris demi baris !!

Lihat:

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

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

Jadi kami dapat menggunakan proses latar belakang yang berjalan lama untuk membuat banyak pekerjaan, tanpa harus memulai garpu baru untuk setiap permintaan.

Kami hanya perlu beberapa deskriptor dan fifos file untuk melakukan ini dengan benar:

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

(Tentu saja, FD 5dan 6harus tidak digunakan!) ... Dari sana, Anda dapat menggunakan proses ini dengan:

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

Menjadi suatu fungsi newConnector

Anda dapat menemukan newConnectorfungsi saya di GitHub.Com atau di situs saya sendiri (Catatan tentang GitHub: ada dua file di situs saya. Fungsi dan demo digabungkan menjadi satu file yang dapat bersumber untuk digunakan atau hanya dijalankan untuk demo.)

Sampel:

. 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"

Fungsi ini myBcmemungkinkan Anda menggunakan tugas latar belakang dengan sintaks sederhana, dan untuk tanggal:

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

Dari sana, jika Anda ingin mengakhiri salah satu proses latar belakang, Anda hanya perlu menutup fd :

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

yang tidak diperlukan, karena semua fd tutup ketika proses utama selesai.

F. Hauri
sumber
Sampel bersarang di atas adalah apa yang saya cari. Mungkin ada cara yang lebih sederhana, tetapi apa yang saya cari adalah cara untuk mengetahui apakah wadah buruh pelabuhan sudah ada namanya di variabel lingkungan. Jadi bagi saya: EXISTING_CONTAINER=$(docker ps -a | grep "$(echo $CONTAINER_NAME)")adalah pernyataan yang saya cari.
Capricorn1
2
@ capricorn1 Itu tidak bergunaecho ; Anda hanya ingingrep "$CONTAINER_NAME"
tripleee
Saya mungkin melewatkan sesuatu di sini: kubectl get ns | while read -r line; do echo $ line | grep Term | cut -d '' -f1 ; donemencetak untuk setiap $linebaris kosong dan kemudian bash: xxxx: command not found. Namun saya akan berharap bahwa itu hanya mencetakxxx
papanito
77

Seperti yang telah ditunjukkan kepada Anda, Anda harus menggunakan 'backticks'.

Alternatif yang diusulkan juga $(command)berfungsi, dan juga lebih mudah dibaca, tetapi perhatikan bahwa itu hanya valid dengan Bash atau KornShell (dan cangkang yang berasal dari itu), jadi jika skrip Anda harus benar-benar portabel pada berbagai sistem Unix, Anda sebaiknya memilih notasi backticks yang lama.

bitwelder
sumber
23
Mereka sangat berhati-hati. Backticks telah ditinggalkan oleh POSIX sejak lama; sintaksis yang lebih modern harus tersedia di sebagian besar shell dari milenium ini. (Masih ada lingkungan warisan batuk HP-UX batuk yang terjebak tegas dalam awal tahun sembilan puluhan.)
tripleee
25
Salah. $()sepenuhnya kompatibel dengan POSIX sh, seperti yang distandarisasi lebih dari dua dekade lalu.
Charles Duffy
3
Perhatikan bahwa /bin/shpada Solaris 10 masih tidak mengenali $(…)- dan AFAIK juga berlaku pada Solaris 11.
Jonathan Leffler
2
@JonathanLeffler Hal ini sebenarnya tidak lebih kasus dengan Solaris 11 di mana /bin/shadalah ksh93.
jlliagre
2
@ tripleee - respons tiga tahun terlambat :-) tapi saya sudah menggunakan $()POSIX shell di HP-UX selama 10+ tahun terakhir.
Bob Jarvis - Reinstate Monica
54

Saya tahu tiga cara untuk melakukannya:

  1. Fungsinya sesuai untuk tugas-tugas tersebut: **

    func (){
        ls -l
    }

    Pinta dengan mengatakan func.

  2. Juga solusi lain yang cocok bisa berupa eval:

    var="ls -l"
    eval $var
  3. Yang ketiga menggunakan variabel secara langsung:

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

Anda bisa mendapatkan output dari solusi ketiga dengan cara yang baik:

echo "$var"

Dan juga dengan cara yang jahat:

echo $var
MLSC
sumber
1
Dua yang pertama tampaknya tidak menjawab pertanyaan seperti saat ini, dan yang kedua umumnya dianggap meragukan.
tripleee
1
Sebagai seseorang yang sepenuhnya baru untuk bash, mengapa "$var"baik dan $varjahat?
Peter
30

Hanya untuk berbeda:

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

Saat mengatur variabel, pastikan Anda memiliki NO Spaces sebelum dan / atau setelah tanda = . Secara harfiah menghabiskan satu jam mencoba untuk mencari tahu ini, mencoba semua jenis solusi! Ini tidak keren.

Benar:

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

Akan Gagal dengan kesalahan "barang: tidak ditemukan" atau serupa

WTFF= `echo "stuff"`
echo "Example: $WTFF"
Emil
sumber
2
Versi dengan ruang berarti sesuatu yang berbeda : var=value somecommandberjalan somecommanddengan varlingkungannya yang memiliki nilai value. Dengan demikian, var= somecommandmengekspor vardalam lingkungan somecommanddengan nilai kosong (nol-byte).
Charles Duffy
Ya, Bash Gotcha.
Peter Mortensen
14

Jika Anda ingin melakukannya dengan multiline / beberapa perintah / s maka Anda dapat melakukan ini:

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

Atau:

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

Contoh:

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

Keluaran:

first
second
third

Dengan menggunakan heredoc , Anda dapat menyederhanakan banyak hal dengan mudah dengan memecah kode baris tunggal panjang Anda menjadi multiline. Contoh lain:

output="$( ssh -p $port $user@$domain <<EOF
# Breakdown your long ssh command into multiline here.
EOF
)"
Jahid
sumber
2
Ada apa dengan yang kedua bashdi dalam substitusi perintah? Anda sudah membuat subkulit dengan substitusi perintah itu sendiri. Jika Anda ingin menempatkan beberapa perintah, cukup pisahkan dengan baris baru atau titik koma. output=$(echo first; echo second; ...)
tripleee
Maka sama 'bash -c "bash -c \"bash -c ...\""'akan "berbeda", juga; tapi saya tidak mengerti intinya.
tripleee
@tripleee heredoc memiliki arti lebih dari itu. Anda dapat melakukan hal yang sama dengan beberapa perintah lain seperti ssh sudo -smengeksekusi perintah mysql di dalam, dll. (Bukan bash)
Jahid
1
Saya tidak merasa kami berkomunikasi dengan baik. Saya menantang kegunaan selama variable=$(bash -c 'echo "foo"; echo "bar"')lebih variable=$(echo "foo"; echo "bar")- di sini dokumen hanya mekanisme mengutip dan tidak benar-benar menambahkan apa-apa kecuali komplikasi tidak berguna lagi.
tripleee
2
Ketika saya menggunakan heredoc dengan ssh, saya mengatur perintah untuk menjalankan ssh -p $port $user@$domain /bin/bash <<EOFuntuk mencegah Pseudo-terminal will not be allocated because stdin is not a terminal.peringatan
F. Hauri
9

Anda harus menggunakan keduanya

$(command-here)

atau

`command-here`

Contoh

#!/bin/bash

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

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

echo "$MOREF"
Diego Velez
sumber
2
$()jauh lebih baik daripada backticks. Lihat: Apa manfaat menggunakan $ () daripada backtick dalam skrip shell?
codeforester
1
Saya tidak tahu Anda bisa bersarang tetapi itu masuk akal, terima kasih banyak atas informasinya!
Diego Velez
6

Ini adalah cara lain dan baik untuk digunakan dengan beberapa editor teks yang tidak dapat menyorot dengan benar setiap kode rumit yang Anda buat:

read -r -d '' str < <(cat somefile.txt)
echo "${#str}"
echo "$str"
Aquarius Power
sumber
Ini tidak berurusan dengan pertanyaan OP, yang sebenarnya tentang penggantian perintah , bukan proses penggantian .
codeforester
6

Anda dapat menggunakan backticks (juga dikenal sebagai aksen kuburan) atau $().

Suka:

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

Keduanya memiliki efek yang sama. Tetapi OUTPUT = $ (x + 2) lebih mudah dibaca dan yang terbaru.

Pratik Patil
sumber
2
Parenthesis diimplementasikan untuk mengizinkan persarangan.
F. Hauri
5

Jika perintah yang Anda coba jalankan gagal, itu akan menulis output ke aliran kesalahan dan kemudian akan dicetak ke konsol.

Untuk menghindarinya, Anda harus mengarahkan aliran kesalahan:

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

Beberapa mungkin menemukan ini berguna. Nilai integer dalam substitusi variabel, di mana triknya menggunakan $(())tanda kurung ganda:

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
Gus
sumber
1
Ini sepertinya tidak memiliki relevansi untuk pertanyaan ini. Ini akan menjadi jawaban yang masuk akal jika seseorang bertanya bagaimana melipatgandakan angka dalam array dengan faktor konstan, meskipun saya tidak ingat pernah melihat ada orang yang menanyakan hal itu (dan kemudian sebuah for ((...))loop akan terlihat lebih cocok untuk variabel loop) ). Selain itu, Anda tidak boleh menggunakan huruf besar untuk variabel pribadi Anda.
tripleee
Saya tidak setuju dengan bagian "relevansi". Pertanyaannya dengan jelas berbunyi: Bagaimana mengatur variabel yang sama dengan output dari perintah di Bash? Dan saya menambahkan jawaban ini sebagai pelengkap karena saya tiba di sini mencari solusi yang membantu saya dengan kode yang kemudian saya posting. Mengenai vars huruf besar, terima kasih untuk itu.
Gus
1
Ini bisa ditulis ARR=(3 2 4 1);for((N=3,M=3,COUNT=N-1;COUNT < ${#ARR[@]};ARR[COUNT]*=M,COUNT+=N)){ :;}tetapi saya setuju dengan @tripleee: Saya tidak mengerti apa yang dilakukan, ini!
F. Hauri
@ F. Hauri ... bash semakin & lebih seperti perl semakin Anda masuk ke dalamnya!
roblogic
4

Berikut ini dua cara lagi:

Harap diingat bahwa ruang sangat penting di Bash. Jadi, jika Anda ingin perintah Anda dijalankan, gunakan apa adanya tanpa memasukkan spasi lagi.

  1. Berikut ini menetapkan harshiluntuk Ldan mencetaknya

    L=$"harshil"
    echo "$L"
  2. Berikut ini memberikan output dari perintah trke L2. trsedang dioperasikan pada variabel lain, L1.

    L2=$(echo "$L1" | tr [:upper:] [:lower:])
Harshil
sumber
4
1. $"..."mungkin tidak melakukan apa yang Anda pikirkan . 2. Ini sudah diberikan dalam jawaban Andy Lester.
gniourf_gniourf
@ gniourf_gniourf benar: lihat lokalisasi bash tidak akan berfungsi dengan multiline . Tetapi di bawah bash , Anda bisa menggunakan echo ${L1,,}untuk menurunkan, atau echo ${L1^^}untuk menghapus.
F. Hauri