Bash, Name der Stdin-Datei?

Bash, Name der Stdin-Datei?

Ich muss ein Bash-Skript schreiben, das Symbole wie „<“ und „>“ von stdin zählt.

Zum Beispiel:

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

Ich tat dies:

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

Ist das ein guter Weg? Ich weiß nicht, wie ich den Dateinamen „example.html“ von stdin bekomme.

Antwort1

Der springende Punkt stdinist, dass es alles sein kann, es kann zum Beispiel eine Pipe sein, ein Netzwerk-Socket, eine normale Datei, ein Gerät, es kann mitten in einer normalen Datei sein, wenn Ihr Skript gestartet wird... Wenn Sie die Daten nicht in einem Durchgang verarbeiten können, beschränken Sie sich aufsuchbarDateien, also normale Dateien und einige Gerätedateien, oder Sie müssen die Informationen irgendwie speichern (in einer temporären Datei oder einem temporären Speicher ...). Es ist hier jedoch möglich, alle Informationen auf einmal abzurufen.

Sie könnten beispielsweise Folgendes tun:

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

POSIX:

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

So erkennen Sie eine Nichtübereinstimmung:

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

Um nun die Frage im Betreff zu beantworten: Unter Linux können Sie einen „Namen“ für stdin wie folgt finden:

readlink -f /dev/stdin

Beispiel:

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

(20238 oben ist die PID von readlink, daher wird dieser Pfad nach dem Beenden nicht mehr von großem Nutzen sein readlink, und das wäre er auch nicht, das pipe:[758683]ist nur informativ, es kann nichtgeöffnet).

Und allgemeiner, wenn lsofverfügbar:

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

( $$Da es sich allerdings um die PID des Prozesses handelt, der die Shell ausgeführt hat, würde es in Sub-Shells, deren Standardeingabe umgeleitet wurde, nicht funktionieren.)

Jetzt können Sie die Datei nicht unbedingt erneut zum Lesen öffnen und selbst wenn Sie dies tun, erhalten Sie beim Lesen aus der Datei möglicherweise nicht wieder dieselben Daten (denken Sie beispielsweise an Pipes).

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

Unter Linux wird beim Öffnen von /dev/stdinstdin eine reguläre Datei die Datei erneut vom Anfang an gelesen, während auf anderen Systemen das Öffnen von /dev/stdin eher einem entspricht dup(0), d. h. die Datei wird nicht an den Anfang zurückgespult (im ersten Beispiel oben würde die Ausgabe 1\n2\n3\neinmal statt zweimal erfolgen).

Antwort2

Sie müssen den Dateiinhalt irgendwie speichern. Sie können eine Variable verwenden.

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

Oder eine temporäre Datei (erforderlich, wenn example.htmlsie Null-Bytes enthält).

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

Wenn das Lesen des Dateiinhalts von stdin nicht erforderlich ist, können Sie den Dateinamen als Argument an das Skript übergeben.

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

Rufen Sie das Skript folgendermaßen auf:

$ ./myscript.sh example.html

Antwort3

Eine Möglichkeit für Ihre Aufgabe ist:

#!/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[<]}"

Wenn Sie dieses Skript aufrufenAnzahlnichtübereinstimmend(Sie können einen kürzeren Namen wählen), Sie können es mit oder ohne Dateinamen verwenden. Einige Möglichkeiten:

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

Die Ausgabe sieht ungefähr so ​​aus:

> 41
< 42

Wenn Sie Nichtübereinstimmungen erkennen müssen, fügen Sie am Ende des Skripts Folgendes hinzu:

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

oder etwas deutlicher:

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

verwandte Informationen