Bash、stdin ファイルの名前は?

Bash、stdin ファイルの名前は?

stdin から「<」や「>」などの記号をカウントする bash スクリプトを作成する必要があります。

例えば:

$ ./myscript.sh <example.html
> - 20
< - 21
Found mismatching brackets!

これは私がしました:

x=`grep -o '>' example.html | wc -l`
y=`grep -o '<' example.html | wc -l`
if [ "$x" -ne "$y" ]; then
  echo "Mismatch!"
fi
echo $x
echo $y

これは良い方法でしょうか? stdin からファイル名「example.html」を取得する方法がわかりません。

答え1

のポイントはstdin、それが何でも可能だということです。例えば、パイプ、ネットワークソケット、通常のファイル、デバイス、スクリプトの開始時に通常のファイルの途中であってもかまいません... 1回のパスでデータを処理できない場合は、シーク可能ファイル、つまり通常のファイルといくつかのデバイス ファイル、または何らかの方法で情報を保存する必要があります (一時ファイルまたはメモリ内など)。ただし、ここではすべての情報を一度に取得することは可能です。

たとえば、次のようにすることができます。

$ grep -o '[<>]' < a.html | sort | uniq -c
     82 <
     82 >

POSIX 的には:

fold -w 1 a.html | grep '[<>]' | sort | uniq -c

不一致を検出するには:

if fold -w 1 a.html | awk '{n[$0]++}
     END{exit(n["<"]!=n[">"])}'
then
  echo match
else
  echo mismatch
fi

さて、件名の質問に答えると、Linux では、次のようにして stdin の「名前」を見つけることができます。

readlink -f /dev/stdin

例:

$ readlink -f /dev/stdin < a
/home/chazelas/a
$ : | readlink -f /dev/stdin
/proc/20238/fd/pipe:[758683]

(上記の20238はのpidなのでreadlink、そのパスはが終了した後にはあまり役に立たずreadlink、いずれにしても役に立たないでしょう。これはpipe:[758683]単なる情報であり、開いた)。

より一般的には、lsofが利用可能な場合:

lsof -ad0 -p "$$" -Fn 2> /dev/null | sed -n 'n;s/^n//p'

(ただし、$$シェルを実行したプロセスの pid であるため、stdin がリダイレクトされたサブシェルでは機能しません)

これで、そのファイルを再度開いて読み取りができるとは限りません。また、再度開いたとしても、そのファイルから読み取っても同じデータが得られるとは限りません (パイプなどを想像してください)。

$ seq 3 > a
$ { cat; cat /dev/stdin; } < a
1
2
3
1
2
3
$ cat a | { cat; cat /dev/stdin; }
1
2
3

Linux では、/dev/stdinstdin が通常のファイルの場合、 を開くと、ファイルが最初から再度読み込まれますが、他のシステムでは、 /dev/stdin を開くと に似ておりdup(0)、ファイルを先頭まで巻き戻しません (上記の最初の例では、出力は1\n2\n3\n2 回ではなく 1 回になります)。

答え2

何らかの方法でファイルの内容を保存する必要があります。変数を使用できます。

content=`cat`
x=`echo "$content" | grep -o '>' | wc -l`
y=`echo "$content" | grep -o '<' | wc -l`
if [ "$x" -ne "$y" ]; then
  echo "Mismatch!"
fi
echo $x
echo $y

または一時ファイル(example.htmlヌルバイトが含まれている場合に必要)。

tmp=`mktemp`
trap "rm $tmp" EXIT
x=`grep -o '>' "$tmp" | wc -l`
y=`grep -o '<' "$tmp" | wc -l`
if [ "$x" -ne "$y" ]; then
  echo "Mismatch!"
fi
echo $x
echo $y

stdin からファイルの内容を読み取る必要がない場合は、ファイル名を引数としてスクリプトに渡すことができます。

x=`grep -o '>' "$1" | wc -l`
y=`grep -o '<' "$1" | wc -l`
if [ "$x" -ne "$y" ]; then
  echo "Mismatch!"
fi
echo $x
echo $y

次のようにスクリプトを呼び出します。

$ ./myscript.sh example.html

答え3

タスクの 1 つの可能性は次のとおりです。

#!/bin/bash

if [[ -n $1 ]]; then
   if [[ ! -f $1 ]] || [[ ! -r $1 ]]; then
      echo >&2 "File \`$1' not found or not readable."
      exit 1
   fi
   exec "$0" < "$1"
fi

declare -A hary
while read c; do
   (( ++hary[$c] ))
done < <(grep -o '[<>]')

echo "> ${hary[>]}"
echo "< ${hary[<]}"

このスクリプトを呼び出すとカウントが一致しません(より短い名前を選択することもできます)、ファイル名の有無にかかわらず使用できます。いくつかの可能性:

$ countmismatched example.html
$ countmismatched < example.html
$ cat example.html | countmismatched

出力は次のようになります。

> 41
< 42

不一致を検出する必要がある場合は、スクリプトの最後に以下を追加します。

if (( hary[<]} != hary[>] )); then
    echo "Mismatched brackets"
else
    echo "It's all good"
fi

またはもっと明確なもの:

((difference=hary[<]-hary[>]))
if (( difference>0 )); then
    echo "Mismatched brackets: you have $difference more <'s than >'s"
elif (( difference<0 )); then
    echo "Mismatched brackets: you have $((-difference)) more >'s than <'s"
else
    echo "It's all good"
fi

関連情報