BashのCスタイルforループでの「while IFS = read -r line」シミュレーション

BashのCスタイルforループでの「while IFS = read -r line」シミュレーション

まず、この問題の背景を紹介します。while IFS= read -r line; do ... done < input.txtよく知られている構造です。ファイルを1行ずつ読み込むシェルスクリプトから。しかし、Cスタイルのforループで作業している間(ここでいくつかのユーザーが知らない場合やfor((i=0;i<=$val;i++))do;...done使用されているタイプです)、Cと同様の言語のwhileとforループは交換可能であり、他のループをシミュレートできることを覚えていました。 。それで、上記の構造をシミュレートするためにCスタイルのループを考えました。bashkshwhile 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 (( ; ; ))シェルは算術式のみを評価するので、これを行う必要がありますreadforwhileCでは簡単に変換できますが、同じ状況がシェルには適用されません。

(Cでもforループの構造は計算ループを意味すると言いたいのですが、もちろん完全には明確ではありません。)

これに関しては:

技術的には、i変数を含む「組み込み」行カウンタもあります。

私はそれに組み込まれたものは何もないと思います。行カウンタを手動で初期化し、手動でインクリメントしました。より伝統的なループを使用してwhile同じことを行うことができます。

i=0
while IFS= read -r line; do 
    ... 
    let i++
done < input.txt

(またはi=$((i + 1))より携帯可能)

答え2

@ilkkachuはあなたの言葉に完全に同意します。

readただし、FWIW、そのコマンドを条件(構文のソース)の一部として使用するには、次の規則を使用できます。forksh93for ((...))

function read.get {
  IFS= read -r line
  .sh.value=$(($? == 0))
}

for ((i = 0; read; i++)) {
  printf '%5d: %s\n' "$i" "$line"
}

コマンドが拡張時に実行され、成功または失敗時に1に拡張されるように変数のgetルールを設定しました。$readread$readread0

または、次のバリエーションを使用してください。タイプ:

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.valueread

または${ ...; }コマンド代替形式:

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)。

関連情報