Bash, nome do arquivo stdin?

4

Eu tenho que escrever um script que contará símbolos como "<" e ">" de stdin.

Por exemplo:

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

Eu fiz isso:

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

Isso é bom? Eu não sei como obter o nome do arquivo "example.html" do stdin.

    
por Georgy 28.09.2013 / 13:29

3 respostas

15

O ponto inteiro de stdin é que pode ser qualquer coisa, pode ser, por exemplo, um pipe, um soquete de rede, um arquivo regular, um dispositivo, pode estar no meio de um arquivo regular quando seu script é iniciado. .. Se você não pode processar os dados em uma passagem, então você se limita a arquivos procuráveis , que são arquivos regulares e alguns arquivos de dispositivos, ou terá que armazenar as informações de alguma forma (em um arquivo temporário ou memória ...). É possível aqui obter todas as informações de uma só vez.

Por exemplo, você poderia fazer:

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

POSIXly:

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

Para detectar incompatibilidade:

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

Agora, para responder a pergunta no assunto, no Linux, você pode encontrar um "nome" para stdin com:

readlink -f /dev/stdin

Exemplo:

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

(20238 acima é o pid de readlink , então esse caminho não será de muita utilidade após readlink ter saído, e não seria assim, que pipe:[758683] é apenas informativo, pode ' t ser aberto ).

E, mais geralmente, se lsof estiver disponível:

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

(Embora, $$ seja o pid do processo que executou o shell, ele não funcionaria em subshells que tiveram seu stdin redirecionado)

Agora, você não será necessariamente capaz de reabrir esse arquivo para leitura, e mesmo quando o fizer, a leitura desse arquivo pode não fornecer os mesmos dados novamente (pense em pipes, por exemplo).

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

No Linux, abrir /dev/stdin se stdin for um arquivo regular, lerá o arquivo do início novamente enquanto em outros sistemas, abrir / dev / stdin for mais parecido com um dup(0) , ou seja, ele não retrocederá arquivo para o início (no primeiro exemplo acima, ele produziria 1\n2\n3\n uma vez em vez de duas vezes).

    
por 28.09.2013 / 14:27
7

Você terá que armazenar o conteúdo do arquivo de alguma forma. Você pode usar uma variável.

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

Ou um arquivo temporário (necessário se example.html contiver 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

Se a leitura do conteúdo do arquivo de stdin não for um requisito, você poderá passar filename como um argumento para o 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

Chame o script assim:

$ ./myscript.sh example.html
    
por 28.09.2013 / 14:25
1

Uma possibilidade para sua tarefa é:

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

Se você chamar este script countmismatched (você pode escolher um nome mais curto), poderá usá-lo com ou sem um nome de arquivo. Algumas possibilidades:

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

A saída será algo como:

> 41
< 42

Se você precisar detectar incompatíveis, adicione no final do script:

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

ou algo mais 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

Espero que isso ajude!

    
por 28.09.2013 / 15:26

Tags