Bashでファイルのコンテンツをループする


1388

Bashでテキストファイルの各行を反復するにはどうすればよいですか?

このスクリプトでは:

echo "Start!"
for p in (peptides.txt)
do
    echo "${p}"
done

画面に次の出力が表示されます。

Start!
./runPep.sh: line 3: syntax error near unexpected token `('
./runPep.sh: line 3: `for p in (peptides.txt)'

(後で$p、画面に出力するだけではなく、もっと複雑なことを行いたいと思っています。)


環境変数SHELLは(envから):

SHELL=/bin/bash

/bin/bash --version 出力:

GNU bash, version 3.1.17(1)-release (x86_64-suse-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.

cat /proc/version 出力:

Linux version 2.6.18.2-34-default (geeko@buildhost) (gcc version 4.1.2 20061115 (prerelease) (SUSE Linux)) #1 SMP Mon Nov 27 11:46:27 UTC 2006

ファイルpeptides.txtは含んでいる:

RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL

19
ああ、私はここで多くのことが起こっているのを目にします:すべてのコメントが削除され、質問が再開されました。参考までに、値を変数に割り当てる行ごとのファイルの読み取りで受け入れられた答えは、標準的な方法で問題に対処し、ここで受け入れられたものよりも優先される必要があります。
fedorqui 'SO stop harming'

回答:


2096

それを行う1つの方法は次のとおりです。

while read p; do
  echo "$p"
done <peptides.txt

コメントで指摘されているように、これには、先​​頭の空白を削除し、バックスラッシュシーケンスを解釈し、終了する改行がない場合は最後の行をスキップするという副作用があります。これらが懸念される場合は、次のことを実行できます。

while IFS="" read -r p || [ -n "$p" ]
do
  printf '%s\n' "$p"
done < peptides.txt

例外として、ループ本体が標準入力から読み取る場合は、別のファイル記述子を使用してファイルを開くことができます。

while read -u 10 p; do
  ...
done 10<peptides.txt

ここで、10は任意の数です(0、1、2とは異なります)。


7
最後の行をどのように解釈すればよいですか?ファイルpeptides.txtは標準入力にリダイレクトされ、どういうわけかwhileブロック全体にリダイレクトされますか?
Peter Mortensen、

11
「peptide.txtをこのwhileループにスラップするので、「read」コマンドは何かを消費します。」私の "cat"メソッドも同様で、コマンドの出力をwhileブロックに送信して「読み取り」で使用することもできます。それだけで、別のプログラムを起動して作業を完了させます。
ウォーレンヤング

8
このメソッドは、ファイルの最後の行をスキップするようです。
xastor 2013年

5
行を二重引用符で囲みます!! "$ p"とファイルをエコーし​​ます。そうでない場合は、噛んでくれると信じてください!!! 知っている!笑
マイクQ

5
どちらのバージョンも、改行で終了していない場合、最終行の読み取りに失敗します。常に使用while read p || [[ -n $p ]]; do ...
dawg

448
cat peptides.txt | while read line 
do
   # do something with $line here
done

ワンライナーバリアント:

cat peptides.txt | while read line; do something_with_$line_here; done

後続の改行がない場合、これらのオプションはファイルの最終行をスキップします。

これは次の方法で回避できます。

cat peptides.txt | while read line || [[ -n $line ]];
do
   # do something with $line here
done

68
一般に、引数が1つだけの「猫」を使用している場合、何か間違っている(または最適ではない)ことになります。
JesperE 2009年

27
はい、ブルーノほど効率的ではありません。不必要に別のプログラムを起動するからです。効率が重要な場合は、ブルーノの方法で行います。私は自分のやり方を覚えています。「redirect in from」構文が機能しない他のコマンドでも使用できるからです。
ウォーレンヤング

74
これにはさらに深刻な問題があります。whileループはパイプラインの一部であるため、サブシェルで実行されるため、ループ内に設定された変数は、終了時に失われます(bash-hackers.org/wiki/dokuを参照)。 php / mirroring / bashfaq / 024)。これは非常に煩わしいことがあります(ループで何をしようとしているのかによって異なります)。
Gordon Davisson、

25
私は多くのコマンドの最初に純粋に「cat file |」を使用していますが、これは「head file |」でプロトタイプを作成することが多いためです。
mat kelcey 2014

62
これはそれほど効率的ではないかもしれませんが、他の答えよりもはるかに読みやすくなっています。
Savageリーダー

144

オプション1a: whileループ:一度に1行:入力リダイレクト

#!/bin/bash
filename='peptides.txt'
echo Start
while read p; do 
    echo $p
done < $filename

オプション1b: Whileループ:一度に1行:
ファイルを開き、ファイル記述子(この場合はファイル記述子#4)から読み取ります。

#!/bin/bash
filename='peptides.txt'
exec 4<$filename
echo Start
while read -u4 p ; do
    echo $p
done

オプション1bの場合:ファイル記述子を再度閉じる必要がありますか?たとえば、ループは内部ループである可能性があります。
Peter Mortensen

3
ファイル記述子は、プロセスの終了時にクリーンアップされます。明示的なクローズを行って、fd番号を再利用できます。fdを閉じるには、次のように&-構文で別のexecを使用します。exec 4 <&
Stan Graves

1
オプション2をありがとう。ループ内で標準入力から読み取る必要があったため、オプション1で大きな問題に遭遇しました。このような場合、オプション1は機能しません。
masgo 2014年

4
オプション2はお勧めしません。@masgoオプション1bは、その場合には動作するはず、と置き換えることにより、オプション1aから入力リダイレクト構文と組み合わせることができるdone < $filenamedone 4<$filenameあなただけ置き換えることができ、その場合には、コマンドパラメータからファイル名を読みたい場合に便利です(、$filename$1)。
Egor Hans

ループtail -n +2 myfile.txt | grep 'somepattern' | cut -f3内でsshコマンドを実行しながら(stdinを使用)、のようなファイルの内容をループする必要があります。ここでのオプション2が唯一の方法のように見えますか?
user5359531 2018年

85

これは他の回答に勝るものはありませんが、スペースなしでファイル内でジョブを実行するためのもう1つの方法です(コメントを参照)。個別のスクリプトファイルを使用するという追加の手順なしで、テキストファイルのリストを掘り下げるために1行が必要になることがよくあります。

for word in $(cat peptides.txt); do echo $word; done

このフォーマットを使用すると、すべてを1つのコマンドラインに入れることができます。「echo $ word」の部分を任意に変更すると、セミコロンで区切られた複数のコマンドを発行できます。次の例では、ファイルの内容を、作成した他の2つのスクリプトへの引数として使用します。

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done

または、これをストリームエディタ(sedを学ぶ)のように使用する場合は、次のように出力を別のファイルにダンプできます。

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done > outfile.txt

1行に1ワードのテキストファイルを作成したので、上記のように使用しました。(コメントを参照)単語/行を分割したくないスペースがある場合、少し醜くなりますが、同じコマンドは次のように機能します。

OLDIFS=$IFS; IFS=$'\n'; for line in $(cat peptides.txt); do cmd_a.sh $line; cmd_b.py $line; done > outfile.txt; IFS=$OLDIFS

これは、スペースではなく改行のみで分割するようにシェルに指示し、環境を以前の状態に戻します。ただし、この時点では、すべてを1行にまとめるのではなく、すべてシェルスクリプトに入れることを検討してください。

がんばって!


6
bash $(<peptides.txt)はおそらくよりエレガントですが、それでも間違っています。Joaoが言ったとおり、スペースまたは改行が同じであるコマンド置換ロジックを実行しています。行にスペースがある場合、ループはその1行に対して2回以上実行します。したがって、コードは正しく読み取る必要があります:$(<peptides.txt); 行う....スペースがないことがわかっている場合、行は単語に等しく、問題ありません。
maxpolk 2013

2
@ JoaoCosta、maxpolk:私が考慮しなかった良い点。元の投稿を編集して反映させました。ありがとう!
mightypile 2013

2
を使用forすると、入力トークン/行がシェル展開の対象になります。これは通常は望ましくありません。これを試してみfor l in $(echo '* b c'); do echo "[$l]"; doneてください。-ご覧のように、*-元は引用されたリテラルですが-現在のディレクトリ内のファイルに展開されます。
mklement0 2013

2
@dblanchard:$ IFSを使用する最後の例では、スペースを無視する必要があります。そのバージョンを試しましたか?
mightypile 2015年

4
重要な問題が修正されたときにこのコマンドがはるかに複雑になる方法は、forファイル行を反復するためにを使用することが悪い考えである理由を非常によく示しています。さらに、@ mklement0で言及されている拡張の側面(エスケープされた引用符を組み込むことで回避できる可能性がありますが、これにより、物事がより複雑で読みにくくなります)。
Egor Hans

69

他の回答でカバーされていないいくつかの事柄:

区切りファイルからの読み取り

# ':' is the delimiter here, and there are three fields on each line in the file
# IFS set below is restricted to the context of `read`, it doesn't affect any other code
while IFS=: read -r field1 field2 field3; do
  # process the fields
  # if the line has less than three fields, the missing fields will be set to an empty string
  # if the line has more than three fields, `field3` will get all the values, including the third field plus the delimiter(s)
done < input.txt

プロセス置換を使用して、別のコマンドの出力から読み取る

while read -r line; do
  # process the line
done < <(command ...)

このアプローチはcommand ... | while read -r line; do ...、ここでのwhileループが後者の場合のようにサブシェルではなく現在のシェルで実行されるためよりも優れています。関連する投稿を参照するwhileループ内で変更された変数は記憶されない

たとえば、ヌル区切りの入力からの読み取り find ... -print0

while read -r -d '' line; do
  # logic
  # use a second 'read ... <<< "$line"' if we need to tokenize the line
done < <(find /path/to/dir -print0)

関連記事: 記事 BashFAQ / 020-改行、スペース、またはその両方を含むファイル名を見つけて安全に処理するにはどうすればよいですか?

一度に複数のファイルから読み取る

while read -u 3 -r line1 && read -u 4 -r line2; do
  # process the lines
  # note that the loop will end when we reach EOF on either of the files, because of the `&&`
done 3< input1.txt 4< input2.txt

基づいて、@のchepnerの答えはここに

-ubashの拡張機能です。POSIX互換性のために、各呼び出しは次のようになります。read -r X <&3ます。

ファイル全体を配列に読み込む(Bashの以前のバージョンから4)

while read -r line; do
    my_array+=("$line")
done < my_file

ファイルが不完全な行で終わっている場合(末尾に改行がない場合)、次のようになります。

while read -r line || [[ $line ]]; do
    my_array+=("$line")
done < my_file

ファイル全体を配列に読み込む(Bashバージョン4x以降)

readarray -t my_array < my_file

または

mapfile -t my_array < my_file

その後

for line in "${my_array[@]}"; do
  # process the lines
done

関連記事:


ノートの代わりにすることをcommand < input_filename.txtあなたは常に行うことができますinput_generating_command | commandcommand < <(input_generating_command)
masterxilo

1
ファイルを配列に読み込んでいただきありがとうございます。私は、二回の解析新しい変数に追加し、いくつかの検証などを行うには、各ラインを必要とするので、私は、必要な正確に何
frank_108

45

次のように、whileループを使用します。

while IFS= read -r line; do
   echo "$line"
done <file

ノート:

  1. IFS適切に設定しないと、インデントが失われます。

  2. ほとんどの場合、-rオプションをreadとともに使用する必要があります。

  3. で行を読みません for


2
なぜ-rオプションなのか?
David

2
@ DavidC.Rankin -rオプションは、バックスラッシュの解釈を防ぎます。Note #2それが詳細に記載されているリンクは...ある
Jahid

これを別の回答の「read -u」オプションと組み合わせると、完璧です。
Florin Andrei

@FlorinAndrei:上記の例は-uオプションを必要としません-u。別の例について話しますか?
Jahid

リンクに目を通し、注2のリンクにリンクするだけの答えはないことに驚いた。そのページには、その主題について知っておくべきすべての情報が含まれている。または、リンクのみの回答は推奨されませんか?
Egor Hans

14

次のファイルがあるとします。

$ cat /tmp/test.txt
Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR

多くのBashソリューションによって読み取られるファイル出力の意味を変更する4つの要素があります。

  1. 空白行4;
  2. 2行の先頭または末尾のスペース。
  3. 個々の行の意味を維持する(つまり、各行はレコードです)。
  4. 行6はCRで終了していません。

空白行とCRなしの終了行を含む行ごとにテキストファイルが必要な場合は、whileループを使用し、最終行の代替テストを行う必要があります。

以下は、ファイルを変更する可能性のあるメソッドです(何catが返されるかと比較して)。

1)最後の行と先頭と末尾のスペースを失う:

$ while read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'

while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt代わりに行う場合、先頭と末尾のスペースは保持されますが、CRで終了していない場合は最後の行が失われます)

2)catwillでプロセス置換を使用すると、ファイル全体が一気に読み取られ、個々の行の意味が失われます。

$ for p in "$(cat /tmp/test.txt)"; do printf "%s\n" "'$p'"; done
'Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR'

"から$(cat /tmp/test.txt)ファイルを削除した場合、ファイルを一口ずつではなく、単語ごとに読み取ります。また、意図したものではないかもしれません...)


ファイルを1行ずつ読み取り、すべての間隔を維持する最も堅牢で最も簡単な方法は次のとおりです。

$ while IFS= read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'    Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space    '
'Line 6 has no ending CR'

リーディングスペースとトレーディングスペースを削除する場合は、IFS=パーツを削除します。

$ while read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
'Line 6 has no ending CR'

(終端なしのテキストファイルは\n、かなり一般的な一方で、POSIXの下で壊れたと考えられている。あなたは、末尾に数えることができるならば\n、あなたが必要としない|| [[ -n $line ]]whileループで。)

BASH FAQの詳細


13

改行文字で改行されないようにするには、次を使用します-

#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "$line"
done < "$1"

次に、ファイル名をパラメーターとしてスクリプトを実行します。


4
#!/bin/bash
#
# Change the file name from "test" to desired input file 
# (The comments in bash are prefixed with #'s)
for x in $(cat test.txt)
do
    echo $x
done

7
この回答には、mightypileの回答に記載されている警告が必要です。シェルのメタ文字が含まれている行があると(引用符で囲まれていない "$ x"により)、失敗する可能性があります。
Toby Speight 2015年

7
私は実際に人々がまだ通常の行を読まなかったのに驚きました ...
Egor Hans

3

これは、別のプログラム出力の行をループし、部分文字列をチェックし、変数から二重引用符を削除し、ループの外でその変数を使用する方法の実際の例です。かなり多くの人が遅かれ早かれこれらの質問をしていると思います。

##Parse FPS from first video stream, drop quotes from fps variable
## streams.stream.0.codec_type="video"
## streams.stream.0.r_frame_rate="24000/1001"
## streams.stream.0.avg_frame_rate="24000/1001"
FPS=unknown
while read -r line; do
  if [[ $FPS == "unknown" ]] && [[ $line == *".codec_type=\"video\""* ]]; then
    echo ParseFPS $line
    FPS=parse
  fi
  if [[ $FPS == "parse" ]] && [[ $line == *".r_frame_rate="* ]]; then
    echo ParseFPS $line
    FPS=${line##*=}
    FPS="${FPS%\"}"
    FPS="${FPS#\"}"
  fi
done <<< "$(ffprobe -v quiet -print_format flat -show_format -show_streams -i "$input")"
if [ "$FPS" == "unknown" ] || [ "$FPS" == "parse" ]; then 
  echo ParseFPS Unknown frame rate
fi
echo Found $FPS

ループ外で変数を宣言し、値を設定してループ外で使用するには、完了した<<< "$(...)"が必要です構文が必要です。アプリケーションは、現在のコンソールのコンテキスト内で実行する必要があります。コマンドを囲む引用符は、出力ストリームの改行を保持します。

部分文字列のループマッチは、名前=値のペアを読み取り、最後=文字の右側の部分を分割し、最初の引用符を削除し、最後の引用符を削除します。他の場所で使用するクリーンな値があります。


3
答えは正しいですが、私はそれがここでどのように終わったかを理解しています。基本的な方法は、他の多くの回答で提案されている方法と同じです。さらに、FPSの例では完全に溺れています。
Egor Hans

0

これはかなり遅いですが、誰かを助けるかもしれないと考えて、私は答えを追加しています。また、これは最善の方法ではない可能性があります。headコマンドを-n引数とともに使用して、ファイルの先頭からn行を読み取るtailことができます。同様に、コマンドを使用して下から読み取ることができます。さて、フェッチするn番目のファイルからの行を、私たちは頭n行を、テールパイプにデータをのみ1つのラインパイプで連結されたデータから。

   TOTAL_LINES=`wc -l $USER_FILE | cut -d " " -f1 `
   echo $TOTAL_LINES       # To validate total lines in the file

   for (( i=1 ; i <= $TOTAL_LINES; i++ ))
   do
      LINE=`head -n$i $USER_FILE | tail -n1`
      echo $LINE
   done

1
これを行わないでください。行番号をループして個々の行をsedorまたはhead+でフェッチすることtail非常に非効率的であり、もちろん、ここで他のソリューションの1つを単に使用しない理由について疑問を投げかけます。行番号を知る必要がある場合は、while read -rループにカウンターを追加するか、を使用nl -baしてループの前に各行に行番号のプレフィックスを追加します。
3

-1

@ピーター:これはあなたのためにうまくいくかもしれません-

echo "Start!";for p in $(cat ./pep); do
echo $p
done

これは出力を返します

Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL


3
この答えは、上記の良い答えによって設定されたすべての原則を打ち負かしています!
codeforester 2017年

3
この回答を削除してください。
dawg

3
さあ、大げさにならないで。答えは悪いですが、少なくとも単純なユースケースでは、うまくいくようです。それが提供されている限り、悪い答えであっても、存在する答えの権利を奪うことはありません。
エゴールハンス

3
@EgorHans、私は強く同意しません。答えのポイントは、ソフトウェアの書き方を人々に教えることです。あなたが知っている方法で人々に何かをするように教えることは彼らに有害であり、彼らのソフトウェアを使用する人々(バグ/予期しない動作などを導入する)は他の人に故意に害を与えています。有害であることがわかっている回答には、適切に管理された教育リソースに「存在する権利」がありません(そして、それをキュレーションすることは、私たち、つまり投票してフラグを立てている人々がここで行うことになっていることです)。
チャールズダフィー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.