Как удалить последнюю строку всех файлов из каталога?

17

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

Как мне это сделать?

ThehalfarisedPheonix
источник
6
Что вы пробовали? unix.stackexchange.com/help/how-to-ask : «Совместное использование вашего исследования помогает всем. Расскажите нам, что вы нашли и почему оно не соответствует вашим потребностям. Это показывает, что вы нашли время, чтобы попытаться помочь себе» Это избавляет нас от повторения очевидных ответов, и, прежде всего, помогает получить более конкретный и актуальный ответ! "
Патрик
Почему мысль о том, что кто-то случайно применил это к / etc, обладает совершенно особым качеством индукции :)
rackandboneman

Ответы:

4

Если у вас есть доступ к vim, вы можете использовать:

for file in ./*
do
  if [ -f "${file}" ]
  then
    vim -c '$d' -c "wq" "${file}"
  fi
done
Р Саху
источник
16

Вы можете использовать этот хороший oneliner, если у вас есть GNU sed.

 sed -i '$ d' ./*

Он удалит последнюю строку каждого не скрытого файла в текущем каталоге. Переключатель -iдля GNU sedозначает, что он работает на месте и '$ d'выдает команду sedна удаление последней строки (что $означает последнюю и dозначает удаление).

StefanR
источник
3
Это приведет к ошибкам (и ничего не сделает), если папка содержит что-либо, кроме обычного файла, например, другую папку ...
Наджиб Идрисси
1
@StefanR Вы используете -iGNUism, так что это спорный вопрос, но я потерял бы свою старую бороду, если бы не указал, что некоторые старые версии sedне позволяют вам ставить пробелы между $и d(или, в общем между шаблоном и командой).
Звол
1
@zwol Как я уже писал, это приведет к ошибке , а не к предупреждению, и sed прекратит работу, как только достигнет этого файла (по крайней мере, с той версией sed, которая у меня есть). Следующие файлы не будут обработаны. Выбрасывать сообщения об ошибках было бы ужасной идеей, так как вы даже не знали бы, что это произошло! С zsh вы можете использовать *(.)для перемещения обычных файлов, я не знаю о других оболочках.
Наджиб Идрисси
@NajibIdrissi Хм, ты прав. Это удивляет меня; Я бы ожидал, что он будет жаловаться на каталог, но затем перейти к следующему файлу в командной строке. На самом деле, я думаю, что сообщу об этом как об ошибке.
Звол
@don_crissti У меня тоже есть GNU sed v4.3 ... Я не знаю, что тебе сказать, я только что проверил снова. gist.github.com/nidrissi/66fad6be334234f5dbb41c539d84d61e
Наджиб Идрисси
11

Все остальные ответы имеют проблемы, если каталог содержит что-то отличное от обычного файла или файл с пробелами / символами новой строки в имени файла. Вот то, что работает независимо от:

find "$dir" -type f -exec sed -i '$d' '{}' '+'
  • find "$dir" -type f: найти файлы в каталоге $dir
    • -type f которые являются обычными файлами;
    • -exec выполнить команду для каждого найденного файла
    • sed -i: редактировать файлы на месте;
    • '$d': удалить ( d) последнюю ( $) строку.
    • '+': сообщает find для добавления аргументов sed(немного эффективнее, чем выполнение команды для каждого файла отдельно, благодаря @zwol).

Если вы не хотите спускаться в подкаталоги, вы можете добавить аргумент -maxdepth 1в find.

Наджиб Идрисси
источник
1
Хм, но в отличие от других ответов это сводится к подкаталогам. (Также с текущими версиями об findэтом написано более эффективно find $dir -type f -exec sed -i '$d' '{}' '+'.)
zwol
@zwol Спасибо, я добавил это в ответ.
Наджиб Идрисси
-print0нет в полной команде, зачем ставить это в объяснение?
Руслан
1
Также -depth 0не работает (findutils 4.4.2), вместо этого должно быть -maxdepth 1.
Руслан
@Ruslan У меня была первая версия, где я использовал xargs, но потом вспомнил -exec.
Наджиб Идрисси
9

Использование GNU sed -i '$d'означает чтение полного файла и создание его копии без последней строки, в то время как было бы намного эффективнее просто обрезать файл на месте (по крайней мере, для больших файлов).

С GNU truncateвы можете сделать:

for file in ./*; do
  [ -f "$file" ] &&
    length=$(tail -n 1 "$file" | wc -c) &&
    [ "$length" -gt 0 ] &&
    truncate -s "-$length" "$file"
done

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

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

Стефан Шазелас
источник
Нужна ли Вам |wc -cв tailвызове? (или а ${#length})
Джефф Шаллер
@JeffSchaller. К сожалению. wc -c действительно был задуман. ${#length}не будет работать, так как он подсчитывает символы, а не байты, и $(...)удаляет завершающий символ новой строки, поэтому ${#...}отключается на единицу, даже если все символы являются однобайтовыми.
Стефан
6

Более портативный подход:

for f in ./*
do
test -f "$f" && ed -s "$f" <<\IN
d
w
q
IN
done

Я не думаю , что это нуждается в каких - либо объяснениях ... кроме того, возможно , что в этом случае dтакой же , как с $dтех пор edпо умолчанию выбирает последнюю строку.
Это не будет выполнять рекурсивный поиск и не будет обрабатывать скрытые файлы (также называемые точечными файлами).
Если вы хотите отредактировать их, также смотрите Как сопоставить * со скрытыми файлами в каталоге

don_crissti
источник
Ницца! Если вы измените [[]]на, []то это будет полностью POSIX-совместимым. ( [[ ... ]]это Bashism.)
Wildcard
@Wildcard - спасибо, изменилось (хотя [[это не чушь )
don_crissti
Я должен сказать, что это не POSIX. :)
Wildcard
3

POSIX-совместимый однострочник для всех файлов, рекурсивно начинающихся в текущем каталоге, включая dot-файлы:

find . -type f -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +

Только для .txtфайлов, не рекурсивно:

find . -path '*/*/*' -prune -o -type f -name '*.txt' -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +

Также см:

Wildcard
источник