bash - 配列 - シェルスクリプト 無限ループ
Bashスクリプトで引数を反復処理する方法 (4)
私は複雑なコマンドを持っていますが、私はシェル/ bashスクリプトを作りたいと思います。 私は$1
点でそれを簡単に書くことができます:
foo $1 args -o $1.ext
私はスクリプトに複数の入力名を渡すことができるようにしたい。 それを行う正しい方法は何ですか?
そして、もちろん、私はスペースでファイル名を扱いたい。
VonCによって今削除されたanswerをVonC 。
ロバートギャンブルの簡潔な答えは、この質問を直接取り扱っている。 これは、スペースを含むファイル名に関するいくつかの問題を増幅します。
基本的な論説: "[email protected]"
は正し、 $*
(引用符なし)はほとんど常に間違っています。 これは、引数に空白が含まれている場合は"[email protected]"
うまく動作しないため、 $*
と同じ働きをするからです。 状況によっては、 "$*"
もOKですが、通常は(常にではありませんが) "[email protected]"
は同じ場所で動作します。 引用符で囲まれていない[email protected]
と$*
は等価です(そしてほとんどの場合は間違っています)。
では、 $*
、 [email protected]
、 "$*"
、 "[email protected]"
の違いは何ですか? 彼らはすべて「シェルに対するすべての議論」に関連していますが、彼らはさまざまなことをしています。 引用符で囲まないと、 $*
と[email protected]
は同じことをします。 彼らは、それぞれの単語(空白以外のシーケンス)を別の引数として扱います。 しかし、 "$*"
は引数リストをスペースで区切られた単一の文字列として扱いますが、 "[email protected]"
は引数をコマンドラインで指定したときとほぼ同じように扱います。 "[email protected]"
は、定位置引数がないときは何も展開しません。 "$*"
は空の文字列に展開されます。そして、違いはありますが、それを知覚するのは難しいでしょう。 (非標準的な)コマンドal
の導入後に、以下の詳細を参照してください。
セカンダリテッセレーション:引数をスペースで処理してから他のコマンドに渡す必要がある場合は、非標準のツールを必要とすることがあります。 (または、配列を慎重に使用する必要があります。 "${array[@]}"
は"[email protected]"
と同様に動作します)。
例:
$ 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
$
シェル(そのすべて)は、そのようなものを扱うのが特に簡単ではないので、(funnily)多くのUnixプログラムは、それらを処理するうまい仕事をしません。 Unixでは、ファイル名(単一コンポーネント)にスラッシュとNUL '\0'
を除く任意の文字を含めることができます。 ただし、シェルはパス名のどこにでもスペースや改行やタブを使用しないことを強く推奨しています。 また、標準のUnixファイル名にスペースなどが含まれていない理由もあります。
スペースやその他の面倒な文字を含んでいる可能性のあるファイル名を扱うときは、非常に注意する必要があります。私はずっと前に、Unixでは標準ではないプログラムが必要であることを発見しました。 私はそれをescape
と呼びます(バージョン1.1は1989-08-23T16:01:45Zと日付が付けられました)。
SCCS制御システムを使用した場合のescape
例を次に示します。 これは、 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 "[email protected]"
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
引数では必要ありませんが、全体的にはこれはescape
を使ったもっと簡単なスクリプトの1つです)。
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"
$
[ Added:そして今、さまざまな"[email protected]"
表記の違いを示すために、もう一つの例があります:
$ cat xx.sh
set -x
al [email protected]
al $*
al "$*"
al "[email protected]"
$ 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
'、 ' arg with space
'の4つのオプションを設定します。
]
うーん、それはかなり長い答えです。おそらく、 言葉遣いが良い言葉です。 リクエストに応じてescape
ソースコードを入手できます(メールアドレスはファーストネームdot gmail.comです)。 al
のソースコードは信じられないほど単純です:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
それで全部です。 これはRobert Gambleが示したtest.sh
スクリプトと同等であり、シェル関数として記述することもできます(しかし、シェル関数は、最初にal
書いたときにBourneシェルのローカルバージョンには存在しませんでした)。
また、 al
をシンプルなシェルスクリプトとして書くこともできます:
[ $# != 0 ] && printf "%s\n" "[email protected]"
引数は渡されないと出力を生成しないように条件付きが必要です。 printf
コマンドは、書式文字列引数だけの空行を生成しますが、Cプログラムは何も出力しません。
すべての引数を表すには"[email protected]"
を使用します。
for var in "[email protected]"
do
echo "$var"
done
これは各引数を繰り返し処理し、別の行に出力します。 $ @は$ *のように振る舞いますが、引用符で囲んだ場合、引数にスペースがあれば正しく分割されます:
sh test.sh 1 2 '3 4'
1
2
3 4
ロバートの答えは正しいことに注意してください。また、 sh
でもうまくいきます。 あなたはそれをさらに移植することができます:
for i in "[email protected]"
次のものと同等です。
for i
つまり、何も必要ありません!
テスト( $
はコマンドプロンプト):
$ set a b "spaces here" d
$ for i; do echo "$i"; done
a
b
spaces here
d
$ for i in "[email protected]"; do echo "$i"; done
a
b
spaces here
d
私はKernighanとPikeのUnix Programming Environmentでこれについて最初に読んだ。
bash
では、これhelp for
文書化するのにhelp for
ます:
for NAME [in WORDS ... ;] do COMMANDS; done
'in WORDS ...;'
が存在しない場合、'in "[email protected]"'
が仮定されます。
配列要素としてアクセスすることもできます。たとえば、すべてを反復処理したくない場合などです
argc=$#
argv=([email protected])
for (( j=0; j<argc; j++ )); do
echo ${argv[j]}
done