Сохранение ведущего пробела при чтении>>запись файла строка за строкой в bash
Я пытаюсь перебрать каталог текстовых файлов и объединить их в один документ. Это отлично работает, но текстовые файлы содержат фрагменты кода, и все мое форматирование сворачивается влево. Все ведущие пробелы на линии удалены.
#!/bin/sh
OUTPUT="../best_practices.textile"
FILES="../best-practices/*.textile"
for f in "$FILES"
do
echo "Processing $f file..."
echo "">$OUTPUT
cat $f | while read line; do
echo "$line">>$OUTPUT
done
echo >>$OUTPUT
echo >>$OUTPUT
done
Я, по общему признанию, Баш нуб, но после поиска повсюду я не мог найти правильное решение. Видимо Баш ненавидит пробел в целом.
5 ответов
вместо:
cat $f | while read line; do
echo "$line">>$OUTPUT
done
этого:
cat $f >>$OUTPUT
(Если есть причина, по которой вам нужно делать вещи строка за строкой, было бы хорошо включить это в вопрос.)
как указывали другие, использование cat или awk вместо цикла read-echo-гораздо лучший способ сделать это-избежать проблемы обрезки пробелов (и нескольких других, на которые вы не наткнулись), работает быстрее, и, по крайней мере, с cat, это просто более чистый код. Тем не менее, я хотел бы сделать попытку заставить цикл read-echo работать правильно.
во-первых, проблема обрезки пробелов: команда read автоматически обрезает ведущие и конечные пробелы; это может быть исправлено изменив определение пробела, установив для переменной IFS значение blank. Кроме того, read предполагает, что обратная косая черта в конце строки означает, что следующая строка является продолжением и должна быть склеена с этой; чтобы исправить это, используйте его-R (raw) флаг. Третья проблема здесь заключается в том, что многие реализации echo интерпретируют escape-последовательности в строке (например, они могут превратить \n в фактическую новую строку); чтобы исправить это, используйте printf. Наконец, как общее правило гигиены сценариев, вы не следует использовать cat, когда вам на самом деле не нужно; вместо этого используйте перенаправление ввода. С этими изменениями внутренний цикл выглядит следующим образом:
while IFS='' read -r line; do
printf "%s\n" "$line">>$OUTPUT
done <$f
...есть также пара других проблем с окружающим скриптом: строка, которая пытается определить файлы как список доступных .текстильные файлы имеют кавычки вокруг него, то есть он никогда не расширяется в фактический список файлов. Лучший способ сделать это-использовать массив:
FILES=(../best-practices/*.textile)
...
for f in "${FILES[@]}"
(и все вхождения $f должен быть в двойных кавычках, если в любом из имен файлов есть пробелы или другие забавные символы в них-должен действительно сделать это с $OUTPUT, хотя, поскольку это определено в скрипте, на самом деле безопасно остановиться.)
наконец,echo "">$OUTPUT
в верхней части циклических файлов, которые будут стирать выходной файл каждый раз (т. е. в конце, он содержит только последний .текстильный файл); это должно быть перемещено до цикла. Я не уверен, что намерение здесь нужно было поставить одну пустую строку в начале файла или три пустые строки между файлами (и одну в начале и две в конце), поэтому я не уверен, что именно подходит для замены. Во всяком случае, вот что я могу сделать после исправления всех этих проблем:
#!/bin/sh
OUTPUT="../best_practices.textile"
FILES=(../best-practices/*.textile)
: >"$OUTPUT"
for f in "${FILES[@]}"
do
echo "Processing $f file..."
echo >>"$OUTPUT"
while IFS='' read -r line; do
printf "%s\n" "$line">>"$OUTPUT"
done <"$f"
echo >>"$OUTPUT"
echo >>"$OUTPUT"
done
это слишком дорогой способ объединения файлов.
cat ../best-practices/*.textile > ../best_practices.textile
Если вы хотите добавить пустую строку( newline) в каждый файл по мере объединения, используйте awk
awk 'FNR==1{print "">"out.txt"}{print > "out.txt" }' *.textile
или
awk 'FNR==1{print ""}{print}' file* > out.txt
Это позволяет вам чередовать новые строки между каждым входным файлом, как вы сделали в исходном скрипте:
for f in $FILES; do echo -ne '\n\n' | cat "$f" -; done > $OUTPUT
отметим, что $FILES
не кавычки для этого работать (в противном случае дополнительные новые строки появляются только один раз в конце всех выходных данных), но $f
должно быть указано для защиты пробелов в именах файлов, если они существуют.
правильный ответ, ИМО, является этой, воспроизводится ниже:
while IFS= read line; do
check=${line:0:1}
done < file.txt
обратите внимание, что он позаботится о ситуациях, когда вход передается из другой команды, а не только из фактического файла.
обратите внимание, что вы можете упростить перенаправление, как показано ниже.
#!/bin/bash
OUTPUT="../best_practices.textile"
FILES="../best-practices/*.textile"
for f in "$FILES"
do
echo "Processing $f file..."
{
echo
while IFS= read line; do
echo "$line"
done < $f
echo
echo;
} > $OUTPUT
done