Як знайти файли, які не мають порожнього рядка в кінці?


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.


@don_crissti Мені потрібні файли, які не мають порожнього рядка.
jcubic

2
Чи можу я запитати причину, коли вам потрібно знайти ці файли? Я думаю, це пов'язано з тим, що текстові файли в unix повинні бути завершені новим рядком (vi, наприклад, "майже безшумно" додасть його, коли ви збережете, наприклад), а кілька (орієнтованих на текст) команд ігнорують останній рядок, якщо він не закінчується новим рядком (wc, iirc .... але є й інші). І це може допомогти
Олів’є Дулак

awk 'END{print}' $file : це повністю ігнорує вміст $ $, і після закінчення розбору всіх файлів, що містяться у "$ file", додається новий рядок. Оскільки це єдине, що друкує команда awk, її можна замінити на: printf '\n'(без жодного ментіно $ файла) і зробити те саме. Я думаю, що це НЕ, на що ти прагнув (тобто: надрукувати останній рядок файлу?)
Олів'є Дулак

@don_crissti: якщо останній символ файлу не є новим рядком, то цей файл не є строго posixly unix-TEXT-файлом. див .: unix.stackexchange.com/a/263919/27616 . зауважте, що багато текстових команд (наприклад, туалет) просто ігнорують останній "рядок", якщо він не закінчується новим рядком
Олів'є Дулак

1
@OlivierDulac: подивитися друкує cі так робить FreeBSD, але я не помітив , що це документовано як залежна від реалізації: gnu.org/software/gawk/manual / ... . Так що це станеться , але не завжди.
dave_thompson_085

Відповіді:


14

Для уточнення, \nсимвол LF (aka або newline) є роздільником рядків , це не роздільник рядків. Рядок не закінчений, якщо він не закінчується символом нового рядка. Файл, який містить лише 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

це перевіряє, чи останній рядок кожного файлу не порожній, якщо так, він друкує ім'я файлу.
Якщо ви хочете навпаки (друкуйте назви файлів, якщо останній рядок порожній), просто замініть /./на/^$/


1
Ніколи не бачив -sу дії раніше. Дякую вам GNU!
Гленн Джекман

Примітка. Опція F існує з версії sed 4.2.2 (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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.