Bash, ¿nombre del archivo stdin?

Bash, ¿nombre del archivo stdin?

Tengo que escribir un script bash que cuente símbolos como "<" y ">" de la entrada estándar.

Por ejemplo:

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

Hice esto:

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

¿Es esta una buena manera? No sé cómo obtener el nombre de archivo "example.html" de la entrada estándar.

Respuesta1

El punto stdines que puede ser cualquier cosa, puede ser por ejemplo una tubería, un socket de red, un archivo normal, un dispositivo, puede estar a la mitad de un archivo normal cuando se inicia el script... Si puedes' Si no procesa los datos en una sola pasada, entonces se limita abuscablearchivos, es decir archivos normales y algunos archivos de dispositivo, o tendrá que almacenar la información de alguna manera (en un archivo temporal o en la memoria...). Sin embargo, aquí es posible obtener toda la información a la vez.

Por ejemplo, podrías hacer:

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

POSIXly:

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

Para detectar discrepancias:

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

Ahora, para responder la pregunta del tema, en Linux, puedes encontrar un "nombre" para stdin con:

readlink -f /dev/stdin

Ejemplo:

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

(20238 arriba es el pid de readlink, por lo que esa ruta no será de mucha utilidad después de readlinkhaber salido, y no lo sería de todos modos, eso pipe:[758683]es solo informativo, no puede serabrió).

Y de manera más general si lsofestá disponible:

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

(Sin embargo, $$al ser el pid del proceso que ejecutó el shell, no funcionaría en subshells a los que se les haya redirigido su stdin)

Ahora, no necesariamente podrá volver a abrir ese archivo para leerlo, e incluso cuando lo haga, es posible que la lectura de ese archivo no le proporcione los mismos datos nuevamente (piense en las tuberías, por ejemplo).

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

En Linux, abrir /dev/stdinsi stdin es un archivo normal, leerá el archivo desde el principio nuevamente, mientras que en otros sistemas, abrir /dev/stdin es más como un archivo dup(0), es decir, no rebobina el archivo al principio (en el primer ejemplo anterior). , saldría 1\n2\n3\nuna vez en lugar de dos veces).

Respuesta2

Tendrás que almacenar el contenido del archivo de alguna manera. Puedes usar una variable.

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

O un archivo temporal (necesario si example.htmlcontiene bytes nulos).

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

Si leer el contenido del archivo desde la entrada estándar no es un requisito, puede pasar el nombre del archivo como argumento al script.

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

Llame al script así:

$ ./myscript.sh example.html

Respuesta3

Una posibilidad para su tarea es:

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

Si llamas a este scriptrecuento no coincidente(puedes elegir un nombre más corto), podrás usarlo con o sin nombre de archivo. Algunas posibilidades:

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

La salida será algo como:

> 41
< 42

Si necesita detectar discrepancias, agregue al final del script:

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

o algo más explícito:

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

información relacionada