Bashでコマンドの出力に変数を設定するにはどうすればよいですか?


1678

次のような非常に単純なスクリプトがあります。

#!/bin/bash

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

echo $MOREF

このスクリプトをコマンドラインから実行して引数を渡しても、何も出力されません。ただし、$MOREF変数に含まれているコマンドを実行すると、出力を取得できます。

スクリプト内で実行する必要があるコマンドの結果を取得して変数に保存し、その変数を画面に出力するにはどうすればよいでしょうか。



40
余談ですが、すべて大文字の変数は、オペレーティングシステムまたはシェル自体に意味のある変数名に対してPOSIX定義されていますが、小文字が1つ以上ある名前はアプリケーションで使用するために予約されています。したがって、意図しない競合を避けるために、シェル変数に小文字の名前を使用することを検討してください(シェル変数を設定すると、同じ名前の環境変数が上書きされることに注意してください)。
Charles Duffy

1
余談ですが、出力を変数にキャプチャして、変数を使用できるようにするのechoは、の無駄な使用でありecho変数の無駄な使用です。
Tripleee

1
さらに余談として、変数に出力を保存することはしばしば不必要です。小さくて短い文字列の場合、プログラムで複数回参照する必要があります。これは完全に問題なく、正確に進む方法です。しかし、重要なデータを処理するには、プロセスをパイプラインに再形成するか、一時ファイルを使用します。
Tripleee、

回答:


2378

バックティック`command`に加えて、コマンドの置換$(command)or "$(command)"で行うこともできます。これは読みやすく、ネストが可能です。

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

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

複数行の変数値"を保持するには、引用符()が重要です単語分割は行われないため、割り当ての右側ではオプションであり、問​​題なくOUTPUT=$(ls -1)機能します。


59
複数行の出力にセパレータを提供できますか?
Aryan

20
ホワイトスペース(またはホワイトスペースの欠如)が問題
アリ

8
@ timhc22、中括弧は無関係です。重要なのは引用符だけです:echoコマンドに渡される前に、展開結果が文字列分割され、グロブ展開されるかどうか。
Charles Duffy

4
ああありがとう!中かっこにメリットはありますか?
timhc22 2015

14
中括弧は、変数の直後に、変数名の一部として解釈される可能性がある文字がさらに続く場合に使用できます。 例えば ${OUTPUT}foo。以下のような、可変のインライン文字列操作を実行するときにも必要とされている${OUTPUT/foo/bar}
リッチレーマー

282

正しい方法は

$(sudo run command)

アポストロフィを使用する場合は`、ではなくが必要'です。この文字は「バックティック」(または「グレイブアクセント」)と呼ばれます。

このような:

#!/bin/bash

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

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

echo "$MOREF"

31
バックティック構文は廃止されており、変数の補間をで二重引用符で囲む必要がありますecho
tripleee 2015

10
上記の割り当てで '='の前後のスペースに注意する必要があることを付け加えます。あなたは、スペースを持っているshouln'tそうでなければ、間違った割り当てを得るでしょう、そこに
zbstof

4
tripleeeeのコメントは正しいです。cygwin(2016年5月)では、「が機能している間は$()機能しません。このページを見るまで修正できませんでした。
toddwz

2
Update(2018)の例のような詳細な説明をいただければ幸いです。
エデュアルド

90

コマンドから変数を設定するために使用するいくつかのBashトリック

2番目の編集2018-02-12:別の方法を追加しました。このタスクの下部で長時間実行されているタスクを検索してください!

2018-01-25編集:サンプル関数を追加しました(ディスク使用に関する変数を入力するため)

最初のシンプルで古い互換性のある方法

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

ほとんど互換性がある、2番目の方法

ネストが重くなる可能性があるため、これには括弧が実装されました

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

ネストされたサンプル:

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

複数の変数を読み取るBashismsを使用

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

使用済みの値が必要な場合:

array=($(df -k /))

あなたは配列変数を見ることができました:

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

次に:

echo ${array[9]}
529020

しかし、私はこれを好みます:

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

1つ目read fooはヘッダー行をスキップするだけですが、1つのコマンドで7つの異なる変数を設定します。

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"

あるいは:

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

... 連想配列でも機能します。read foo disk[total] disk[used] ...

いくつかの変数を設定するためのサンプル関数:

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

注意:declare読みやすくするために、行は必要ありません。

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

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

(役に立たないようにしてくださいcat!したがって、これはフォークが1つ少なくなります。

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

すべてのパイプ(|)はフォークを意味します。別のプロセスを実行する必要がある場合、ディスクへのアクセス、ライブラリの呼び出しなど。

したがってsed、サンプルに使用すると、サブプロセスが1つのフォークのみに制限されます。

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

そしてバシズムとは

しかし、多くのアクション、主に小さなファイルに対して、Bashはそれ自体で仕事をすることができます:

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

または

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

変数分割についてさらに進める...

Bashの区切り文字で文字列を分割する方法に対する私の答えを見てください

別の方法:バックグラウンドで長時間実行されるタスクを使用してフォークを減らす

2番目の編集2018-02-12:

のような複数のフォークを防ぐために

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

または

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

これは問題なく動作しますが、多くのフォークを実行すると重くて時間がかかります。

そして、コマンドが好きdatebc、多くの操作を行うことができ行ずつ !!

見る:

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

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

したがって、リクエストごとに新しいforkを開始する必要なく、長時間実行されるバックグラウンドプロセスを使用して多くのジョブを実行できます。

これを正しく行うには、いくつかのファイル記述子fifoが必要です

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

(もちろん、FD 56未使用でなければなりません!)...そこから、次の方法でこのプロセスを使用できます。

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

関数に newConnector

私のnewConnector関数はGitHub.Comまたは自分のサイトで見つかるかもしれません(GitHubでの注意:私のサイトには2つのファイルがあります。関数とデモは1つのファイルにバンドルされており、それらをソースとして使用したり、デモ用に実行したりできます。)

サンプル:

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

この関数myBcを使用すると、単純な構文で日付のバックグラウンドタスクを使用できます。

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

そこから、バックグラウンドプロセスの1つを終了する場合は、その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

メインプロセスが終了するとすべてのfdが閉じるため、これは必要ありません。


上記のネストされたサンプルは私が探していたものです。もっと簡単な方法があるかもしれませんが、私が探していたのは、Dockerコンテナーが環境変数にその名前を指定して既に存在するかどうかを確認する方法でした。だから私にとって:EXISTING_CONTAINER=$(docker ps -a | grep "$(echo $CONTAINER_NAME)")私が探していたステートメントでした。
Capricorn1

2
@ capricorn1それは役に立たない使用法ですecho。あなたは単純にしたいですgrep "$CONTAINER_NAME"
tripleee


私はおそらくここで何かを見逃しています:kubectl get ns | while read -r line; do echo $ line | grep用語| cut -d '' -f1 ; done$line、空の行ごとに出力してから、を出力しbash: xxxx: command not foundます。しかし、私はそれがちょうど印刷することを期待しますxxx
パパニート

77

彼らがあなたにすでに示したように、あなたは「バックティック」を使うべきです。

提案された代替案$(command)も同様に機能し、読みやすくなりますが、BashまたはKornShell(およびそれらから派生したシェル)でのみ有効であるため、スクリプトをさまざまなUnixシステムで本当に移植可能にする必要がある場合は、古いバックティック表記。


23
彼らはあからさまに用心深い。バックティックは、かなり前にPOSIXで廃止されました。より新しい構文は、この千年紀のほとんどのシェルで利用できるはずです。(90年代前半にしっかりと立ち往生しているレガシー環境の HP-UX がまだあります。)
tripleee '18

25
不正解です。$()20年以上前に標準化されたPOSIX shと完全に互換性があります。
Charles Duffy

3
/bin/shSolaris 10ではまだ認識されないことに注意してください。$(…)また、AFAIKはSolaris 11でも同様です。
Jonathan Leffler、2015

2
@JonathanLefflerそれは何より、実際のSolaris 11の場合ではない/bin/shですksh93
jlliagre 2016

2
@tripleee-3年遅れの応答:-)しかし$()、過去10年以上、HP-UXのPOSIXシェルで使用してきました。
ボブジャービス-モニカを

54

私はそれを行う3つの方法を知っています。

  1. 関数はそのようなタスクに適しています:**

    func (){
        ls -l
    }

    と言って呼び出しますfunc

  2. また、別の適切なソリューションはevalです。

    var="ls -l"
    eval $var
  3. 3つ目は、変数を直接使用する方法です。

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

3番目のソリューションの出力は、適切な方法で取得できます。

echo "$var"

そしてまた厄介な方法で:

echo $var

1
最初の2つは現在のところ問題に答えていないようで、2つ目は疑わしいと一般的に考えられています。
tripleee 2016

1
完全にbashを初めて使う人として、なぜ"$var"善悪$varなのでしょうか。
Peter


30

ただ違うために:

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

22

変数を設定するときは、=記号の前後にスペースがないことを確認してください。文字通り、これを理解するために1時間を費やし、あらゆる種類のソリューションを試しました!これはクールではありません。

正しい:

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

エラー「stuff:not found」などで失敗します

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

2
スペースでのバージョンは、別の何かを意味var=value somecommand実行するsomecommandvar、その環境では、値を持ちますvalue。したがって、var= somecommandは空の(ゼロバイト)値varの環境でエクスポートされsomecommandます。
Charles Duffy、

はい、Bashの落とし穴です。
Peter Mortensen

14

multiline / multiple command / sでそれをしたいなら、これを行うことができます:

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

または:

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

例:

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

出力:

first
second
third

ヒアドキュメントを使用すると、長い単一行のコードを複数行のコードに分解することで、物事をかなり簡単に簡略化できます。もう一つの例:

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

2
bashコマンド置換内の2番目とは何ですか?コマンド置換自体によってすでにサブシェルを作成しています。複数のコマンドを入力する場合は、改行またはセミコロンで区切るだけです。 output=$(echo first; echo second; ...)
tripleee 2015

次に、同様'bash -c "bash -c \"bash -c ...\""'に「異なる」でしょう。でもその意味はわかりません。
tripleee 2015

@tripleee heredocはそれ以上のものを意味します。ssh sudo -smysqlコマンドを内部で実行するなど、他のいくつかのコマンドでも同じことができます(bashの代わりに)
Jahid

1
ちゃんとコミュニケーションしている気がしません。私は以上の有用性に挑戦していますvariable=$(bash -c 'echo "foo"; echo "bar"')オーバーvariable=$(echo "foo"; echo "bar")-ここに文書がちょうど引用メカニズムであり、実際には別の無用な合併症を除いて何も追加しません。
tripleee 2015

2
ヒアドキュメントをsshで使用する場合ssh -p $port $user@$domain /bin/bash <<EOFPseudo-terminal will not be allocated because stdin is not a terminal.警告を回避するためにコマンドを正確に実行します
F. Hauri

9

あなたはどちらかを使う必要があります

$(command-here)

または

`command-here`

#!/bin/bash

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

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

echo "$MOREF"


1
あなたが巣を作ることができることを知りませんでした、しかしそれは完全に理にかなっています、情報をありがとうございました!
ディエゴベレス

6

これは別の方法であり、作成したすべての複雑なコードを正しく強調表示できない一部のテキストエディターで使用すると便利です。

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

これはOPの質問には対応していません。これは、プロセスの置換ではなく、コマンドの置換に関するものです。
codeforester 2018

6

バックティック(アクセントグレーブ)やを使用できます$()

お気に入り:

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

どちらも同じ効果があります。しかし、OUTPUT = $(x + 2)はより読みやすく最新のものです。


2
ネストを許可するために括弧が実装されました。
F.ハウリ16

5

実行しようとしているコマンドが失敗すると、出力がエラーストリームに書き込まれ、コンソールに出力されます。

これを回避するには、エラーストリームをリダイレクトする必要があります。

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

4

これは便利な人もいるかもしれません。変数置換の整数値。トリックは$(())二重括弧を使用しています。

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
これは、この質問には関係がないようです。誰かが配列の数値に定数係数を掛ける方法を尋ねるなら、それは理にかなった答えですが、誰かがそれを尋ねるのを見たことは一度もありません(そして、for ((...))ループはループ変数のより良い一致のように見えるでしょう) )。また、プライベート変数には大文字を使用しないでください。
Tripleee、2015

「関連性」の部分に同意しません。質問には、「Bashのコマンドからの出力に等しい変数を設定する方法は?後で投稿したコードで役立つソリューションを探してここにたどり着いたため、この回答を補足として追加しました。大文字の変数については、ありがとうございます。
Gus

1
これは書くこともできますARR=(3 2 4 1);for((N=3,M=3,COUNT=N-1;COUNT < ${#ARR[@]};ARR[COUNT]*=M,COUNT+=N)){ :;}が、@ tripleeeに同意します。何をするかわかりません。
F.ハウリ16

@ F.Hauri ... bashはますます深くなるほどperlのようになっていきます!
roblogic

4

さらに2つの方法があります。

Bashではスペースが非常に重要であることを覚えておいてください。したがって、コマンドを実行する場合は、スペースを追加せずにそのまま使用します。

  1. 以下はそれに割り当てharshilL印刷します

    L=$"harshil"
    echo "$L"
  2. 以下は、コマンドの出力trをL2に割り当てます。tr別の変数L1で操作されています。

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

4
1. $"..."おそらく、あなたが思っていることをしていません。2.これは、Andy Lesterの回答ですでに示されています。
gniourf_gniourf 2016年

@gniourf_gniourfは正しいです。bashのローカリゼーションが複数行で機能しないことを確認してください。しかし、bashでは、大文字echo ${L1,,}またはecho ${L1^^}小文字を使用できます。
F.ハウリ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.