У меня проблема со сценарием.
Прелюдия Во-первых, у меня есть список, файл из 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.