まず、この問題の背景を紹介します。while IFS= read -r line; do ... done < input.txt
よく知られている構造です。ファイルを1行ずつ読み込むシェルスクリプトから。しかし、Cスタイルのforループで作業している間(ここでいくつかのユーザーが知らない場合やfor((i=0;i<=$val;i++))do;...done
使用されているタイプです)、Cと同様の言語のwhileとforループは交換可能であり、他のループをシミュレートできることを覚えていました。 。それで、上記の構造をシミュレートするためにCスタイルのループを考えました。bash
ksh
while IFS= read -r line
for((i=1;;i++))
do
IFS= read -r line || break
# do something with line here
done < input.txt
\n
さまざまな種類の入力(通常の行、前のタブ/スペースのある行、終了しない行(最後の行をキャプチャできない場合))でテストしてみましたが、すべての場合に機能します。どちらも完全に有効でread
ループ方式と同じですwhile
。技術的には、変数には「組み込み」行カウンタもありますi
。
したがって、質問にこのアプローチを使用しない理由はありますか(PythonとPythonを除く他のシェルに移植できないことに加えてksh
)?bash
可能な失敗はありますか?このアプローチはうまく機能しますが、私のスクリプトでこのアプローチを積極的に使用する前に私が見落としている問題があるかどうかを知りたいです。
答え1
このアプローチを使用しない理由はありますか?
明確さまたは不足。
while
このようなループは次のようにwhile sometest ; do ...
書くこともできます。
while :; do
if ! sometest; then
break
fi
...
しかし、私たちは(シェルやCで)これをしません。なぜなら、ループ条件を人々が見つけるのに慣れている場所から遠ざかるからです。構成は似ています。構成の中間式を空白のままにしますfor (( ; ; ))
。
もちろん、for (( ; ; ))
シェルは算術式のみを評価するので、これを行う必要がありますread
。for
とwhile
Cでは簡単に変換できますが、同じ状況がシェルには適用されません。
(Cでもfor
ループの構造は計算ループを意味すると言いたいのですが、もちろん完全には明確ではありません。)
これに関しては:
技術的には、i変数を含む「組み込み」行カウンタもあります。
私はそれに組み込まれたものは何もないと思います。行カウンタを手動で初期化し、手動でインクリメントしました。より伝統的なループを使用してwhile
同じことを行うことができます。
i=0
while IFS= read -r line; do
...
let i++
done < input.txt
(またはi=$((i + 1))
より携帯可能)
答え2
@ilkkachuはあなたの言葉に完全に同意します。
read
ただし、FWIW、そのコマンドを条件(構文のソース)の一部として使用するには、次の規則を使用できます。for
ksh93
for ((...))
function read.get {
IFS= read -r line
.sh.value=$(($? == 0))
}
for ((i = 0; read; i++)) {
printf '%5d: %s\n' "$i" "$line"
}
コマンドが拡張時に実行され、成功または失敗時に1に拡張されるように変数のget
ルールを設定しました。$read
read
$read
read
0
または、次のバリエーションを使用してください。タイプ:
typeset -T read_t=(
typeset value
function get {
IFS= read -r _.value
((.sh.value = $? == 0))
}
)
read_t line
for ((i = 0; line; i++)) {
printf '%5d: %s\n' "$i" "${line.value}"
}
ここで、type は拡張時に行として読み取られ、成功すれば 1 に拡張され、そうでなければ 0 に拡張されるread_t
オブジェクトです。theobject.value
read
または${ ...; }
コマンド代替形式:
for ((i = 0; ${ IFS= read -r line; echo "$(($? == 0))";}; i++)) {
printf '%5d: %s\n' "$i" "$line"
}
とともにzsh
、拉致する動的に名前が付けられたディレクトリ特徴:
set -o extendedglob
handle_-read:var()
case $1:$2 in
(d:-read:[a-zA-Z_][a-zA-Z0-9_]#)
IFS= read -r ${2#*:} && reply=('' $#2);;
(*) false;;
esac
zsh_directory_name_functions+=(handle_-read:var)
for ((i = 0; ${#${(D):--read:line}} == 3; i++)) {
printf '%5d: %s\n' "$i" "$line"
}
(私はこれをお勧めしません)。
これが算術式の一部としてコマンドを実行できる唯一の方法です。いいえサブシェルから。
一般的なコマンド置換($(...)
または`...`
)を使用して算術式内でコマンドを実行することもできますが、これはサブシェルで実行されるため、次のようになります。
for ((i = 0; $(IFS= read -r line; echo "$((!$?))"); i++)) {
printf '%5d: %s\n' "$i" "$line"
}
これは有効な構文ですが、サブシェルの外部で変数を設定するbash
ことはできません。$line
ただし、 を使用すると、zsh
次のことができます。
for ((i = 0; ${${line::=$(IFS= read -re)}+$?} == 0; i++)) {
printf '%5d: %s\n' "$i" "$line"
}
しかし、これは各ラインに対してサブシェルを分岐し、追加のパイプを介してラインを供給するので、非効率的である。
bash
無条件に割り当てられたパラメータ拡張演算子はなく、ネストされた${var::=value}
パラメータ拡張機能は非常に制限されています。これにはBourne演算子(以前に設定されていない場合は値の割り当て)があり、入れ子にするための${var=value}
いくつかの演算子があります。${foo#${bar}}
たとえば、次のようにできます。
unset line
for ((i = 0; 0*${?#"x${line=`IFS= read -r && printf %s "$REPLY"`}"}+$? == 0; i++)); do
printf '%5d: %s\n' "$i" "$line"
unset line
done
(ここで解決する必要があります2つのエラーbash
)。