web-dev-qa-db-ja.com

Bashのファイルの内容をループする

テキストファイルの各行を 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

Peptide.txtファイルには、次のものが含まれています。

RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
1126
Peter Mortensen

それをする一つの方法は:

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とは異なります)。

1786
Bruno De Fraine
cat peptides.txt | while read line
do
   # do something with $line here
done
345
Warren Young

オプション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

オプション2: forループ:ファイルを単一の変数に読み込み、解析します。
この構文は、トークン間の空白に基づいて「行」を解析します。与えられた入力ファイルの行がシングルワードトークンであるため、これはまだうまくいきます。 1行に複数のトークンがあると、この方法は機能しません。また、ファイル全体を単一の変数に読み込むことは、大きなファイルには適していません。

#!/bin/bash
filename='peptides.txt'
filelines=`cat $filename`
echo Start
for line in $filelines ; do
    echo $line
done
130
Stan Graves

これは他の答えよりも優れているわけではありませんが、スペースなしのファイルでジョブを完成させるもう1つの方法です(コメントを参照)。別のスクリプトファイルを使用するという余分な手順を踏まずに、テキストファイル内のリストを掘り下げるためのワンライナーが必要になることがよくあります。

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

このフォーマットは私がそれを一つのコマンドラインに入れることを可能にします。 「echo $ Word」の部分を必要なものに変更すると、セミコロンで区切って複数のコマンドを発行できます。次の例では、ファイルの内容を他の2つのスクリプトへの引数として使用します。

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

あるいは、これをストリームエディタのように使うつもりなら(learn sed)、次のように出力を別のファイルにダンプすることができます。

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

1行に1つのWordで作成したテキストファイルを使用したので、上記のように使用しました。あなたがあなたの単語/行を分割したくないスペースがあるならば、それは少しより上品になります、しかし同じコマンドはまだ以下のように働きます:

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行にまとめるのではなく、すべてをShellスクリプトにまとめることを検討してください。

頑張って!

69
mightypile

他の答えではカバーされていないことがいくつかあります。

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

# ':' 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 ...)

ここでのwhileループは、後者の場合のようにサブシェルではなく現在のシェルで実行されるため、このアプローチはcommand ... | while read -r line; do ...よりも優れています。関連する記事を参照してください 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の answer に基づいて

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

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

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

関連記事:

56
codeforester

次のようにwhileループを使います。

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

ノート:

  1. IFSを正しく設定しないと、字下げを失います。

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

  3. forで行を読み取らない

42
Jahid

改行文字で読みが壊れたくない場合は、 - を使用してください。

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

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

13
Anjul Sharma

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

$ 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を含まない終了行を含むテキストファイルを1行ずつ表示したい場合は、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)catでプロセス置換を使用すると、ファイル全体が1回の読み取りで読み込まれ、個々の行の意味が失われます。

$ 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つの単語ではなくWord単位でファイルを読むことになります。意図したものではないかもしれません...)


ファイルを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を信頼できる場合は、whileループで|| [[ -n $line ]]は必要ありません。)

BASH FAQ にもっと

12
dawg
#!/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
4
Sine

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

##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

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

次に部分文字列のループマッチは name = value pairを読み取り、最後の = 文字の右側部分を分割し、最初の引用符を削除し、最後の引用符を削除します。

3
Whome

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

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

これは出力を返します

Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
1
Alan Jebakumar

これはかなり遅いですが、誰かを助けるかもしれないという考えで、答えを追加しています。また、これは最良の方法ではないかもしれません。 headコマンドは、-n引数とともに使用して、n行をファイルの先頭から読み取り、同様に、tailコマンドを使用して下から読み取ることができます。ここで、ファイルからnth行を取得するために、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
0
madD7