¿Por qué awk no ignora el "espacio" como delimitador?

¿Por qué awk no ignora el "espacio" como delimitador?

Tengo un problema con mi guión.

Preludio En primer lugar, tengo una lista, un archivo de 100 líneas como ese:

100;TEST ONE
101;TEST TWO
...
200;TEST HUNDRED

Cada línea tiene 2 argumentos. Por ejemplo, los argumentos de la primera línea son: "645", "PRUEBA UNO". Entonces el punto y coma es un delimitador.

Necesito poner ambos argumentos en dos variables. Digamos que será $id y $name. Para cada línea, los valores $id y $name serán diferentes. Por ejemplo, para la segunda línea $id = "646" y $name = "TEST TWO".

Después de eso, necesito tomar el archivo de muestra y cambiar las palabras clave predefinidas a los valores $id y $name. El archivo de muestra se ve así:

xxx is yyy

Y como resultado quiero tener 100 archivos con contenido diferente. Cada archivo debe contener datos $id y $name de cada línea. Y debe ser nombrado por su valor $name.

Ahí está mi guión:

#!/bin/bash -x
rm -f output/*

for i in $(cat list)
    do

        id="$(printf "$i" | awk -F ';' '{print $1}')"
        name="$(printf "$i" | awk -F ';' '{print $2}')"

        cp sample.xml output/input.tmp

        sed -i -e "s/xxx/$id/g" output/input.tmp
        sed -i -e "s/yyy/$name/g" output/input.tmp

        mv output/input.tmp output/$name.xml


    done

Entonces, solo trato de leer mi archivo de lista línea por línea. Para cada línea obtengo dos variables y luego las uso para reemplazar las palabras clave (xxx y yyy) del archivo de muestra y luego guardo el resultado.

Pero algo salió mal

Como resultado, solo tengo 1 archivo de salida. Y la depuración tiene mala pinta.

Aquí hay una ventana de depuración con solo 2 líneas en mi archivo de lista. Solo obtuve un archivo de salida. El nombre del archivo es simplemente "PRUEBA" y contiene una cadena: "101 es PRUEBA".

Se esperan dos archivos: "PRUEBA UNO", "PRUEBA DOS" y debe contener "100 es PRUEBA UNO" y "101 es PRUEBA DOS".

captura de pantalla de depuración

Como puede ver, la segunda variable tiene un espacio ("PRUEBA UNO", por ejemplo). Creo que el problema está relacionado con el símbolo especial del espacio, pero no sé por qué. Puse el parámetro -F awk en ";", por lo que awk debe interpretar solo el punto y coma como separador.

¿Que hice mal?

Respuesta1

Si te entiendo correctamente, puedes usar un bucle while y una expansión variable.

while IFS= read -r line; do 
  id="${line%;*}"
  name="${line#*;}"
  cp sample.xml output/input.tmp
  sed -i -e "s/xxx/$id/g" output/input.tmp
  sed -i -e "s/yyy/$name/g" output/input.tmp
  mv output/input.tmp output/"$name".xml
done < file

Según lo propuesto por @steeldriver, aquí hay una opción (más elegante):

while IFS=';' read -r id name; do 
  cp sample.xml output/input.tmp
  sed -i -e "s/xxx/$id/g" output/input.tmp
  sed -i -e "s/yyy/$name/g" output/input.tmp
  mv output/input.tmp output/"$name".xml
done < file

Respuesta2

Citando!!. Falta la cita en esta línea:

mv output/input.tmp output/$name.xml

Debería ser:

mv output/input.tmp output/"$name".xml

para evitar problemas con un nombre de archivo con espacios.

Y la expansión de $(cat list)está siendo dividida (y globalizada) por el caparazón, que también se rompe en espacios.

Quizás puedas cambiar a este script:

#!/bin/bash -x
rm -f output/*

inputfile=output/input.tmp

while read -r line
do
    id=${line%%;*}
    name=${line##*;}

    cp sample.xml "$inputfile"
    sed -i -e "s/xxx/$id/g" "$inputfile"
    sed -i -e "s/yyy/$name/g" "$inputfile"
    mv "$inputfile"  output/"$name".xml; echo

done <list

Respuesta3

La razón por la que su awk no produce los resultados esperados se debe a la forma en que está iterando sobre el archivo. Cuando iteras usando for i in $(cat file), estás iterando sobre palabras (divididas por IFS), no sobre líneas. Para leer un archivo línea por línea, use while read:

while read -r line; do
    ...
done < file

Para obtener más información, consulte las siguientes preguntas frecuentes sobre bash:¿Cómo puedo leer un archivo (flujo de datos, variable) línea por línea (y/o campo por campo)?

Respuesta4

Como enfoque alternativo,puedes hacer este trabajo con awken 1 proceso en lugar de 4 para cada línea. Es más probable que esto sea beneficioso si hay muchas líneas en la lista pero sample.xml es pequeño.

awk -F';' 'FNR==NR{x=x $0 RS; next} 
{t=x; gsub(/xxx/,$1,t); gsub(/yyy/,$2,t); f="output/"$2".xml"; printf "%s",t >f; close(f)}
' sample.xml list
# shown with unnecessary linebreaks for clarity, but you can put it all on one line

Si la lista tiene finales de línea CRLF (también conocido como formato DOS o Windows) como se comentó en su Q, y no puede (fácilmente) o no quiere eliminarlos primero, awk también puede manejar eso; justo después de la segunda {inserción sub(/\r$/,"",$0);(o $2si lo prefiere).

perl también puede hacer esto (perl puede hacer casi todo lo que awk puede hacer), pero de manera un poco más detallada, y aunque perl está comúnmente disponible, no es POSIX como lo es awk.

información relacionada