BashでファイルまたはSTDINから読み取る方法は?


244

次のPerlスクリプト(my.pl)は、コマンドライン引数のファイルまたはSTDINから読み取ることができます。

while (<>) {
   print($_);
}

perl my.pl一方で、STDINから読み込みますperl my.pl a.txtから読み込みますa.txt。これはとても便利です。

Bashに相当するものはありますか?

回答:


409

次の解決策は、スクリプトが最初のパラメーターとしてファイル名を指定して呼び出された場合はファイルから読み取り、$1そうでない場合は標準入力から読み取ります。

while read line
do
  echo "$line"
done < "${1:-/dev/stdin}"

定義されている場合、置換${1:-...}が行わ$1れます。それ以外の場合、独自のプロセスの標準入力のファイル名が使用されます。


1
うまくいきました。もう1つの質問は、なぜ見積もりを追加するのかということです。「$ {1:-/ proc / $ {$} / fd / 0}」
Dagang

15
コマンドラインで指定するファイル名には空白が含まれる場合があります。
Fritz G. Mehner、2011

3
使用の間に何らかの違いがある/proc/$$/fd/0とは/dev/stdin?後者の方が一般的であり、より簡単に見えます。
ノワ

19
コマンドに追加-rしてread、誤って\ 文字を食べないようにしてください。while IFS= read -r line先頭と末尾の空白を保持するために使用します。
mklement0 2015

1
@NeDark:それは不思議です。使用している場合でも、そのプラットフォームで動作することを確認しました/bin/sh- bashまたは以外のシェルを使用していますかsh
mklement0

119

おそらく最も簡単な解決策は、マージするリダイレクト演算子を使用してstdinをリダイレクトすることです。

#!/bin/bash
less <&0

stdinはファイル記述子ゼロです。上記は、bashスクリプトにパイプされた入力をlessのstdinに送信します。

ファイル記述子のリダイレクトの詳細をご覧ください


1
私はあなたにもっと多くの賛成票を与えたいと思っています。これを何年も探していました。
マーカスダウニング2014年

13
<&0この状況で使用してもメリットはありません-例はそれがあってもなくても同じように動作します-おそらく、bashスクリプト内から呼び出すツールは、デフォルトでスクリプト自体と同じstdinを参照します(スクリプトが最初にそれを消費する場合を除く)。
mklement0 2015

@ mkelement0では、ツールが入力バッファの半分を読み取った場合、次に呼び出すツールが残りを取得しますか?
Asad Saeeduddin

「ファイル名がありません(ヘルプの "less --help")」これを実行すると... Ubuntu 16.04
OmarOthman

5
この回答の「またはファイルから」の部分はどこですか?
セバスチャン

84

最も簡単な方法は次のとおりです。

#!/bin/sh
cat -

使用法:

$ echo test | sh my_script.sh
test

stdinを変数に割り当てるには、次を使用できます。STDIN=$(cat -)または単にSTDIN=$(cat)演算子が必要ない場合(@ mklement0のコメントに従って)。


標準入力から各行を解析するには、次のスクリプトを試してください。

#!/bin/bash
while IFS= read -r line; do
  printf '%s\n' "$line"
done

ファイルまたはstdin(引数が存在しない場合)から読み取るには、次のように拡張できます。

#!/bin/bash
file=${1--} # POSIX-compliant; ${1:--} can be used either.
while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")

ノート:

-- read -rバックスラッシュ文字を特別な方法で処理しないでください。各バックスラッシュを入力行の一部と見なします。

-設定せずIFS、デフォルトでのシーケンスSpaceTab行の先頭と末尾には(トリミング)は無視されます。

-使用printfの代わりにecho線が単一で構成されている場合、印刷空白行を避けるために-e-nまたは-E。ただし、それをサポートenv POSIXLY_CORRECT=1 echo "$line"する外部 GNU echoを実行するを使用することによる回避策があります。参照:「-e」をエコーするにはどうすればよいですか?

参照:引数が渡されないときにstdinを読み取る方法は?Stackoverflow SEで


あなたは単純化でき[ "$1" ] && FILE=$1 || FILE="-"FILE=${1:--}。(Quibble:環境変数との名前の衝突を回避するためにすべて大文字のシェル変数を回避する方が良い。)
mklement0

どういたしまして; 実際に${1:--} は、 POSIXに準拠しているため、すべてのPOSIXのようなシェルで動作するはずです。このようなすべてのシェルで機能しないのは、プロセス置換(<(...))です。たとえば、bash、ksh、zshでは機能しますが、ダッシュでは機能しません。また、誤って文字を食べないよう-rに、readコマンドに追加することをお\ 勧めします。IFS= 先頭と末尾の空白を保持するために付加します。
mklement0 2015

4
実際には、あなたのコードはまだブレークのためecho:ラインはで構成されている場合-e-nまたは-E、それは表示されません。これを修正するには、使用する必要がありますprintfprintf '%s\n' "$line"。以前の編集には含めていませんでした:(。このエラーを修正すると、編集内容がロールバックされることがよくあります。
gniourf_gniourf 2015年

1
いいえ、失敗しません。そして、--最初の引数が'%s\n'
gniourf_gniourf

1
あなたの答えは私には問題ありません(つまり、私が知っているバグや不要な機能はもうないということです)。ただし、Perlのように複数の引数を処理することはありません。実際には、あなたが複数の引数を処理したい場合は、あなたが使用したいので、良いだろうジョナサン・レフラーの優れた実際の答え-であなたを書き終わるだろうIFS=readし、printf代わりにecho:)
gniourf_gniourf 2015年

19

これは簡単な方法だと思います:

$ cat reader.sh
#!/bin/bash
while read line; do
  echo "reading: ${line}"
done < /dev/stdin

-

$ cat writer.sh
#!/bin/bash
for i in {0..5}; do
  echo "line ${i}"
done

-

$ ./writer.sh | ./reader.sh
reading: line 0
reading: line 1
reading: line 2
reading: line 3
reading: line 4
reading: line 5

4
これは、投稿者による標準入力またはファイル引数のいずれかからの読み取りの要件に適合しません。これは、標準入力から読み取るだけです。
2014

2
さておきナッシュの有効な異議@ままにしておくと:read標準入力から読み込み、デフォルトではそうありません、不要のため< /dev/stdin
mklement0 2015

13

echoソリューションは、いつでも新しい行を追加してIFS入力ストリームを破ります。@fgmの回答は少し変更できます。

cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"

「IFSが入力ストリームを中断するたびにエコーソリューションが新しい行を追加する」とはどういう意味ですか?あなたがreadの振る舞いを参照していた場合:read 潜在的に文字によって複数のトークンに分割されます。に含まれている場合、単一の変数名のみを指定した場合にのみ単一のトークン$IFSが返されます(ただし、デフォルトでは先頭と末尾の空白が削除されます)。
mklement0 2015

@ mklement0 readおよびの動作について100%同意します$IFS- echoそれ自体が-nフラグなしで新しい行を追加します。"エコーユーティリティは、単一の空白(` ')文字で区切られ、その後に改行( `\ n')文字が続く指定されたオペランドを標準出力に書き込みます。"
デビッドサウザー2015年

とった。ただし、Perlループをエミュレートするには、以下によって追加される末尾が必要です。Perlに、読み取られた行から終わる行が含まれますが、bashには含まれません。(ただし、@ gniourf_gniourfが別の場所で指摘しているように、より堅牢なアプローチはの代わりに使用することです)。\necho$_ \nreadprintf '%s\n'echo
mklement0 2015年

8

問題のPerlループは、コマンドラインのすべてのファイル名引数から、またはファイルが指定されていない場合は標準入力から読み取ります。ファイルが指定されていない場合、すべての回答で単一のファイルまたは標準入力が処理されるようです。

多くの場合、UUOC(Useless Use of cat)として正確に欺かれますが、がcatそのジョブに最適なツールである場合があり、これがその1つであると主張できます。

cat "$@" |
while read -r line
do
    echo "$line"
done

これの唯一の欠点は、サブシェルで実行されるパイプラインが作成されるため、whileループ内の変数の割り当てなどはパイプラインの外部からアクセスできないことです。それをbash回避する方法はプロセス置換です:

while read -r line
do
    echo "$line"
done < <(cat "$@")

これにより、whileループがメインシェルで実行されたままになるため、ループで設定された変数はループの外部からアクセスできます。


1
複数のファイルについての優れた点。リソースとパフォーマンスにどのような影響があるかはわかりませんが、bash、ksh、またはzshを使用していないためにプロセス置換を使用できない場合は、コマンド置換を使用してhere-docを試すことができます(3つに分散)行)>>EOF\n$(cat "$@")\nEOF。最後に、quibble:while IFS= read -r lineは、while (<>)Perlでの動作のより良い近似です(先頭と末尾の空白は保持されますが、Perlも末尾を保持します\n)。
mklement0 2015

4

OPで指定されたコードを使用したPerlの動作は、引数を取らないか、複数引数を取ることができます。引数が単一のハイフンである場合、-これはstdinとして理解されます。さらに、でファイル名を指定することは常に可能$ARGVです。これまでのところ、これらの点でのPerlの動作を真似た回答はありません。これが純粋なBashの可能性です。秘訣はexec適切に使うことです。

#!/bin/bash

(($#)) || set -- -
while (($#)); do
   { [[ $1 = - ]] || exec < "$1"; } &&
   while read -r; do
      printf '%s\n' "$REPLY"
   done
   shift
done

ファイル名はで使用できます$1

引数が指定さ-れていない場合は、最初の位置パラメーターとして人為的に設定します。次に、パラメーターをループします。パラメータがでない場合、-標準入力をファイル名からリダイレクトしますexec。このリダイレクトが成功した場合、whileループでループします。私は標準REPLY変数を使用しています。この場合、をリセットする必要はありませんIFS。別の名前が必要IFSな場合は、そのようにリセットする必要があります(もちろん、それを望まず、何をしているのかを知っている場合を除きます)。

while IFS= read -r line; do
    printf '%s\n' "$line"
done

2

より正確に...

while IFS= read -r line ; do
    printf "%s\n" "$line"
done < file

2
これは本質的にはstackoverflow.com/a/6980232/45375に対するコメントであり、回答ではないと思います。コメントを明示的に するには、コマンドにIFS=-rを追加する と、read各行が変更されずに(先頭と末尾の空白を含めて)読み取られます。
mklement0

2

次のコードを試してください:

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

1
修正されたとしても、これは標準入力または複数のファイルから読み取られないため、質問に対する完全な回答ではないことに注意してください。(回答が最初に提出されてから3年以上経過して数分で2つの編集が行われるのも驚くべきことです。)
Jonathan Leffler

@JonathanLefflerは、このような古い(そしてあまり良くない)回答を編集して申し訳ありませんでした...しかし、この貧しい人々readIFS=となしで-r、そして貧しい人々$lineがその健全な引用なしで見るのは我慢できませんでした。
gniourf_gniourf 2015年

1
@gniourf_gniourf:read -r表記が嫌いです。IMO、POSIXは間違っています。オプションは、末尾のバックスラッシュを無効にするのではなく、特別な意味を有効にする必要があります。これにより、(POSIXが存在する前の)既存のスクリプト-rが省略されたために壊れないようにします。ただし、これはPOSIXシェルおよびユーティリティ標準の最も初期のバージョンであるIEEE 1003.2 1992の一部であることがわかりましたが、それでも追加としてマークされていたため、これはずっと昔のチャンスについて悩んでいます。私のコードが使用していないので、問題が発生することはありません-r。私は幸運である必要があります。これについては無視してください。
Jonathan Leffler、2015年

1
@JonathanLeffler私はそれ-rが標準であるべきだと本当に同意します。使用しないことでトラブルが発生する可能性は低いと思います。しかし、壊れたコードは壊れたコードです。私の編集は最初に$line、その引用符をひどく逃した貧しい変数によって引き起こされました。私readはそれをしている間に修正しました。echoロールバックされる編集の種類なので、修正しませんでした。:(
gniourf_gniourf 2015年

1

コード${1:-/dev/stdin}は最初の引数を理解するだけなので、これはどうでしょう。

ARGS='$*'
if [ -z "$*" ]; then
  ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
   echo "$line"
done

1

これらの答えはどれも受け入れられません。特に、受け入れられた回答は最初のコマンドラインパラメータのみを処理し、残りは無視します。エミュレートしようとしているPerlプログラムは、すべてのコマンドラインパラメータを処理します。したがって、受け入れられた答えは質問に答えることすらありません。他の回答はbash拡張を使用し、不必要な「cat」コマンドを追加し、入力を出力にエコーする単純なケースでのみ機能するか、または不必要に複雑です。

しかし、彼らは私にいくつかのアイデアを与えたので、私は彼らにいくつかの信用を与えなければなりません。ここに完全な答えがあります:

#!/bin/sh

if [ $# = 0 ]
then
        DEFAULT_INPUT_FILE=/dev/stdin
else
        DEFAULT_INPUT_FILE=
fi

# Iterates over all parameters or /dev/stdin
for FILE in "$@" $DEFAULT_INPUT_FILE
do
        while IFS= read -r LINE
        do
                # Do whatever you want with LINE here.
                echo $LINE
        done < "$FILE"
done

1

上記の答えをすべて組み合わせて、自分のニーズに合ったシェル関数を作成しました。これは、2台のWindows10マシンのcygwinターミナルからのもので、その間に共有フォルダーがありました。以下を処理できるようにする必要があります。

  • cat file.cpp | tx
  • tx < file.cpp
  • tx file.cpp

特定のファイル名が指定されている場合、コピー中に同じファイル名を使用する必要があります。入力データストリームがパイプ処理されている場合、時間と分と秒を持つ一時的なファイル名を生成する必要があります。共有メインフォルダには、曜日のサブフォルダがあります。これは組織化のためです。

見よ、私のニーズのための究極のスクリプト:

tx ()
{
  if [ $# -eq 0 ]; then
    local TMP=/tmp/tx.$(date +'%H%M%S')
    while IFS= read -r line; do
        echo "$line"
    done < /dev/stdin > $TMP
    cp $TMP //$OTHER/stargate/$(date +'%a')/
    rm -f $TMP
  else
    [ -r $1 ] && cp $1 //$OTHER/stargate/$(date +'%a')/ || echo "cannot read file"
  fi
}

これをさらに最適化する方法が見つかれば、私は知りたいと思います。


0

以下は標準shdashDebianでテスト済み)で動作し、かなり読みやすいですが、それは好みの問題です。

if [ -n "$1" ]; then
    cat "$1"
else
    cat
fi | commands_and_transformations

詳細:最初のパラメーターが空でない場合はcatそのファイル、それ以外の場合はcat標準入力。次に、ifステートメント全体の出力がによって処理されますcommands_and_transformations


それが真の解決策を指し示しているので、私見は最良の答えですcat "${1:--}" | any_command。シェル変数を読み取ってそれらをエコーすると、小さなファイルで機能する可能性がありますが、それほど拡張されません。
Andreas Spindler 2017

[ -n "$1" ]に単純化することができます[ "$1" ]
agc

0

これはターミナルで使いやすいです:

$ echo '1\n2\n3\n' | while read -r; do echo $REPLY; done
1
2
3

-1

いかがですか

for line in `cat`; do
    something($line);
done

の出力はcatコマンドラインに配置されます。コマンドラインには最大サイズがあります。また、これは行ごとではなく、単語ごとに読み取ります。
ノーティリスト2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.