Bash, имя файла stdin?

Bash, имя файла stdin?

Мне нужно написать bash-скрипт, который будет подсчитывать символы типа «<» и «>» из stdin.

Например:

$ ./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

Это хороший способ? Я не знаю, как получить имя файла "example.html" из stdin.

решение1

Суть в том stdin, что это может быть что угодно, например, канал, сетевой сокет, обычный файл, устройство, это может быть половина обычного файла, когда запускается ваш скрипт... Если вы не можете обработать данные за один проход, то вы ограничиваете себяискомыйфайлы, то есть обычные файлы и несколько файлов устройств, или придется как-то хранить информацию (во временном файле или памяти...). Однако здесь можно получить всю информацию сразу.

Например, вы можете сделать следующее:

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

POSIXly:

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\nодин вывод вместо двух).

решение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

Один из вариантов решения вашей задачи:

#!/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

Связанный контент