最初の質問のちょっとした文脈。 _while IFS= read -r line; do ... done < input.txt
_は、シェルスクリプトで ファイルを1行ずつ読み取る のよく知られた構造です。しかし、cスタイルのforループを使用してきました(ここのユーザーの一部が知らない場合は、bash
とksh
で使用されるfor((i=0;i<=$val;i++))do;...done
タイプです)、 Cのような言語のwhileループとforループは交換可能であり、相互にエミュレートできることを思い出しました。そこで、上記の_while IFS= read -r line
_構造をエミュレートするcスタイルのループを思いつきました。
_for((i=1;;i++))
do
IFS= read -r line || break
# do something with line here
done < input.txt
_
複数のタイプの入力でテストしました-通常の行、先頭のタブ/スペースのある行、_\n
_で終わらない行(これはread
がキャッチできない場合です最後の行)-そしてすべての場合において、これはwhile
ループアプローチとまったく同じように機能します。これには、技術的にはi
変数を持つ「組み込み」ラインカウンターもあります。
したがって、問題はこれです:このアプローチの使用を避ける理由はありますか(ksh
とbash
以外の他のシェルへの非移植性以外に)?これが失敗する可能性がある場合はありますか?これはうまく機能しますが、自分のスクリプトでこのアプローチを積極的に使用し始める前に、見落としている問題があるかどうかを知りたいと思います。
このアプローチの使用を避ける理由はありますか?
明快さ、またはそれの欠如。
_while sometest ; do ...
_のようなwhile
ループは次のように書くこともできます
_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))
の移植性を高める)
私はここで@ilkkachuに完全に同意します。
しかし、FWIWは、そのread
コマンドをfor
条件の一部として、_ksh93
_(for ((...))
構文の由来)とともに使用できるようにするために、分野を使用できます:
_function read.get {
IFS= read -r line
.sh.value=$(($? == 0))
}
for ((i = 0; read; i++)) {
printf '%5d: %s\n' "$i" "$line"
}
_
_$read
_変数のget
規律を設定して、展開時にread
コマンドが実行され、read
の場合は_$read
_が1に展開されるようにします。成功したか、それ以外の場合は_0
_。
またはtypesを使用するバリアント:
_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}"
}
_
ここで、_read_t
_タイプは、展開時に行を_theobject.value
_に読み取り、read
が成功した場合は1に展開し、それ以外の場合は0に展開するオブジェクトの一種です。
または、コマンド置換の_${ ...; }
_形式:
_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"
}
_
(私はそれをすることをお勧めしません)。
これが、サブシェルの算術式notの一部としてコマンドを実行できる場所を私が知っている唯一の方法です。
通常のコマンド置換($(...)
または_`...`
_)を使用して、算術式でコマンドを実行することもできますが、サブシェルで実行されるため、次のようになります。
_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}
_無条件割り当てパラメーター展開演算子がなく、パラメーター展開をネストする機能は非常に制限されています。 _${var=value}
_ Bourne演算子(以前に設定されていない場合は割り当て)があり、_${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
_
(ここでは回避する必要があります bash
の2つのバグ )。