Замена символов в строках на строки-команды

Дано

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

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 шт):

Автор решения: Vitalizzare

Проще и безопаснее работать либо через 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 }"

В любом случае, этот подход имеет ограничение - в именах файлов не должно быть пробелов и переводов строк.

→ Ссылка
Автор решения: arhat

Если установлена программа 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)
→ Ссылка
Автор решения: Jdvn622

Подставить команду <(echo) в строку co списком файлов с помощью экранизции вполне возможно.

Но после этого, команда cat, как я понимаю, будет восприимать её не как команду, а как строку, т.е. как имя файла, которого не существует в директории.

Мной задача была решена императивно с помощью создания временного файла, содержащего \n и подстановки его имени между именами файлов в листинге.

Второй вариант — использование цикла, как указано в комментариях (так обычно и делают в других языках программирования)

→ Ссылка
Автор решения: Ivan

Минималистичный вариант:

$ cat f[1-3]
111
222
333

$ printf '%s\n\n' $(cat f[1-3])
111

222

333
→ Ссылка