Modo Slurp no awk?

17

Ferramentas como sed , awk ou perl -n processam um registro de cada vez, registros sendo linhas por padrão.

Alguns, como awk com RS , GNU sed com -z ou perl com -0ooo podem alterar o tipo de registro selecionando um separador de registro diferente.

perl -n pode fazer toda a entrada (cada arquivo individual quando passado vários arquivos) um único registro com a opção -0777 (ou -0 seguido por qualquer número octal maior que 0377, 777 sendo o canônico). Isso é o que eles chamam de modo slurp .

Pode algo semelhante ser feito com awk RS ou qualquer outro mecanismo? Onde awk processa cada conteúdo arquivo como um todo em ordem em oposição a cada linha de cada arquivo?

    
por Stéphane Chazelas 19.08.2016 / 14:20

1 resposta

15

Você pode adotar abordagens diferentes dependendo se awk trata RS como um único caractere (como o tradicional awk implementações faz) ou como uma expressão regular (como gawk ou mawk do). Arquivos vazios também são difíceis de serem considerados, pois awk tende a ignorá-los.

gawk , mawk ou outras awk implementações em que RS pode ser um regexp.

Nessas implementações (para mawk , cuidado com o fato de alguns sistemas operacionais como o Debian enviarem uma versão muito antiga em vez de um o moderno mantido por @ThomasDickey ), se RS contiver um único caractere, o separador de registro é esse caractere ou awk entra no modo de parágrafo quando RS está vazio ou trata RS como uma expressão regular caso contrário.

A solução é usar uma expressão regular que possivelmente não pode ser correspondida. Alguns vêm à mente como x^ ou $x ( x antes do início ou após o final). No entanto, alguns (particularmente com gawk ) são mais caros que outros. Até agora, descobri que ^$ é o mais eficiente. Ele só pode coincidir com uma entrada vazia, mas então não haveria nada para combinar.

Então, podemos fazer:

awk -v RS='^$' '{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...

Uma ressalva é que ele pula arquivos vazios (ao contrário de perl -0777 -n ). Isso pode ser resolvido com o GNU awk colocando o código em uma instrução ENDFILE . Mas também precisamos redefinir $0 em uma instrução BEGINFILE, pois, caso contrário, ela não seria redefinida após o processamento de um arquivo vazio:

gawk -v RS='^$' '
   BEGINFILE{$0 = ""}
   ENDFILE{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...

implementações tradicionais awk , POSIX awk

Nesses, RS é apenas um caractere, eles não têm BEGINFILE / ENDFILE , eles não têm a variável RT , eles geralmente também não podem processar o caractere NUL. / p>

Você poderia pensar que usar RS='RS='RS='' poderia funcionar, pois, de qualquer forma, eles não podem processar entradas que contenham o byte NUL, mas não, em implementações tradicionais é tratado como $'\U10FFFE' , que é o modo de parágrafo. .

Uma solução pode ser usar um caractere com pouca probabilidade de ser encontrado na entrada como sed . Em localidades de caractere multibyte, você pode até mesmo criar sequências de bytes que são muito improváveis de ocorrer, pois elas formam caracteres que não são atribuídos ou não caracterizam como $0 em locales UTF-8. Não é realmente infalível e você também tem problemas com arquivos vazios.

Outra solução pode ser armazenar toda a entrada em uma variável e processá-la na instrução END no final. Isso significa que você pode processar apenas um arquivo por vez:

awk '{content = content $0 RS}
     END{$0 = content
       printf "%s: <%s>\n", FILENAME, $0
     }' file

Esse é o equivalente de gawk :

sed '
  :1
  $!{
   N;b1
  }
  ...' file1

Outro problema com essa abordagem é que, se o arquivo não estava terminando em um caractere de nova linha (e não estava vazio), ainda é adicionado arbitrariamente em RT no final (com RS , você contornar isso usando NR em vez de FNR no código acima). Uma vantagem é que você tem um registro do número de linhas no arquivo em %code% / %code% .

    
por 19.08.2016 / 14:20

Tags