
Мне нужно написать 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/stdin
stdin — обычного файла приведет к повторному чтению файла с самого начала, тогда как в других системах открытие /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