Замена символов в строках на строки-команды
Дано
Папка в которой находится неизвестное количество текстовых файлов, заранее отсортированных в некую последовательность, например:
f1.txt f2.txt ... fN.txt
Требуется
Склеить эти файлы в один файл и вставить между частями пустые строки
Проблема
Задача должна решаться элементарно. Нужно получить список файлов в переменную:
list="$(ls)"
Убрать лишние пробелы:
list="$(echo $list)"
Заменить все пробелы в строке list на:
" <(echo) "
И создать итоговый файл, к примеру:
cat $list > text.txt
У меня не получается вставить строку " <(echo) " между именами файлов в строке, так чтобы cat работал, как положено.
Задача решена через создание временного файла, содержащего "\n", и вставки его имени вместо строки " <(echo) ", но мне это не нравится.
Мной перепробовано около 40 вариантов с экранизациями, но втыком проблема не решилась.
Вопрос
Имеется строка:
"f1.txt f2.txt ... fN.txt"
Как заменить в ней пробелы на " <(echo) ", чтобы работала конструкция:
cat строка > новый_файл
Ответы (4 шт):
Проще и безопаснее работать либо через find ... | xargs ..., либо в цикле, перебирая нужные файлы по шаблону. Например, если файлы последовательно пронумерованы как указано в вопросе, то:
for ((x=1; x<$N; x++)); do cat f$x.txt; echo; done; cat f$N.txt
Если в нумерации есть пропуски или доступ к некоторым файлам ограничен, то добавляем проверку, что файл существует и читаем [ -r f$x.txt ] && { cat f$x.txt; echo; }
Если номера выровнены по ширине с помощью начальных нулей, то:
for x in $(seq --equal-width 1 $N); do ...
Текст ниже - это факультативный ответ на исходный вопрос. Разберемся, где там ошибка, и как её исправить.
Замену подстроки средствами bash можно делать подстановкой такого вида:
echo "${list//$'\n'/ \<(echo) }"
Здесь:
${...}- указание на границы действия$${x//abc/xyz}- заменить внутри переменнойxвсе подстроки"abc"на"xyz"$'\n'- вставка перевода строки\<- интерпретировать<как обычный, а не управляющий символ
Когда вывод программы ls перенаравлен в конвеер или файл вместо терминала, то разделителем между именами файлов используется перевод строки. В частности, это касается подстановки команды, когда мы присваиваем её вывод переменной list. Поэтому менять нужно не пробелы, а перевод строки (при этом важно учесть, что в именах файлов не должно быть символа перевода строки).
Но даже если вы правильно укажете перенаправление как Here strings, т.е. <<<$(echo), команда cat предпочтет работать с указанными файлами, а не стандартным вводом. А если попробуете схитрить, использовав между файлами - для обращения к стандартному входу, а в конце добавив перенаправление <<<$(echo), то содержимое стандартного входа будет прочитано при первом появлении - и повторные обращения к нему вернут пустую строку. Поставить несколько перенаправлений на каждое появление - не вариант, потому как каждое следующее отменяет предыдущее.
Если всё-таки идти этим путем, то нужно создать временный файл с пустой строкой и вставить его имя между именами текстовых файлов. Например:
x=$(mktemp)
if [ -n "$x" ]
then
echo >$x
list=$(ls)
cat ${list//$'\n'/$'\n'$x$'\n'}
rm $x
fi
unset x
Или же заменить разделитель между файлами на строку "; echo; cat " и применить eval:
eval "cat ${list//$'\n'/; echo; cat }"
В любом случае, этот подход имеет ограничение - в именах файлов не должно быть пробелов и переводов строк.
Если установлена программа GNU Awk, то мы можем воспользоваться определенными в ней специальными паттернами BEGINFILE и ENDFILE для выполнения дополнительных комманд перед или после обработки каждого файла. Например:
awk '{print} ENDFILE {print ""}' ПАПКА/* > новый_файл
Здесь awk - ссылка на gawk. В реализациях original-awk, nawk и mawk это работать не будет.
Версия с удалением последней пустой строки:
awk '{print} ENDFILE {print ""}' ПАПКА/* | head -n -1 > новый_файл
Вроде предельно ясно и в комментариях не нуждается.
$ awk --version | head -1
GNU Awk 5.3.2, API 4.0, PMA Avon 8-g1, (GNU MPFR 4.2.2, GNU MP 6.3.0)
Подставить команду <(echo) в строку co списком файлов с помощью экранизции вполне возможно.
Но после этого, команда cat, как я понимаю, будет восприимать её не как команду, а как строку, т.е. как имя файла, которого не существует в директории.
Мной задача была решена императивно с помощью создания временного файла, содержащего \n и подстановки его имени между именами файлов в листинге.
Второй вариант — использование цикла, как указано в комментариях (так обычно и делают в других языках программирования)
Минималистичный вариант:
$ cat f[1-3]
111
222
333
$ printf '%s\n\n' $(cat f[1-3])
111
222
333