練習中です。シェルスクリプトディレクトリをパラメータとして使用し、ディレクトリ内の各ファイルを繰り返して名前とサイズを出力する単純なスクリプトを作成しようとしています。
#!/bin/bash
# A practice shell script to try and display a list of file names
# and their sizes using the output of ls -l and cut.
declare -i index
export index=0
export name=""
export size=0
for file in $1 ; do
index+=1
name=`basename $file`
size=`ls -l $file | cut -d " " -f 5`
echo "$index: $name, size: $size bytes"
done
パラメータとして指定すると、./*
ファイルに対してこれを行い、それがすべてです。ただし、上記のコードを編集して./*
場所に配置すると、$1
現在のディレクトリ内のすべてのファイルが機能して繰り返されます。
$1
と同じでなければならないのになぜ同じことをしないのでしょうか./*
?
答え1
その理由は、スクリプトを呼び出すシェルがワイルドカードパターンを拡張するためです。./*
今後スクリプトに渡されます。つまり、ワイルドカードパターンが一致すると、file1.txt
スクリプトは次のようにfile4.txt
呼び出されます。
./my_script.sh ./*
実際には、次のように解釈されます。
./my_script.sh file1.txt file2.txt file3.txt file4.txt
これはシェルスクリプトに表示されるパラメータです。
詳細については、以下を確認してください。シェル拡張順序に関する部分Bashリファレンスマニュアルにあります。
この問題を解決する方法は2つあります。
- 特定のディレクトリ内のすべてのファイルを常に繰り返したい場合は、そのディレクトリをパラメータとして渡してから繰り返します。
for f in "$1"/* do # operations on "$f" done
- または、作業したいファイル名だけを渡すことが確実な場合は、次のように引数の完全なリストを繰り返します。
for f in "$@" do # operations on "$f" done
確かに興味深い練習であるスクリプトにglobパターンを渡すことでこれを行うことができます(@ilkkachuの説明を参照)。 @fra-sanがコメントで述べたように、このアプローチには利点があります。スクリプトの使用に柔軟性を追加し、シェルのコマンドライン引数の制限をバイパスします(「引数リストが長すぎます」を参照)。 RAMはまだ長さを制限しますが、生成されたファイル名のリスト) - しかし、非常に注意が必要です。
- パラメータを引用符(一重引用符または二重引用符)で囲むか、バックスラッシュでglob文字をエスケープしてシェルがglobを拡張するのを防ぐことができます。
./my_script.sh "./*" ./my_script.sh './*' ./my_script.sh ./\*
- スクリプト内の位置パラメータを参照します。
$1
引用しないこのようにして実際にシェルによって解釈されます(私たちがしばしば避けたいもの)。 - 「解釈」には拡張(上記参照)だけでなく単語分割も含まれるため入力フィールド区切り記号
IFS
トークン化が発生しないように空の文字列に置き換えます。 - ループは
for
次のとおりですIFS= for f in $1 do # Operations on "$f" done
スクリプトに関するいくつかの一般的な注意:
- 常にシェル変数を引用します。特にファイル名が含まれている場合、そうでなければスクリプトはスペースやその他のはるかにエキゾチックな文字を含むファイル名を見つけます。ファイル名には改行文字も許可されることを覚えておいてください!
ls
次の出力を解析します。非常にイライラ同様の理由で。特定のファイルの属性を識別したい場合は、このstat
ツールがより良い選択です。たとえば、ファイルサイズを確認するには、次のようにします。size=$(stat --printf="%s" "$f")
- これは尊敬される
$( ... )
コマンドの置き換えには、古い「バックティックスタイル」の代わりに「新しい」スタイルを使用してください` ... `
。 - シェルスクリプトを確認することをお勧めします
shellcheck
また、これらの(および他の)可能なエラーソースから保護するために、多くのLinuxディストリビューションでスタンドアロンツールとしても使用できます。
答え2
Bashがワイルドカードとパラメータを解釈する方法を理解する必要があります。ワイルドカードがある場合、bashはそれをすぐに解釈し、一致するすべてのファイルに置き換えます。 $1を./*に置き換えると、これが起こります。現在のディレクトリからすべてのファイルをインポートして繰り返します。
あなたが持っているとき
for file in $1 ; do
最初のパラメータのみが必要です。それはあなたが望むものではありません。すべてのファイルを繰り返すには、以下を使用する必要があります。
for file in $* ; do
(これはすべてのパラメータを受け入れます)
または - を使用して繰り返すことができます。これにより、shift
他の引数があるたびに最初の引数が削除されます。
while [ $# -gt 0 ]
do
file=$1
shift
...
done