web-dev-qa-db-ja.com

Bashでファイルや標準入力から読み込める方法?

Perlでは、次のコードがコマンドライン引数で指定されたファイルまたは標準入力から読み込まれます。

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

これはとても便利です。ファイルや標準入力から読み込む最も簡単な方法は何かを知りたいだけです。

207
Dagang

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

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

代入${1:-...}は、定義されていれば$1を取ります。そうでなければ、独自プロセスの標準入力のファイル名が使用されます。

344
Fritz G. Mehner

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

#!/bin/bash
less <&0

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

ファイルディスクリプタのリダイレクトについてもっと読んでください

94
Ryan Ballantyne

これが最も簡単な方法です。

#!/bin/sh
cat -

使用法:

$ echo test | sh my_script.sh
test

stdinを変数に代入するには、STDIN=$(cat -)または単にSTDIN=$(cat)を演算子として使う必要はありません( @ mklement0 comment)のとおり )。


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

#!/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を設定しない場合、デフォルトでは Space そして Tab 行の始めと終わりの行は無視されます(切り捨てられます)。

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

参照してください: 引数が渡されないときの標準入力の読み方 at stackoverflow SE

63
kenorb

これは直接的な方法だと思います。

$ 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
14
Amir Mehler

echoソリューションは、IFSが入力ストリームを中断するたびに新しい行を追加します。 @ fgmの答え は少し修正できます。

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

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

UUOCcatの無駄な使用)として正確に省略されることがよくありますが、catがその仕事に最適なツールである場合があり、それは議論の余地がありますこれはそれらの1つであること:

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

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

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

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

7

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を使ってfilenameからの標準入力をリダイレクトします。このリダイレクトが成功した場合、whileループでループします。私は標準のREPLY変数を使っています、そしてこの場合あなたはIFSをリ​​セットする必要はありません。別の名前が必要な場合は、IFSをそのように再設定する必要があります(もちろん、それが望ましくなく、自分のしていることがわからない場合を除く)。

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

より正確に...

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

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

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

コード${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
Gungwald

上記のすべての答えを組み合わせて、自分のニーズに合ったシェル関数を作成しました。これは、私が2台のWindows 10マシンの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
typelogic

以下は標準的なsh(Debianではdashでテスト済み)で動作し、非常に読みやすいですが、それは好みの問題です。

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

詳細:最初のパラメータが空でなければそのファイルをcat、そうでなければcat標準入力。その後、ifステートメント全体の出力がcommands_and_transformationsによって処理されます。

0
Notinlist