Padrão literal de correspondência de script em várias linhas?

3

Eu tenho uma string de várias linhas na variável $PAT . $PAT deve ser pesquisado no arquivo $FILE . Se $PAT estiver em $FILE , precisará imprimir o arquivo com $PAT removido. Se $PAT não for encontrado, imprima nada. Não se sabe se $PAT contém algum caractere especial e deve ser correspondido literalmente. Por exemplo, se $PAT for //\/\|* , a mesma sequência de 8 caracteres deverá ser pesquisada em $FILE .

O uso no mundo real para isso é para instalar e remover texto em arquivos / scripts já existentes. Se você quiser acrescentar $PAT em $FILE , você quer saber se já foi adicionado anteriormente. Se $PAT já estiver em $FILE , a saída sem $PAT permitirá que você a desinstale facilmente.

Os sistemas que eu estou precisando de tal script para (dispositivos Android) só tem BusyBox neles. Nenhum Perl ou outras linguagens de script.

    
por Sepero 06.12.2012 / 09:18

3 respostas

2

Suponho que você esteja reescrevendo um arquivo de texto que cabe na memória (parece que você está reescrevendo um arquivo de configuração).

O script a seguir usa somente recursos internos do shell e cat . Deve funcionar no shell do Android, pelo menos desde Gingerbread e definitivamente desde Ice Cream Sandwich. Imprime o conteúdo do arquivo menos a primeira ocorrência de $PAT se houver um; se $PAT não ocorrer, nada será impresso.

contents=$(cat "$FILE")
case $contents in
  *"$PAT"*)
    echo "${contents%%$PAT*}${contents#*$PAT}";;
esac

Esse snippet pressupõe que o arquivo não contém nenhum byte nulo, termina em uma única nova linha e não inicia com um traço. Além disso, se o padrão terminar com uma nova linha, ele não será encontrado no final do arquivo. Os snippets mais complexos a seguir lidam com arquivos de texto arbitrários:

contents=$(cat "$FILE"; echo a)
contents=${contents%a}
case $contents in
  *"$PAT"*)
    contents="${contents%%$PAT*}${contents#*$PAT}"
    dashes=${contents%%[!-]*}
    echo -n "$dashes"
    echo -n "${contents#$dashes}";;
esac

(Observe que o comportamento proposto torna impossível distinguir um arquivo que contenha exatamente o padrão e um arquivo vazio.)

Na verdade, é mais fácil implementar seu script de acréscimo / remoção diretamente do que usar a função intermediária proposta.

contents=$(cat "$FILE"; echo a)
contents=${contents%a}
append=
case $contents in
  *"$PAT"*) contents="${contents%%$PAT*}${contents#*$PAT}";;
  *) contents="$contents$PAT"
esac
dashes=${contents%%[!-]*}
{ echo -n "$dashes"; echo -n "${contents#$dashes}"; } >"$FILE.new"
mv -- "$FILE.new" "$FILE"
    
por 07.12.2012 / 01:29
3

Se você quiser corresponder $PAT como linhas completas, eu tenho uma solução. Por linhas completas, quero dizer que, no caso de uma partida, você pode dividir $FILE em três sub-arquivos (f1, f2 & f3), onde:

  • cat f1 f2 f3 é $FILE ,
  • f2 é $PAT .

Note que f1 e / ou f3 podem estar vazios.

Primeiro, crie o arquivo f2:

cat << EOF > f2
$PAT
EOF

Então, diff $ FILE e f2, salvando o resultado:

diff $FILE f2 > diff_res
res=$?

Se $res for zero, então f1 e f3 estarão vazios e $ FILE será igual a $ PAT. Eu suponho que você queira um arquivo vazio neste caso.

Se diff_res contiver uma linha começando por " > ", f2 contém pelo menos uma linha que não está em $ FILE. Para testar isso:

grep -q '^> ' diff_res
test $? -eq 0 && echo "PAT not found"

Se diff_res não contiver linhas iniciadas por " > ", todas as linhas de f2 estarão em $ FILE, mas talvez não de forma contígua. Se for contiguamente, diff_res conterá:

  • Uma única linha que não começa com " < " (se f1 ou f3 estiverem vazias),
  • Duas linhas que não começam com " < ", a primeira sempre começando com " 1d " ou "1".

Para testar isso, temos:

nb=$(grep -v "^< " diff_res | wc -l)
if test $nb -gt 2; then
  pat_found=0
elif test $nb -eq 1; then
  pat_found=1
else
  pat_found=$(sed -n -e '1{/^1d/p;/^1,/p}' diff_res | wc -l)
fi

Em seguida, se pat_found for 1, o arquivo sem $ PAT será o resultado do diff apenas com as linhas iniciadas por " < " sem esses 2 char:

grep '^< ' diff_res | cut -c 3-

O script completo e reorganizado seria semelhante:

# Output the desired result on stdin.

f2=/tmp/f2              # Use of PID or mktmp would be better'
diff_res=/tmp/diff_res  # Use of PID or mktmp would be better'

cat << EOF > $f2
$PAT
EOF

diff $FILE $f2 > $diff_res
if test $? -ne 0; then
  grep -q '^> ' $diff_res
  if test $? -ne 0; then
    nb=$(grep -v "^< " $diff_res | wc -l)
    if test $nb -eq 1; then
      grep '^< ' $diff_res | cut -c 3-
    elif test $nb -eq 2; then
      pat_found=$(sed -n -e '1{/^1d/p;/^1,/p}' $diff_res | wc -l)
      test $pat_found -eq 1 && grep '^< ' $diff_res | cut -c 3-
    fi
  fi
fi

rm -f $f2 $diff_res
    
por 06.12.2012 / 13:30
1

Leia o caractere de arquivo por caractere. Se o caractere corresponder ao primeiro caractere da variável, compare o próximo e assim por diante. Se a variável inteira não for correspondida, retorne novamente. Você pode até mesmo implementar um algoritmo mais avançado para fazê-lo funcionar mais rápido, mas como a sua linguagem é a casca, seria ser terrivelmente lento de qualquer maneira.

    
por 06.12.2012 / 09:44