Сохранение ведущего пробела при чтении>>запись файла строка за строкой в 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