Как найти файлы, которые не имеют пустой строки в конце?

9

У меня есть файлы в подкаталогах текущего каталога, которые могут иметь или не иметь новые строки в конце; Как мне найти файлы, в конце которых нет новой строки?

Я пробовал это:

find . -name '*.styl' | while read file; do
    awk 'END{print}' $file | grep -E '^$' > /dev/null || echo $file;
done

но это не работает awk 'END{print}' $fileпечатает строку перед пустой новой строкой, так же, как tail -n 1 $file.

jcubic
источник
@don_crissti Мне нужны файлы, у которых нет завершающей пустой строки.
jcubic
2
Могу я узнать причину, по которой вам нужно найти эти файлы? Я предполагаю, что это связано с тем фактом, что текстовые файлы в Unix должны заканчиваться символом новой строки (например, vi "почти бесшумно" добавит один, когда вы сохраните), а несколько (тексто-ориентированных) команд будут игнорировать последняя строка, если она не заканчивается новой строкой (wc, iirc .... но есть и другие). И это может помочь
Оливье Дюлак
awk 'END{print}' $file : это полностью игнорирует содержимое $ file, и после окончания анализа всех файлов, содержащихся в $ file, добавляет новую строку. Поскольку это единственная вещь, которую печатает команда awk, ее можно заменить на: printf '\n'(без ментино $ file вообще) и сделать то же самое. Я думаю, что это НЕ то, к чему вы стремились (то есть: напечатать последнюю строку файла?)
Оливье Дюлак
@don_crissti: если последний символ файла не является новой строкой, то этот файл не является строго текстовым файлом Unix TEXT. см .: unix.stackexchange.com/a/263919/27616 . обратите внимание, что многие текстовые команды (например, wc) просто игнорируют эту последнюю «строку», если она не завершается символом новой строки
Оливье Дюлак
1
@OlivierDulac: gawk печатает, как cи FreeBSD, но я не заметил, что это задокументировано как зависящее от реализации: gnu.org/software/gawk/manual/… . Так что это произойдет , но не всегда.
dave_thompson_085

Ответы:

14

Чтобы уточнить, \nсимвол LF (он же или новая строка) - это разделитель строк , а не разделитель строк. Строка не заканчивается, если она не заканчивается символом новой строки. Файл, который содержит только, a\nbне является допустимым текстовым файлом, поскольку он содержит символы после последней строки. То же самое для файла, который содержит только a. Файл, который содержит a\nодну непустую строку.

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

Если:

 tail -c 2 file | od -An -vtc

Вывод \nили \n \n, то файл содержит хотя бы одну завершающую пустую строку. Если он ничего не выводит, то это пустой файл, если он выводит <anything-but-\0> \n, то он заканчивается непустой строкой. Все остальное, это не текстовый файл.

Теперь, чтобы использовать это для поиска файлов, оканчивающихся пустой строкой, хорошо, что это эффективно (особенно для больших файлов), поскольку оно считывает только последние два байта файлов, но сначала выходные данные не легко анализируются программно, особенно если учесть, что это не согласуется от одной реализации odк следующей, и нам нужно будет запускать одну tailи одну odна файл.

find . -type f -size +0 -exec gawk '
  ENDFILE{if ($0 == "") print FILENAME}' {} +

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

В идеале вам нужна оболочка, которая может прочитать конец файла самостоятельно.

С zsh:

zmodload zsh/system
for f (**/*(D.L+0)) {
  {
    sysseek -w end -2
    sysread
    [[ $REPLY = $'\n' || $REPLY = $'\n\n' ]] && print -r -- $f
  } < $f
}
Стефан Шазелас
источник
способ использовать метод этот ответ, чтобы знать , если какой - то файл (ы) текстовые файлы: are_textfiles () { nontext=0; rem="return 0 if all args are files with terminating newline, or n [=number of non-textfiles]" ; for f in "$@" ; do [ -f "$f" ] && { tail -c 1 "$f" | od -An -vtc | grep "\\n" ;} >/dev/null 2>&1 || ((nontext++)) ; done ; return $nontext ; }. Используйте как:if ( are_textfiles this that otherthing ) ; then echo all are text files ; else echo "are_textfiles returned : $?" ; fi
Оливье Дюлак
6

С gnu sedи оболочки вроде zsh(или bashс shopt -s globstar):

sed -ns '${/./F}' ./**/*.styl

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

don_crissti
источник
1
Никогда не видел -sв действии раньше. Спасибо GNU!
Гленн Джекман
Примечание. Параметр F существует в версии 4.2.2 sed (22 декабря 2012 г.)
Исаак
3

Правильно завершенный текстовый файл с пустой последней строкой заканчивается двумя \n.

Затем мы ожидаем, что tail -c2должно быть равно $'\n\n'.

К сожалению, расширения команд удаляют завершающие новые строки. Нам нужно немного настроить.

f=filename
nl='
'
t=$(tail -c2 $f; printf x)  # capture the last two characters.
r="${nl}${nl}$"                 # regex for: "ends in two newlines".
[[ ${t%x} =~ $r ]] &&  echo "file $f ends in an empty line"

Мы могли бы даже немного расширить, чтобы проверить, какие файлы не имеют новой строки:

nl='
'
nl=$'\n'
find . -type f -name '*.styl' | while read f; do
    t=$(tail -c2 $f; printf x); r1="${nl}$"; r2="${nl}${r1}"
    [[ ${t%x} =~ $r1 ]] || echo "file $f is missing a trailing newline"
    [[ ${t%x} =~ $r2 ]] && echo "$f"
done

Обратите внимание, что перевод строки может быть изменен на что-то вроде, $'\r\nесли это необходимо.
В этом случае также измените tail -c2на tail -c4.

Исаак
источник
0
for file in *; do
    # Check if the file is readable to avoid clutter
    if cat "./$file" 2&>1 /dev/null; then
        # Compare the last character with a single newline character.
        if [ -n "$(tail -c 1 -- "./$file")" ]; then
            echo "$file"
        fi
        # Also report empty files.
        if [ $(wc -c  < "./$file") -eq 0 ]; then
            echo "$file"
        fi
    fi
done
Оскар Ског
источник
1
это не работает с пустыми файлами, но я могу жить с этим.
jcubic
Может быть еще несколько ошибок, потому что сравнение строк не работает так, как я ожидал. Я добавил проверку на пустые файлы.
Оскар Ског
Ах, он игнорирует символы новой строки.
Оскар Ског
Рассмотрим более читаемым cat $file 2>&1 /dev/null, или , если это Bash-только cat $file &> /dev/null.
кот
1
Кроме того, рассмотрите возможность цитирования $fileвезде, где это используется - и, пожалуйста, используйте $(commands ...)вместо `backticks`...
cat