Bashスクリプトの引数を反復処理する方法


900

シェル/ bashスクリプトを作成したい複雑なコマンドがあります。私はそれを$1簡単に書くことができます:

foo $1 args -o $1.ext

スクリプトに複数の入力名を渡せるようにしたいのですが。それを行う正しい方法は何ですか?

そしてもちろん、スペースを含むファイル名も処理したいと思います。


67
参考までに、パラメーターは常に引用符で囲むことをお勧めします。parmに埋め込まれたスペースが含まれる可能性がある場合は、決してわかりません。
David R Tribble、

回答:


1482

"$@"すべての引数を表すために使用します。

for var in "$@"
do
    echo "$var"
done

これにより、各引数が繰り返され、別の行に出力されます。$ @は$ *のように動作しますが、引用符で囲まれている場合、引数にスペースがあると引数が適切に分割されます。

sh test.sh 1 2 '3 4'
1
2
3 4

38
ちなみに、他のメリットの1つは、"$@"位置パラメータがない場合は何も展開せず"$*"、空の文字列に展開されることです。そして、はい、引数なしと1つの空の引数の間には違いがあります。${1:+"$@"} in / bin / sh`を参照してください。
Jonathan Leffler、2014

9
この表記法は、シェル関数でも使用して、関数のすべての引数にアクセスする必要があることに注意してください。
Jonathan Leffler、2016年

二重引用符を削除$varすると、引数を実行できました。
eonist 16

2番目の引数から始めるにはどうすればよいですか?つまり、これが必要ですが、常に2番目の引数から開始します。つまり、$ 1をスキップします。
m4l490n

4
@ m4l490n:shiftを破棄$1し、後続のすべての要素をシフトダウンします。
MSalters

239

削除された回答VonCで書き換えます。

ロバート・ギャンブルの簡潔な答えは、質問を直接扱います。これは、スペースを含むファイル名に関するいくつかの問題を増幅します。

関連項目:/ bin / shの$ {1:+ "$ @"}

基本的な論文: "$@"正しい、そして$*(引用符で囲まれていない)ほとんどの場合間違っている。これは"$@"、引数にスペースが含まれている場合は正常に機能し、含まれ$*ていない場合と同様に機能するためです。状況によって"$*"は問題ありませんが、"$@"通常は(常にではありませんが)同じ場所で機能します。引用符で囲まれていない、$@$*同等の(とほとんど常に間違っている)です。

だから、何の違いは$*$@"$*"、と"$@"?それらはすべて「シェルへのすべての引数」に関連していますが、実行する処理は異なります。引用符で囲まれ$*$@いない場合、同じことを行います。彼らはそれぞれの「単語」(空白以外のシーケンス)を個別の引数として扱います。ただし、引用符で囲まれた形式はまったく異なります。"$*"引数リストはスペースで区切られた単一の文字列"$@"として扱われますが、引数はコマンドラインで指定されたときとほとんど同じように扱われます。 "$@"位置引数がない場合、何も展開しません。"$*"空の文字列に展開されます—そうです、違いがありますが、それを理解するのは難しいかもしれません。(非標準)コマンドの導入後、以下の詳細情報を参照してくださいal

副論文:引数をスペースで処理してから他のコマンドに渡す必要がある場合は、支援するために非標準のツールが必要になることがあります。(または、配列を慎重に使用する必要があります。"${array[@]}"動作はと同様"$@"です。)

例:

    $ mkdir "my dir" anotherdir
    $ ls
    anotherdir      my dir
    $ cp /dev/null "my dir/my file"
    $ cp /dev/null "anotherdir/myfile"
    $ ls -Fltr
    total 0
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir/
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir/
    $ ls -Fltr *
    my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ ls -Fltr "./my dir" "./anotherdir"
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ var='"./my dir" "./anotherdir"' && echo $var
    "./my dir" "./anotherdir"
    $ ls -Fltr $var
    ls: "./anotherdir": No such file or directory
    ls: "./my: No such file or directory
    ls: dir": No such file or directory
    $

それがうまくいかないのはなぜですか?シェルは変数を展開する前に引用符を処理するため、機能しません。したがって、シェルにに埋め込まれた引用符に注意を向けさせるには、以下$varを使用する必要がありますeval

    $ eval ls -Fltr $var
    ./my dir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 my file

    ./anotherdir:
    total 0
    -rw-r--r--   1 jleffler  staff  0 Nov  1 14:55 myfile
    $ 

" He said, "Don't do this!""(引用符と二重引用符とスペースを含む)などのファイル名がある場合、これは本当にトリッキーになります。

    $ cp /dev/null "He said, \"Don't do this!\""
    $ ls
    He said, "Don't do this!"       anotherdir                      my dir
    $ ls -l
    total 0
    -rw-r--r--   1 jleffler  staff    0 Nov  1 15:54 He said, "Don't do this!"
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 anotherdir
    drwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir
    $ 

シェル(それらすべて)は、そのようなものを処理することを特に簡単にはしません。そのため、(おかしなことに)多くのUnixプログラムは、それらをうまく処理できません。Unixでは、ファイル名(単一のコンポーネント)には、スラッシュとNULを除く任意の文字を含めることができます'\0'。ただし、シェルでは、パス名のどこにもスペースや改行、タブを使用しないでください。標準のUnixファイル名にスペースなどが含まれていないのもこのためです。

スペースやその他の厄介な文字が含まれている可能性のあるファイル名を処理するときは、非常に注意する必要があります。また、Unixでは標準的でないプログラムが必要であることがかなり前にわかりました。私はそれを呼び出しますescape(バージョン1.1の日付は1989-08-23T16:01:45Z)。

escapeSCCS制御システムでの使用例を以下に示します。deltaチェックインを考える)とgetチェックアウトを考える)の 両方を実行するカバースクリプトです。特に-y(変更を加えた理由)さまざまな引数には、空白と改行が含まれます。このスクリプトは1992年から$(cmd ...)使用さ#!/bin/shれているため、表記ではなくバックティックを使用し、最初の行では使用しないことに注意してください 。

:   "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $"
#
#   Delta and get files
#   Uses escape to allow for all weird combinations of quotes in arguments

case `basename $0 .sh` in
deledit)    eflag="-e";;
esac

sflag="-s"
for arg in "$@"
do
    case "$arg" in
    -r*)    gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    -e)     gargs="$gargs `escape \"$arg\"`"
            sflag=""
            eflag=""
            ;;
    -*)     dargs="$dargs `escape \"$arg\"`"
            ;;
    *)      gargs="$gargs `escape \"$arg\"`"
            dargs="$dargs `escape \"$arg\"`"
            ;;
    esac
done

eval delta "$dargs" && eval get $eflag $sflag "$gargs"

(私はおそらく最近、エスケープをそれほど徹底的に使用しないでしょう- -eたとえば、引数では必要ありません-しかし、全体として、これはを使用する私の最も簡単なスクリプトの1つですescape。)

escapeプログラムは、単に、むしろのように、その引数を出力しecho ませんが、それは引数がで使用するために保護されることが保証されます eval(1つのレベルeval、私はリモートシェルの実行をしたプログラムを持っている、との出力をエスケープする必要があることescape)。

    $ escape $var
    '"./my' 'dir"' '"./anotherdir"'
    $ escape "$var"
    '"./my dir" "./anotherdir"'
    $ escape x y z
    x y z
    $ 

al引数を1行に1つリストする別のプログラムがあります(それはさらに古いものです:バージョン1.1、日付は1987-01-27T14:35:49)。コマンドにプラグインして、実際にコマンドに渡される引数を確認できるため、スクリプトのデバッグ時に最も役立ちます。

    $ echo "$var"
    "./my dir" "./anotherdir"
    $ al $var
    "./my
    dir"
    "./anotherdir"
    $ al "$var"
    "./my dir" "./anotherdir"
    $

[ 追加: 次に、さまざまな"$@"表記法の違いを示すために、もう1つの例を示します。

$ cat xx.sh
set -x
al $@
al $*
al "$*"
al "$@"
$ sh xx.sh     *      */*
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file
He
said,
"Don't
do
this!"
anotherdir
my
dir
xx.sh
anotherdir/myfile
my
dir/my
file
+ al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file'
He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file
+ al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file'
He said, "Don't do this!"
anotherdir
my dir
xx.sh
anotherdir/myfile
my dir/my file
$

コマンドラインで*との間の元の空白を保持するものがないことに注意してください*/*。また、次のコマンドを使用して、シェルの「コマンドライン引数」を変更できることに注意してください。

set -- -new -opt and "arg with space"

これにより、「-new」、「-opt」、「and」、「」の4つのオプションが設定されますarg with space
]

うーん、それはかなり長い答えです -おそらく解釈はより良い言葉です。escapeリクエストに応じて利用可能なソースコード(Gmail dot comのfirstname dot lastnameへのメール)。のソースコードalは信じられないほど単純です:

#include <stdio.h>
int main(int argc, char **argv)
{
    while (*++argv != 0)
        puts(*argv);
    return(0);
}

それで全部です。これはtest.sh、Robert Gambleが示したスクリプトに相当し、シェル関数として書くことができます(ただし、最初に書いたとき、シェルバージョンはローカルバージョンのBourneシェルには存在しませんでしたal)。

またal、単純なシェルスクリプトとして記述できることにも注意してください。

[ $# != 0 ] && printf "%s\n" "$@"

条件が必要なのは、引数が渡されない場合に出力を生成しないためです。printfコマンドは、フォーマット文字列引数で空白行を生成しますが、Cプログラムでは、何も生成しません。


132

ロバートの答えは正しいことに注意してくださいsh。あなたは(移植性のある)それをさらに簡単にすることができます:

for i in "$@"

以下と同等です。

for i

つまり、何も必要ありません。

テスト($コマンドプロンプト):

$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "$@"; do echo "$i"; done
a
b
spaces here
d

KernighanとPikeによるUnixプログラミング環境でこれについて最初に読みました。

で、これbashhelp for文書化します:

for NAME [in WORDS ... ;] do COMMANDS; done

'in WORDS ...;'が存在しない場合は、'in "$@"'想定されます。


4
同意しません。不可解な"$@"意味が何であるかを知る必要があります。いったんfor i意味がわかると、それはと同じくらい読みやすくなりfor i in "$@"ます。
Alok Singhal 2017

10
for iそれはキーストロークを節約するので、私はそれがより良いと断言しませんでした。私はの読みやすさを比較for iしてfor i in "$@"
Alok Singhal 2017

これは私が探していたものです-明示的に参照する必要がないループでの$ @の仮定を何と呼びますか?$ @参照を使用しないことの欠点はありますか?
qodeninja

58

単純なケースでは、も使用できますshift。引数リストをキューのように扱います。それぞれshiftが最初の引数をスローし、残りの各引数のインデックスがデクリメントされます。

#this prints all arguments
while test $# -gt 0
do
    echo "$1"
    shift
done

shiftforループで実行した場合、その要素はまだ配列の一部であるので、この答えはより良いと思いますが、これはshift期待どおりに機能します。
DavidG

2
を使用echo "$1"して、引数文字列の値の間隔を維持する必要があることに注意してください。また、(マイナーな問題)これは破壊的です。引数をすべて移動すると、引数を再利用できなくなります。多くの場合、それは問題ではありません。とにかく一度だけ引数リストを処理します。
ジョナサンレフラー2016

1
"-o outputfile"のようなフラグ引数を処理するためにスイッチケースで2つの引数を取得する簡単な方法があるため、シフトの方が良いことに同意します。
Baxissimo 2017年

一方、フラグ引数の解析を開始する場合は、bashよりも強力なスクリプト言語を検討する必要があるかもしれません:)
nuoritoveri

4
たとえば、いくつかのパラメーター値が必要な場合、このソリューションはより優れています。たとえば--file myfile.txt 、$ 1がパラメーター、$ 2が値であり、別の引数をスキップする必要がある場合は、shiftを2回呼び出します
Daniele Licitra

16

たとえば、すべての要素を反復処理しない場合は、配列要素としてそれらにアクセスすることもできます。

argc=$#
argv=("$@")

for (( j=0; j<argc; j++ )); do
    echo "${argv[j]}"
done

5
私はラインのargv =($ @)はARGV =(「$ @」)をスペースで他の賢明な引数が正しく処理されていないされなければならないと考えている
kdubs

正しい構文がargv =( "$ @")であることを確認します。引用符で囲まれたパラメータがある場合、最後の値を取得しない限り。たとえば、次のようなものを使用する場合./my-script.sh param1 "param2 is a quoted param"-argv =($ @)を使用すると[param1、param2]が取得されます
-argv

2
aparse() {
while [[ $# > 0 ]] ; do
  case "$1" in
    --arg1)
      varg1=${2}
      shift
      ;;
    --arg2)
      varg2=true
      ;;
  esac
  shift
done
}

aparse "$@"

1
以下に述べるように、[ $# != 0 ]より良いです。上記では、#$あるべきである$#while [[ $# > 0 ]] ...
hute37

1

(特定の単語を検索するなどのために)引数リストをインデックスで列挙する必要がある場合は、bazの答えを増幅して、リストをコピーしたり変更したりせずにこれを行うことができます。

引数リストを2つのダッシュ( "-")で分割し、ダッシュの前の引数を1つのコマンドに渡し、ダッシュの後の引数を別のコマンドに渡したいとします。

 toolwrapper() {
   for i in $(seq 1 $#); do
     [[ "${!i}" == "--" ]] && break
   done || return $? # returns error status if we don't "break"

   echo "dashes at $i"
   echo "Before dashes: ${@:1:i-1}"
   echo "After dashes: ${@:i+1:$#}"
 }

結果は次のようになります。

 $ toolwrapper args for first tool -- and these are for the second
 dashes at 5
 Before dashes: args for first tool
 After dashes: and these are for the second

1

getoptスクリプトでコマンドを使用して、コマンドラインオプションまたはパラメーターをフォーマットします。

#!/bin/bash
# Extract command line options & values with getopt
#
set -- $(getopt -q ab:cd "$@")
#
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) param="$2"
echo "Found the -b option, with parameter value $param"
shift ;;
-c) echo "Found the -c option" ;;
--) shift
break ;;
*) echo "$1 is not an option";;
esac
shift

トリッキーな1つ、これを徹底的に調査します。例:file:///usr/share/doc/util-linux/examples/getopt-parse.bash、マニュアル:man.jimmylandstudios.xyz/
man=
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.