2011-09-09 2 views
30

Я хотел бы удалить все пустые строки из файла, но только когда они находятся в конце/начале файла (то есть, если в начале нет непустых строк, а если в них нет непустых строк, то в конце.)Удаление конечных/начальных строк с помощью sed, awk, tr и друзей

Возможно ли это за пределами полнофункционального языка сценариев, такого как Perl или Ruby? Я бы предпочел сделать это с sed или awk, если это возможно. В принципе, любой легкий и широко доступный инструмент UNIX-y будет в порядке, особенно тот, который я могу узнать о нем быстрее (Perl, следовательно, не входит в комплект.)

ответ

43

От Useful one-line scripts for sed:

# Delete all leading blank lines at top of file (only). 
sed '/./,$!d' file 

# Delete all trailing blank lines at end of file (only). 
sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' file 

Поэтому, чтобы удалить обе начальные и конечные пустые строки из файла, может объединить вышеуказанный ком Mands в:

sed -e :a -e '/./,$!d;/^\n*$/{$d;N;};/\n$/ba' file 
+0

В соответствии с запиской на этом сайте сценарий с закрывающейся строкой не будет работать для gsed 3.02. * Это будет работать: 'sed -e: a -e '/^\ n * $/{ $ d; N; ba '-e'} '' – BryanH

+0

Если это не удается, попробуйте выполнить dos2unix раньше. Эта ссылка является таким полезным полным набором примеров sed. –

+0

Это не подходит для больших файлов. – ExceptionSlayer

1

В bash, используя cat, wc, grep, sed, хвост и голова:

# number of first line that contains non-empty character 
i=`grep -n "^[^\B*]" <your_file> | sed -e 's/:.*//' | head -1` 
# number of hte last one 
j=`grep -n "^[^\B*]" <your_file> | sed -e 's/:.*//' | tail -1` 
# overall number of lines: 
k=`cat <your_file> | wc -l` 
# how much empty lines at the end of file we have? 
m=$(($k-$j)) 
# let strip last m lines! 
cat <your_file> | head -n-$m 
# now we have to strip first i lines and we are done 8-) 
cat <your_file> | tail -n+$i 

Человек, безусловно, стоит изучить «настоящий» язык программирования, чтобы избежать этого безобразия!

+0

Ну * это * часть достаточно легко с sed! Позвольте мне поиграть с ним и попытайтесь вернуться сюда с завершенной командой. Благодаря! – ELLIOTTCABLE

+0

На самом деле, это не будет работать для последних строк, потому что оно удаляет * все * новые строки на этапе grep, тем самым отбрасывая счет в конце./= – ELLIOTTCABLE

+0

Нет: после выполнения этих команд у вас все еще есть исходный файл. Вторая команда печатает все незаполненные запятые с номерами строк. Таким образом, у вас будет номер последнего не пустого. –

1

Использование bash

$ filecontent=$(<file) 
$ echo "${filecontent/$'\n'}" 
+0

Это удаляет только пустую строку с самого начала, и ни один из них не заканчивается. –

+3

@me_and: Хотя вы правы в том, что удаляете только пустую строку _one_ с начала, это фактически _does_ удаляет все завершающие символы новой строки, потому что подстановка команд ('$ (<файл)') делает это _implicitly_. – mklement0

+0

@ mklement0: Да, так оно и есть. Узнавайте новую вещь каждый день! –

2

с использованием AWK:

awk '{a[NR]=$0;if($0 && !s)s=NR;} 
    END{e=NR; 
     for(i=NR;i>1;i--) 
      if(a[i]){ e=i; break; } 
     for(i=s;i<=e;i++) 
      print a[i];}' yourFile 
+0

Интересно, есть ли способ уменьшить/рефакторинг, чтобы обрабатывать его за один проход? (Я не очень хорошо знаком с awk, я могу прочитать то, что вы написали, но я не уверен, как его реорганизовать.) – ELLIOTTCABLE

+0

в основном это однострочная команда, единственной динамической частью является «yourFile», которая является именем файла, который вы хотите обработать. почему вы нуждаетесь в сокращении/рефакторе? – Kent

+1

Потому что он длинный и сложный, даже если ему не нужны новые символы? Несколько для циклов, несколько операторов; излишняя сложность. (= – ELLIOTTCABLE

3

вот один проход решения в AWK: он не начинает печать, пока он не видит, не пустую строку, и когда он видит пустую строку, она не помнит его до следующей непустой строки

awk ' 
    /[[:graph:]]/ { 
     # a non-empty line 
     # set the flag to begin printing lines 
     p=1  
     # print the accumulated "interior" empty lines 
     for (i=1; i<=n; i++) print "" 
     n=0 
     # then print this line 
     print 
    } 
    p && /^[[:space:]]*$/ { 
     # a potentially "interior" empty line. remember it. 
     n++ 
    } 
' filename 

Обратите внимание, из-за механизм, я использую, чтобы рассмотреть пустые/непустые строки (с [[:graph:]] и /^[[:space:]]*$/), внутренние линии только с пробелами будут усечены, чтобы стать действительно пустыми.

+0

+1 для однопроходное однокомпонентное решение, которое также эффективно с точки зрения памяти (хотя, как уже отмечалось, его поведение немного отличается от того, что было предложено). – mklement0

10

Так что я собираемся брать часть ответа @ кендыря для этого, так что sed линии для удаления пустых строк так коротка ...

tac is part of coreutils и изменяет файл. Так сделайте это дважды:

tac file | sed -e '/./,$!d' | tac | sed -e '/./,$!d' 

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

+0

Я оставляю исходный, но это, безусловно, элегантно. чтобы узнать о 'tac'. Какое милое имя.: D – ELLIOTTCABLE

+1

Существует замечательный вопрос: если файл не имеет завершающего' \ n', последняя строка не будет обрабатываться правильно: try 'tac <(printf 'a \ nb') '. Возможно, это неправильное поведение, также влияет на эквивалент OSX tac' 'tail -r'. – mklement0

2

Как упоминалось в another answer, tac is part of coreutils и отменяет файл. Комбинируя идею сделать это дважды the fact that command substitution will strip trailing new lines, мы получаем

echo "$(echo "$(tac "$filename")" | tac)" 

, который не зависит от sed. Вы можете использовать echo -n, чтобы удалить оставшуюся конечную новую линию.

+0

+1 для (относительной) простоты (хотя и за счет эффективности); Версия OSX (где 'tac' недоступна по умолчанию):' echo '$ (echo "$ (tail -r" $ filename ")" | tail -r) "' Я провел тесты для сравнения относительной скорости выполнения с 1-миллионный файл для нескольких ответов (не обращал внимания на использование памяти); ранее означает более быстрое: OSX 10.10: sed (dogbane) mklement0

+1

Существует замечательный вопрос: если файл не имеет завершающего '\ n', последняя строка не будет обрабатываться правильно: try' echo '$ (echo "$ (printf' a \ nb '| tac) "| tac)". Это присуще - возможно, ошибочному поведению 'tac' (а также' tail -r' на OSX) с вводом, не заканчивающимся '\ n'. – mklement0

+0

Использование 'echo '$ (echo" $ (cat "$ filename") "| tac)" | tac' исправляет край кейс эта @ mklement0 упомянутый. – rivy

0

A bash решение.

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

[[ $(<file) =~ ^$'\n'*(.*)$ ]] && echo "${BASH_REMATCH[1]}" 
  • $(<file) читает весь файл и обрезает задний новой строки, так как команда подстановки ($(....)) неявно делает это.
  • =~ является регулярных выражений соответствия оператора в Bash и =~ ^$'\n'*(.*)$ необязательно соответствует любым ведущих переноса строк (с жадностью), и захватывает все, что приходит после. Обратите внимание на потенциально запутывающий $'\n', который вставляет литерал новой строки, используя ANSI C quoting, потому что escape-последовательность \n не поддерживается.
  • Обратите внимание, что данный конкретный регулярный номер всегда соответствует, поэтому команда после && равна всегда выполнено.
  • Специальная переменная массива BASH_REMATCH rematch содержит результаты последнего совпадения регулярных выражений, а элемент массива [1] содержит то, что захвачено (первое и единственное) вложенное подвыражение (группа захвата), которое является входной строкой с лидирующей линией переноса. Чистый эффект заключается в том, что ${BASH_REMATCH[1]} содержит содержимое входного файла с разделенными как ведущими, так и завершающими символами новой строки.
  • Обратите внимание, что печать с помощью echo добавляет одну конечную новую строку. Если вы хотите этого избежать, используйте вместо этого echo -n (или используйте более портативный printf '%s').
0

Я хотел бы представить еще один вариант для поглазеть v4.1 +

result=($(gawk ' 
    BEGIN { 
     lines_count   = 0; 
     empty_lines_in_head = 0; 
     empty_lines_in_tail = 0; 
    } 
    /[^[:space:]]/ { 
     found_not_empty_line = 1; 
     empty_lines_in_tail = 0; 
    } 
    /^[[:space:]]*?$/ { 
     if (found_not_empty_line) { 
      empty_lines_in_tail ++; 
     } else { 
      empty_lines_in_head ++; 
     } 
    } 
    { 
     lines_count ++; 
    } 
    END { 
     print (empty_lines_in_head " " empty_lines_in_tail " " lines_count); 
    } 
' "$file")) 

empty_lines_in_head=${result[0]} 
empty_lines_in_tail=${result[1]} 
lines_count=${result[2]} 

if [ $empty_lines_in_head -gt 0 ] || [ $empty_lines_in_tail -gt 0 ]; then 
    echo "Removing whitespace from \"$file\"" 
    eval "gawk -i inplace ' 
     { 
      if (NR > $empty_lines_in_head && NR <= $(($lines_count - $empty_lines_in_tail))) { 
       print 
      } 
     } 
    ' \"$file\"" 
fi 
0

@dogbane имеет приятный простой ответ для удаления ведущих пустых строк. Вот простая команда awk, которая удаляет только завершающие строки. Используйте это с командой sed @ dogbane, чтобы удалить как ведущие, так и завершающие пробелы.

awk '{ LINES=LINES $0 "\n"; } /./ { printf "%s", LINES; LINES=""; }' 

Это довольно просто в эксплуатации.

  • Добавьте каждую строку в буфер, когда мы ее прочитаем.
  • Для каждой строки, содержащей символ, распечатайте содержимое буфера и очистите его.

Таким образом, единственные вещи, которые буферизуются и никогда не отображаются, - это любые завершающие пробелы.

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

2

Вот адаптированная версия sed, которая также считает «пустыми» эти строки только с пробелами и вкладками.

sed -e :a -e '/[^[:blank:]]/,$!d; /^[[:space:]]*$/{ $d; N; ba' -e '}' 

Это в основном принятый ответ версия (с учетом BryanH комментарий), но точка . в первой команде было изменено на [^[:blank:]] (ничего не пусто) и \n внутри второго адреса команды было изменено на [[:space:]], чтобы новые строки, пробелы - вкладки.

Альтернативная версия, без использования классов POSIX, но ваш sed должен поддерживать вставку \t и \n внутри […]. GNU sed делает, BSD sed не делает.

sed -e :a -e '/[^\t ]/,$!d; /^[\n\t ]*$/{ $d; N; ba' -e '}' 

Тестирование:

prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' 



foo 

foo 



prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' | sed -n l 
$ 
\t $ 
$ 
foo$ 
$ 
foo$ 
$ 
\t $ 
$ 
prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' | sed -e :a -e '/[^[:blank:]]/,$!d; /^[[:space:]]*$/{ $d; N; ba' -e '}' 
foo 

foo 
prompt$ 
1

Для эффективной нерекурсивна версии задней Newlines полосы (в том числе «белых» персонажей) Я разработал этот sed сценарий.

sed -n '/^[[:space:]]*$/ !{x;/\n/{s/^\n//;p;s/.*//;};x;p;}; /^[[:space:]]*$/H' 

Он использует буфер хранения для хранения всех пустых строк и распечатывает их только после того, как находит непустую строку. Если кто-то хочет только новые строки, это достаточно, чтобы избавиться от двух [[:space:]]* частей:

sed -n '/^$/ !{x;/\n/{s/^\n//;p;s/.*//;};x;p;}; /^$/H' 

Я пытался простое сравнение производительности с хорошо известным рекурсивной сценарий

sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' 

на 3MB файл с 1 МБ случайных пустых строк вокруг случайного текста base64.

shuf -re 1 2 3 | tr -d "\n" | tr 123 " \t\n" | dd bs=1 count=1M > bigfile 
base64 </dev/urandom | dd bs=1 count=1M >> bigfile 
shuf -re 1 2 3 | tr -d "\n" | tr 123 " \t\n" | dd bs=1 count=1M >> bigfile 

Скрипт потоковой передачи занимает примерно 0,5 секунды, рекурсивный не заканчивается через 15 минут. Win :)

Для полноты ответа ответные строки, снятые с sed-скрипта, уже отлично передаются. Используйте наиболее подходящий для вас.

sed '/[^[:blank:]]/,$!d' 
sed '/./,$!d' 
Смежные вопросы