Perlでは、次のコードがコマンドライン引数で指定されたファイルまたは標準入力から読み込まれます。
while (<>) {
print($_);
}
これはとても便利です。ファイルや標準入力から読み込む最も簡単な方法は何かを知りたいだけです。
次の解決策は、スクリプトがファイル名を最初のパラメーターとして$1
として呼び出されている場合はファイルから読み取り、それ以外の場合は標準入力から読み取ります。
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
代入${1:-...}
は、定義されていれば$1
を取ります。そうでなければ、独自プロセスの標準入力のファイル名が使用されます。
おそらく最も簡単な解決策は、マージリダイレクト演算子でstdinをリダイレクトすることです。
#!/bin/bash
less <&0
Stdinはファイル記述子ゼロです。上記はbashスクリプトにパイプされた入力をlessの標準入力に送ります。
これが最も簡単な方法です。
#!/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
を使用します。ただし、それをサポートする外部GNUecho
を実行するenv POSIXLY_CORRECT=1 echo "$line"
を使用することによる回避策があります。参照してください: どのように私は "-e"をエコーするのですか?
参照してください: 引数が渡されないときの標準入力の読み方 at stackoverflow SE
これは直接的な方法だと思います。
$ 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
echo
ソリューションは、IFS
が入力ストリームを中断するたびに新しい行を追加します。 @ fgmの答え は少し修正できます。
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
問題のPerlループは、コマンドライン上のファイル名引数をallから、ファイルが指定されていない場合は標準入力から読み込みます。私が見た答えはすべて、ファイルが指定されていない場合は単一のファイルまたは標準入力を処理するようです。
UUOC (cat
の無駄な使用)として正確に省略されることがよくありますが、cat
がその仕事に最適なツールである場合があり、それは議論の余地がありますこれはそれらの1つであること:
cat "$@" |
while read -r line
do
echo "$line"
done
唯一の欠点は、サブシェル内で実行されるパイプラインを作成することです。そのため、while
ループ内の変数割り当てなどは、パイプライン外からはアクセスできません。それを回避するbash
の方法は、 プロセス置換 です。
while read -r line
do
echo "$line"
done < <(cat "$@")
これにより、メインシェル内でwhile
ループが実行されたままになるため、ループ内で設定された変数はループ外でアクセスできます。
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
より正確に...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
次のコードを試してください。
while IFS= read -r line; do
echo "$line"
done < file
コード${1:-/dev/stdin}
は最初の引数を理解するだけなので、これはどうですか。
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
私はこれらの答えのどれもが許容できるとは思いません。特に、受け入れられた答えは最初のコマンドラインパラメータを処理するだけで残りを無視します。エミュレートしようとしている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
上記のすべての答えを組み合わせて、自分のニーズに合ったシェル関数を作成しました。これは、私が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
}
あなたがこれをさらに最適化するために見ることができる方法があるならば、私は知りたいです。
以下は標準的なsh
(Debianではdash
でテスト済み)で動作し、非常に読みやすいですが、それは好みの問題です。
if [ -n "$1" ]; then
cat "$1"
else
cat
fi | commands_and_transformations
詳細:最初のパラメータが空でなければそのファイルをcat
、そうでなければcat
標準入力。その後、if
ステートメント全体の出力がcommands_and_transformations
によって処理されます。