読みながらオンラインチュートリアル、次のコードが見つかりました。
#!/bin/bash
# Counting the number of lines in a list of files
# for loop over arguments
# count only those files I am owner of
if [ $# -lt 1 ]
then
echo "Usage: $0 file ..."
exit 1
fi
echo "$0 counts the lines of code"
l=0
n=0
s=0
for f in $*
do
if [ -O $f ] # checks whether file owner is running the script
then
l=`wc -l $f | sed 's/^\([0-9]*\).*$/\1/'`
echo "$f: $l"
n=$[ $n + 1 ]
s=$[ $s + $l ]
else
continue
fi
done
echo "$n files in total, with $s lines in total"
sed
この例では、呼び出しの目的は何ですか?
答え1
例6のコマンドは、sed
出力から行数のみを抽出しますwc -l
。
実行中ですwc -l
($f
引数として渡されたスクリプトが所有するファイル)。通常、次のような出力が生成されます。
$ wc -l .bashrc
17 .bashrc
1列の行数と2列のファイル名。このsed
コマンドは非常に不要な方法で行数だけを取得します。
$ wc -l .bashrc | sed 's/^\([0-9]*\).*$/\1/'
17
このsed
ステートメントは's/^\([0-9]*\).*$/\1/'
次のことを行います。
^
- 行の先頭に一致します。\([0-9]*\)
- 数字を無制限に一致させます(角かっこをエスケープするとキャプチャグループが形成されます)。.*
- 何でも無制限に一致$
- 行末の一致\1
- 最初のキャプチャグループの内容を示します。
デフォルトでは、これは数字で始まるすべての行と一致し、行全体を最初のキャプチャグループ(番号)に置き換えます。
これを推薦してくれたStephen Kittに感謝します。
$ wc -l < .bashrc
17
それ以外の場合は、以下のようにcut
使用する方が良いです。awk
$ wc -l .bashrc | cut -d' ' -f1
17
$ wc -l .bashrc | awk '{print $1}'
17
答え2
このコードの目的sed
は、出力を解析してwc -l
ファイルの行数を抽出することです。
これは通常必要ありません。
l=$( wc -l <"$f" )
同じことをします(あなたはこれを試す必要があります)。
スクリプトは移植可能ではなく、「古い」と見なされるいくつかの構成を使用し、スクリプトには安全でないようにするいくつかの詳細があります。
拡張子を引用する必要があります。たとえば、
if [ $# -lt 1 ]
を書くほうがいいですし、書く必要がif [ "$#" -eq 0 ]
ありif [ -O $f ]
ますif [ -O "$f" ]
。これにより、すべての文字、さらにはその中の文字$IFS
(スペース、タブ、および改行)を含むファイル名をサポートできます。何らかの理由で数字を含める$#
場合は、$IFS
引用符で囲む必要があります。このコンテンツの詳細については、次の3つの質問をご覧ください。bash / POSIXシェルで変数を引用することを忘れてしまうセキュリティリスク「、」スペースやその他の特殊文字が原因でシェルスクリプトが停止するのはなぜですか?「そして」いつ二重引用符が必要ですか?」。
場合によっては、コマンドの置き換えにバックティックを使用するのは面倒です。その行は
l=`wc -l ...`
次のように書き直すことができますl=$(wc -l ...)
。最新の行は入れ子になっており、参照が期待どおりに機能し(たとえば、構文エラーを生成するのと比較して)、読みやすくなるため、より$(...)
良いです。echo "`echo "`echo ok`"`"
echo "$(echo "$(echo ok)")"
これについて詳しくは、「* shシェルではバックティック(「cmd」など)は使用されなくなりましたか?」
$[ $s + $l ]
移植できない文です$(( s + l ))
。printf
変数データはを使用する代わりにを使用して出力する必要がありますecho
。たとえば、最後の行はecho "$n files in total, with $s lines in total"
次のように書き換えることができます。
printf '%d files in total, with %d lines in total\n' "$n" "$s"
たとえば、なぜprintfがechoより優れているのですか?」。
ループを使用して
$*
コマンドライン引数を繰り返すと、スペースを含むファイル名からスクリプトは実行されません。その
continue
ステートメントとelse
そのステートメントの分岐はとにかくループの終わりにあるため、まったく必要ありません。if
診断出力は標準エラーで印刷する必要があります。
スクリプトの「修正済み」バージョン:
#!/bin/bash
# Counting the number of lines in a list of files
# for loop over arguments
# count only those files I am owner of
if [ "$#" -eq 0 ]; then
printf 'Usage: %s file ...\n' "$0" >&2
exit 1
fi
printf '%s counts the lines of code\n' "$0"
l=0; n=0; s=0
for name do
if [ -f "$name" ] && [ -O "$name" ]; then # checks whether its a regular file and file owner is running the script
nlines=$( wc -l <"$name" )
printf '%s: %d\n' "$name" "$nlines"
totlines=$(( totlines + nlines ))
nfiles=$(( nfiles + 1 ))
fi
done
printf '%d files in total, with %s lines in total" "$nfiles" "$totlines"