Por que o awk para e espera se o nome do arquivo contém = e como contornar isso?

25
awk 'processing_script_here' my=file.txt

parece parar e esperar indefinidamente ...
O que está acontecendo aqui e como faço para funcionar?

don_crissti
fonte

Respostas:

19

Como Chris diz , os argumentos do formulário variablename=anythingsão tratados como atribuição de variável (executada no momento em que os argumentos são processados, em oposição aos (mais recentes) -v var=valueexecutados antes das BEGINinstruções), em vez de nomes de arquivo de entrada.

Isso pode ser útil em coisas como:

awk '{print $1}' FS=/ RS='\n' file1 FS='\n' RS= file2

Onde você pode especificar um arquivo diferente FS/ RSpor. Também é comumente usado em:

awk '!file1_processed{a[$0]; next}; {...}' file1 file1_processed=1 file2

Qual é uma versão mais segura de:

awk 'NR==FNR{a[$0]; next}; {...}' file1 file2

(que não funciona se file1estiver vazio)

Mas isso atrapalha quando você tem arquivos cujo nome contém =caracteres.

Agora, isso é apenas um problema quando o que resta do primeiro =é um awknome de variável válido .

O que constitui um nome de variável válido em awké mais rigoroso que em sh.

O POSIX exige que seja algo como:

[_a-zA-Z][_a-zA-Z0-9]*

Com apenas caracteres do conjunto de caracteres portátil. No entanto, o /usr/xpg4/bin/awkSolaris 11 pelo menos não é compatível nesse sentido e permite que caracteres alfabéticos no código do idioma nos nomes de variáveis, não apenas a-zA-Z.

Portanto, um argumento como x+y=fooou =barou ./foo=barainda é tratado como um nome de arquivo de entrada e não como uma atribuição, pois o que resta do primeiro =não é um nome de variável válido. Um argumento como Stéphane=Chazelas.txtpode ou não, dependendo da awkimplementação e da localidade.

Por isso, com o awk, é recomendável usar:

awk '...' ./*.txt

ao invés de

awk '...' *.txt

por exemplo, para evitar o problema se você não puder garantir que o nome dos txtarquivos não contenha =caracteres.

Além disso, lembre-se de que um argumento como esse -vfoo=bar.txtpode ser tratado como uma opção se você usar:

awk -f file.awk -vfoo=bar.txt

(aplica-se também para awk '{code}' -vfoo=bar.txtcom o awkde versões busybox antes 1.28.0, ver relatório de erros correspondente ).

Novamente, o uso de ./*.txtsoluções para isso (o uso de um ./prefixo também ajuda com um arquivo chamado -que, de outra forma, awkentende como significando entrada padrão ).

É por isso também

#! /usr/bin/awk -f

shebangs realmente não funcionam. Enquanto esses var=valuepodem ser contornados, fixe os ARGVvalores (adicione um ./prefixo) em uma BEGINinstrução:

#! /usr/bin/awk -f
BEGIN {
  for (i = 1; i < ARGC; i++)
    if (ARGV[i] ~ /^[_[:alpha:]][_[:alnum:]]*=/)
      ARGV[i] = "./" ARGV[i]
}
# rest of awk script

Isso não ajudará com as opções, pois elas são vistas awke não o awkscript.

Um problema cosmético em potencial com o uso desse ./prefixo é que ele acaba FILENAME, mas você sempre pode usá substr(FILENAME, 3)-lo para removê-lo, se não quiser.

A implementação do GNU awkcorrige todos esses problemas com sua -Eopção.

Depois -E, o gawk espera apenas o caminho do awkscript (onde -ainda significa stdin) e, em seguida, uma lista apenas dos caminhos do arquivo de entrada (e, nem mesmo -é tratado especialmente).

Foi especialmente desenvolvido para:

#! /usr/bin/gawk -E

shebangs onde a lista de argumentos sempre são arquivos de entrada (observe que você ainda pode editar essa ARGVlista em uma BEGINinstrução).

Você também pode usá-lo como:

gawk -e '...awk code here...' -E /dev/null *.txt

Usamos -Eum script vazio ( /dev/null) apenas para garantir que esses *.txtitens sejam sempre tratados como arquivos de entrada, mesmo que contenham =caracteres.

Stéphane Chazelas
fonte
Não vejo como o caminho explícito que termina em FILENAME é um problema. O script awk é geral; nesse caso, ele deve lidar com todos os tipos de caminhos que terminam em FILENAME (incluindo, entre outros ../foo, /path/to/fooe caminhos que estão em uma codificação diferente) - nesse caso substr(FILENAME,3), não será suficiente ou será um script de um tiro onde o usuário basicamente sabe o que os nomes de arquivos são - caso em que ele / ela provavelmente não deve se preocupar com qualquer um deles contendo =quer ;-)
mosvy
2
@mosvy Eu não acho que exista tanto que ./seja um problema, mas que pode ser indesejável sob certas condições, como casos em que o nome do arquivo deve ser incluído na saída, caso em que ./deve ser redundante e desnecessário, para que você precisará se livrar dele de alguma forma. Aqui está pelo menos um exemplo . Quanto ao usuário saber o que são os nomes de arquivos - bem, neste caso, também sabemos o que é o nome do arquivo, mas =ainda atrapalha o processamento adequado. Assim, a liderança pode -atrapalhar.
Sergiy Kolodyazhnyy
@mosvy, sim, a idéia é que você deseja usar o ./prefixo para contornar esse awkrecurso (mis), mas então você acaba com um ./resultado na saída que pode querer remover. Veja como verificar se a primeira linha do arquivo contém uma sequência específica? como um exemplo.
Stéphane Chazelas
Não é apenas o local (relativo a este diretório), ./mas também o global (caminho absoluto) /que faz o awk interpretar o argumento como um arquivo.
Isaac
21

Na maioria das versões do awk, os argumentos após a execução do programa são:

  1. Um arquivo
  2. Uma atribuição do formulário x=y

Como seu nome de arquivo está sendo interpretado como caso 2, o awk ainda está esperando algo para ler no stdin (já que ele não percebe que houve qualquer nome de arquivo passado).

Portably, esse comportamento está documentado no POSIX :

Um dos dois tipos de argumento a seguir pode ser misturado:

  • file: um nome de caminho de um arquivo que contém a entrada a ser lida, que corresponde ao conjunto de padrões no programa. Se nenhum operando de arquivo for especificado, ou se um operando de arquivo for '-', a entrada padrão deve ser usada.
  • atribuição: um operando que começa com um caractere sublinhado ou alfabético do conjunto de caracteres portáteis (consulte a tabela no volume Definições básicas da IEEE Std 1003.1-2001, Seção 6.1, Conjunto de caracteres portáteis), seguido por uma sequência de sublinhados, dígitos, e os alfabéticos do conjunto de caracteres portáteis, seguidos pelo caractere '=', devem especificar uma atribuição de variável em vez de um nome de caminho.

Como tal, de maneira portável, você tem algumas opções (o primeiro é provavelmente o menos invasivo):

  1. Use awk ... ./my=file, que evita isso, pois .não é "um caractere sublinhado ou alfabético do conjunto de caracteres portátil".
  2. Coloque o arquivo no stdin usando awk ... < my=file. No entanto, isso não funciona bem com vários arquivos.
  3. Faça um hardlink para o arquivo temporariamente e use-o. Você pode fazer algo como ln my=file my_filee depois usar my_filenormalmente. Nenhuma cópia será executada e os dois arquivos serão apoiados pelos mesmos dados e metadados do inode. Depois de usá-lo, é seguro remover o link criado, pois o número de referências ao inode ainda será maior que 0.
Chris Down
fonte
6
Não ./my=file funciona? % awk 'processing_script_here' ./my=file.txt awk: fatal: cannot open file ./my=file.txt' for reading (No such file or directory). Isso deve ser portátil, porque ./mynão é um nome de variável válido, portanto não deve ser analisado dessa maneira.
Stephen Harris
2
Como diz o texto do POSIX, o problema ocorre apenas quando o primeiro =é precedido por um caractere sublinhado ou alfabético do conjunto de caracteres portáteis (consulte a tabela no volume Definições básicas da IEEE Std 1003.1-2001, Seção 6.1, Conjunto de caracteres portáteis), seguido por uma sequência de sublinhados, dígitos e alfabéticos do conjunto de caracteres portátil . portanto, um caminho de arquivo como ++foo=bar.txtou =fooou ./foo=barestá tudo bem assim .ou +não [_a-zA-Z].
Stéphane Chazelas
11
O @SergiyKolodyazhnyy awk é externo ao shell, portanto, não importa qual você use. ./my=fileserá passado literalmente.
Chris Down
11
@SergiyKolodyazhnyy, mesmo para awk '{print $1,$2}' /etc/passwd. O ponto é que ter o shell aberto o arquivo em oposição ao awk não faz diferença se o torna ou não procurável. Na verdade, awk '{exit}' < /etc/passwdvocê esperaria awkvoltar ao final do primeiro registro exitpara ter certeza de que ele deixaria a posição dentro do padrão. O POSIX exige isso. /usr/xpg4/bin/awkfaz isso no Solaris, mas gawknem mawkparece fazê-lo no GNU / Linux.
Stéphane Chazelas
3
@mosvy, consulte a seção INPUT FILES em pubs.opengroup.org/onlinepubs/9699919799/utilities/… É útil em vários padrões de uso que só fazem sentido com arquivos regulares, como quando você deseja truncar um arquivo ou gravar dados nele em uma posição identificada por awkesse caminho.
Stéphane Chazelas
3

Para citar a documentação do gawk (observe a ênfase adicionada):

Quaisquer argumentos adicionais na linha de comandos são normalmente tratados como arquivos de entrada a serem processados ​​na ordem especificada. No entanto, um argumento que tem o formato var = value, atribui o valor do valor à variável var - ele não especifica um arquivo.

Por que o comando para e espera? Como no formulário awk 'processing_script_here' my=file.txt não há arquivo especificado pela definição acima - my=file.txté interpretado como atribuição de variável, e se não houver arquivo definido, awkserá lido stdin (também é evidente a partir do stracequal mostra que o awk nesse comando está aguardando read(0,'...)syscall.

Isso também está documentado nas especificações do POSIX awk , consulte a seção OPERANDS e parte das atribuições )

A atribuição de variável é evidente, awk '{print foo}' foo=bar /etc/passwdpois o valor de fooé impresso para cada linha em / etc / passwd. ./foo=barNo entanto, especificar ou caminho completo funciona.

Note que a execução straceem awk '1' foo=bar, bem como verificar com cat foo=barmostra que este é problema específico do awk, e execve faz show de nome de arquivo como argumento passado, então conchas não têm nada a ver com atribuições de variáveis env neste caso.

Além disso, observe que awk '...script...' foo=barisso não causará a criação de variáveis ​​de ambiente pelo shell, pois as atribuições de variáveis ​​de ambiente devem estar precedendo um comando para entrar em vigor. Consulte Regras de gramática do POSIX Shell , ponto número 7. Além disso, isso pode ser verificado viaawk '{print ENVIRON["foo"]}' foo=bar /etc/passwd

Sergiy Kolodyazhnyy
fonte