Почему awk не игнорирует «пробел» как разделитель?

Почему awk не игнорирует «пробел» как разделитель?

У меня проблема со сценарием.

Прелюдия Во-первых, у меня есть список, файл из 100 строк, например:

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

Каждая строка имеет 2 аргумента. Например, аргументы первой строки: "645", "TEST ONE". Таким образом, точка с запятой является разделителем.

Мне нужно поместить оба аргумента в две переменные. Допустим, это будут $id и $name. Для каждой строки значения $id и $name будут разными. Например, для второй строки $id = "646" и $name = "TEST TWO".

После этого мне нужно взять файл-образец и изменить предопределенные ключевые слова на значения $id и $name. Файл-образец выглядит так:

xxx is yyy

И в результате я хочу иметь 100 файлов с разным содержимым. Каждый файл должен содержать данные $id и $name из каждой строки. И он должен быть назван по значению $name.

Вот мой сценарий:

#!/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

Итак, я просто пытаюсь прочитать свой файл списка построчно. Для каждой строки я получаю две переменные, а затем использую их для замены ключевых слов (xxx и yyy) из файла-образца, а затем сохраняю результат.

Но что-то пошло не так.

В результате у меня только 1 выходной файл. И отладка выглядит плохо.

Вот окно отладки, в котором всего 2 строки в моем файле списка. Я получил только один выходной файл. Имя файла просто "TEST", и он содержит строку: "101 is TEST".

Ожидаются два файла: «TEST ONE», «TEST TWO», и он должен содержать «100 is TEST ONE» и «101 is TEST TWO».

отладочный снимок экрана

Как вы видите, во второй переменной есть пробел (например, «TEST ONE»). Я думаю, что проблема связана со специальным символом пробела, но не знаю, почему. Я установил параметр -F awk в «;», поэтому awk должен интерпретировать только точку с запятой как разделитель!

Что я сделал не так?

решение1

Если я правильно вас понял, вы можете использовать цикл while и расширение переменной

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

Как предложил @steeldriver, вот (более элегантный) вариант:

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

решение2

Цитата !!. Цитата в этой строке отсутствует:

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

Должен быть:

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

чтобы избежать проблем с именем файла, содержащим пробелы.

И расширение $(cat list)разделяется (и дробится) оболочкой, которая также распадается на пространства.

Возможно, вы можете перейти на этот скрипт:

#!/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

решение3

Причина, по которой ваш awk не выдает ожидаемых результатов, заключается в способе, которым вы выполняете итерацию по файлу. При итерации с использованием for i in $(cat file), вы выполняете итерацию по словам (разделенным IFS), а не по строкам. Чтобы прочитать файл построчно, используйте while read:

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

Для получения дополнительной информации см. следующий раздел часто задаваемых вопросов по bash:Как можно прочитать файл (поток данных, переменную) построчно (и/или по полю)?

решение4

В качестве альтернативного подхода,вы можете сделать эту работу с помощью awkв 1 процессе, а не в 4 для каждой строки. Это, скорее всего, будет полезно, если в списке много строк, но sample.xml небольшой.

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

Если список содержит окончания строк CRLF (формат DOS или Windows), как прокомментировано в вашем Q, и вы не можете (легко) или не хотите сначала удалить их, awk может справиться и с этим; сразу после второй {вставки sub(/\r$/,"",$0);(или $2если вам так удобнее).

perl тоже может это делать (perl может делать почти все, что может awk), но немного более многословно, и хотя perl широко доступен, он не соответствует POSIX, как awk.

Связанный контент