Сохранение начального пробела при чтении >> запись файла построчно в bash - PullRequest
6 голосов
/ 30 октября 2009

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

#!/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

Я, по общему признанию, новичок, но после поиска высоко и низко я не мог найти правильное решение. Очевидно, BASH ненавидит лидирующие пробелы в целом.

Ответы [ 5 ]

40 голосов
/ 31 октября 2009

Как уже отмечали другие, использование cat или awk вместо цикла read-echo - гораздо лучший способ сделать это - избежать проблемы обрезки пробелов (и нескольких других, с которыми вы не сталкивались), работает быстрее, и, по крайней мере, с кошкой, это просто более чистый код. Тем не менее, я хотел бы попытаться заставить цикл чтения-эхо работать правильно.

Во-первых, проблема с пропуском пробелов: команда чтения автоматически обрезает начальные и конечные пробелы; это можно исправить, изменив определение пробела, установив пустую переменную IFS. Кроме того, read предполагает, что обратная косая черта в конце строки означает, что следующая строка является продолжением и должна быть соединена вместе с этой; чтобы исправить это, используйте флаг -r (raw). Третья проблема здесь заключается в том, что многие реализации echo интерпретируют escape-последовательности в строке (например, они могут превращать \ n в фактическую новую строку); чтобы исправить это, используйте printf. Наконец, как общее правило гигиены сценариев, вы не должны использовать cat, когда вам это не нужно; вместо этого используйте перенаправление ввода. С этими изменениями внутренний цикл выглядит так:

while IFS='' read -r line; do 
  printf "%s\n" "$line">>$OUTPUT
done <$f

... есть также несколько других проблем с окружающим сценарием: строка, которая пытается определить FILES как список доступных .textile файлов, имеет кавычки, означающие, что она никогда не будет расширена в фактический список файлов , Лучший способ сделать это - использовать массив:

FILES=(../best-practices/*.textile)
...
for f in "${FILES[@]}"

(и все вхождения $ f должны быть в двойных кавычках на тот случай, если в именах файлов есть пробелы или другие забавные символы - действительно следует делать это и с $ OUTPUT, хотя, поскольку это определено в сценарии, на самом деле безопасно оставить.)

Наконец, есть echo "">$OUTPUT в верхней части зацикленных файлов, которые будут стирать выходной файл каждый раз до конца (т.е. в конце он содержит только последний файл .textile); это должно быть перемещено до цикла. Я не уверен, было ли здесь намерение поставить одну пустую строку в начале файла или три пустые строки между файлами (и одну в начале и две в конце), поэтому я не уверен, что именно соответствующая замена есть. Во всяком случае, вот что я могу сделать после устранения всех этих проблем:

#!/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
4 голосов
/ 30 октября 2009

это слишком дорогой способ объединения файлов.

cat ../best-practices/*.textile >  ../best_practices.textile

если вы хотите добавить пробел (перевод строки) в каждый файл при объединении, используйте awk

awk 'FNR==1{print "">"out.txt"}{print > "out.txt" }' *.textile

OR

awk 'FNR==1{print ""}{print}' file* > out.txt
3 голосов
/ 30 октября 2009

Вместо:

cat $f | while read line; do 
    echo "$line">>$OUTPUT
done

Сделайте это:

cat $f >>$OUTPUT

(Если есть причина, по которой вам нужно что-то делать построчно, было бы хорошо включить это в вопрос.)

1 голос
/ 30 октября 2009

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

for f in $FILES; do echo -ne '\n\n' | cat "$f" -; done > $OUTPUT

Обратите внимание, что $FILES не заключено в кавычки, чтобы это работало (в противном случае дополнительные символы новой строки появляются только один раз в конце всех выходных данных), но $f необходимо заключать в кавычки для защиты пробелов в именах файлов, если они существуют. *

0 голосов
/ 09 декабря 2015

Правильный ответ, IMO, это это , воспроизведенный ниже:

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
...