¿Por qué awk se detiene y espera si el nombre de archivo contiene = y cómo solucionarlo?

Respuestas:

19

Como dice Chris , los argumentos del formulario variablename=anythingse tratan como asignación de variables (que se realizan en el momento en que se procesan los argumentos en lugar de los (más nuevos) -v var=valueque se realizan antes de las BEGINdeclaraciones) en lugar de los nombres de los archivos de entrada.

Eso puede ser útil en cosas como:

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

Donde puede especificar un archivo diferente FS/ RSpor archivo. También se usa comúnmente en:

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

Cuál es una versión más segura de:

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

(que no funciona si file1está vacío)

Pero eso se interpone cuando tienes archivos cuyo nombre contiene =caracteres.

Ahora, eso es solo un problema cuando lo que queda del primero =es un awknombre de variable válido .

Lo que constituye un nombre de variable válido en awkes más estricto que en sh.

POSIX requiere que sea algo como:

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

Con solo caracteres del juego de caracteres portátil. Sin embargo, /usr/xpg4/bin/awkal menos Solaris 11 no es compatible en ese sentido y permite cualquier carácter alfabético en la configuración regional en nombres de variables, no solo a-zA-Z.

Por lo tanto, un argumento como x+y=fooo =baro ./foo=bartodavía se trata como un nombre de archivo de entrada y no una asignación, ya que lo que queda del primero =no es un nombre de variable válido. Un argumento como Stéphane=Chazelas.txtmay o may, dependiendo de la awkimplementación y el entorno local.

Es por eso que con awk, se recomienda usar:

awk '...' ./*.txt

en lugar de

awk '...' *.txt

por ejemplo, para evitar el problema si no puede garantizar que el nombre de los txtarchivos no contendrá =caracteres.

Además, tenga en cuenta que un argumento como -vfoo=bar.txtpuede ser tratado como una opción si usa:

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

(también se aplica a awk '{code}' -vfoo=bar.txtlas awkversiones de busybox anteriores a 1.28.0, consulte el informe de error correspondiente ).

Una vez más, el uso ./*.txtfunciona alrededor de eso (el uso de un ./prefijo también ayuda con un archivo llamado -que de otro modo awkentiende como entrada estándar ).

Por eso también

#! /usr/bin/awk -f

los shebangs realmente no funcionan. Si bien los var=valueque se pueden solucionar arreglando los ARGVvalores (agregue un ./prefijo) en una BEGINdeclaración:

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

Eso no ayudará con las opciones, ya que esas son vistas por ellos awky no por el awkscript.

Un problema cosmético potencial con el uso de ese ./prefijo es que termina en FILENAME, pero siempre puede usar substr(FILENAME, 3)para quitarlo si no lo desea.

La implementación de GNU awksoluciona todos esos problemas con su -Eopción.

Después -E, gawk espera solo la ruta del awkscript (donde -todavía significa stdin) y luego una lista de rutas de archivos de entrada solamente (y allí, ni siquiera -se trata especialmente).

Está especialmente diseñado para:

#! /usr/bin/gawk -E

shebangs donde la lista de argumentos siempre son archivos de entrada (tenga en cuenta que aún puede editar esa ARGVlista en una BEGINdeclaración).

También puedes usarlo como:

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

Lo usamos -Econ un script vacío ( /dev/null) solo para asegurarnos de que los *.txtposteriores se traten siempre como archivos de entrada, incluso si contienen =caracteres.

Stéphane Chazelas
fuente
No veo cómo la ruta explícita que termina en FILENAME es un problema. O bien, el script awk es general, en cuyo caso debe manejar todo tipo de rutas que terminan en FILENAME (incluidas, entre otras ../foo, las /path/to/foorutas que están en una codificación diferente), en cuyo caso substr(FILENAME,3)no será suficiente, o es una secuencia de comandos de una sola toma en la que el usuario básicamente sabe cuáles son los nombres de archivo, en cuyo caso probablemente no debería molestarse con ninguno de ellos que contenga =ninguno ;-)
mosvy
2
@mosvy No creo que indique tanto que ./es un problema, pero que puede ser indeseable bajo ciertas condiciones, como casos en los que el nombre de archivo debe incluirse en la salida, en cuyo caso ./debe ser redundante e innecesario, por lo que Tendré que deshacerme de él de alguna manera. Aquí hay al menos un ejemplo . En cuanto a que el usuario sepa qué son los nombres de archivo, bueno, en este caso también sabemos qué nombre de archivo es, pero =aún se interpone en el proceso adecuado. Entonces, el liderazgo puede -interponerse en el camino
Sergiy Kolodyazhnyy
@mosvy, sí, la idea es que quieras usar el ./prefijo para evitar esa característica awk(incorrecta), pero luego terminas con un ./resultado en el que quizás quieras quitar. ¿ Ves cómo verificar si la primera línea del archivo contiene una cadena específica? como ejemplo.
Stéphane Chazelas
No es solo el local (relativo a este directorio) ./sino también el global (ruta absoluta) lo /que hace que awk interprete el argumento como un archivo.
Isaac
21

En la mayoría de las versiones de awk, los argumentos después del programa a ejecutar son:

  1. Un archivo
  2. Una asignación del formulario x=y

Como su nombre de archivo se interpreta como el caso n. ° 2, awk todavía está esperando que se lea algo en stdin (ya que no percibe que se haya pasado ningún nombre de archivo).

Portablemente, este comportamiento está documentado en POSIX :

Cualquiera de los siguientes dos tipos de argumentos se pueden mezclar:

  • archivo: un nombre de ruta de un archivo que contiene la entrada a leer, que coincide con el conjunto de patrones en el programa. Si no se especifican operandos de archivo, o si un operando de archivo es '-', se utilizará la entrada estándar.
  • asignación: un operando que comienza con un carácter de subrayado o alfabético del conjunto de caracteres portátil (consulte la tabla en el volumen de Definiciones básicas de IEEE Std 1003.1-2001, Sección 6.1, Conjunto de caracteres portátil), seguido de una secuencia de caracteres de subrayado, dígitos, y el alfabeto del juego de caracteres portátil, seguido del carácter '=', especificará una asignación de variable en lugar de un nombre de ruta.

Como tal, de forma portátil, tiene algunas opciones (es probable que el n. ° 1 sea el menos intrusivo):

  1. Use awk ... ./my=file, que evita esto ya .que no es "un carácter de subrayado o alfabético del conjunto de caracteres portátil".
  2. Ponga el archivo en stdin usando awk ... < my=file. Sin embargo, esto no funciona bien con varios archivos.
  3. Haga un enlace fijo al archivo temporalmente y úselo. Puede hacer algo como ln my=file my_file, y luego usarlo my_filenormalmente. No se realizará ninguna copia, y ambos archivos estarán respaldados por los mismos datos y metadatos de inodo. Después de usarlo, es seguro eliminar el enlace creado ya que el número de referencias al inodo seguirá siendo mayor que 0.
Chris Down
fuente
66
No ./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). Esto debería ser portátil porque ./myno es un nombre de variable válido, por lo que no debe analizarse de esa manera.
Stephen Harris
2
Como dice el texto POSIX, el problema es solo cuando el primero =está precedido por un carácter de subrayado o alfabético del conjunto de caracteres portátil (consulte la tabla en el volumen de Definiciones básicas de IEEE Std 1003.1-2001, Sección 6.1, Conjunto de caracteres portátil), seguido de una secuencia de guiones bajos, dígitos y alfabéticos del juego de caracteres portátil . así que una ruta de archivo como ++foo=bar.txto =fooo ./foo=barestán todas bien como eso .o +no es a [_a-zA-Z].
Stéphane Chazelas
1
@SergiyKolodyazhnyy awk es externo al shell, por lo que no importa cuál use. ./my=fileserá pasado literalmente.
Chris Down
1
@SergiyKolodyazhnyy, lo mismo para awk '{print $1,$2}' /etc/passwd. El punto es que hacer que el shell abra el archivo en lugar de awk no hace ninguna diferencia en cuanto a si lo hace buscable o no. En realidad, en awk '{exit}' < /etc/passwd, esperaría awkvolver al final del primer registro exitpara asegurarse de que deja la posición dentro de stdin allí. POSIX requiere eso. /usr/xpg4/bin/awklo hace en Solaris, pero gawktampoco mawkparece hacerlo en GNU / Linux.
Stéphane Chazelas
3
@mosvy, vea la sección INPUT ARCHIVOS en pubs.opengroup.org/onlinepubs/9699919799/utilities/… Es útil en una serie de patrones de uso que solo tienen sentido con archivos normales como cuando desea truncar un archivo o escribir datos en él en una posición identificada de awkesa manera.
Stéphane Chazelas
3

Para citar la documentación de gawk (énfasis agregado):

Cualquier argumento adicional en la línea de comando normalmente se trata como archivos de entrada para ser procesados ​​en el orden especificado. Sin embargo, un argumento que tiene la forma var = value, asigna el valor del valor a la variable var; no especifica un archivo en absoluto.

¿Por qué el comando se detiene y espera? Debido a que en el formulario awk 'processing_script_here' my=file.txt no hay un archivo especificado por la definición anterior, my=file.txtse interpreta como asignación de variable, y si no hay un archivo definido awk, leerá stdin (también evidente a partir de lo straceque muestra que awk en dicho comando está esperando en read(0,'...)syscall.

Esto también está documentado en las especificaciones de POSIX awk , consulte la sección OPERANDS y parte de las asignaciones de eso)

La asignación variable es evidente en awk '{print foo}' foo=bar /etc/passwdque el valor de foose imprime para cada línea en / etc / passwd. ./foo=barSin embargo, la ruta específica o completa funciona.

Tenga en cuenta que se ejecuta straceen awk '1' foo=barasí como la comprobación con cat foo=barespectáculos que esto es cuestión awk-específica, y execve sí muestra nombre de archivo como argumento pasado, por lo que los depósitos no tienen nada que ver con la asignación de variables env en este caso.

Además, tenga en cuenta que awk '...script...' foo=barno provocará la creación de variables de entorno por shell, ya que las asignaciones de variables de entorno deben preceder a un comando para que surta efecto. Consulte las Reglas de gramática de shell POSIX , punto número 7. Además, esto se puede verificar a través deawk '{print ENVIRON["foo"]}' foo=bar /etc/passwd

Sergiy Kolodyazhnyy
fuente