У меня есть много текстовых файлов в каталоге, и я хочу удалить последнюю строку каждого файла в каталоге.
Как мне это сделать?
У меня есть много текстовых файлов в каталоге, и я хочу удалить последнюю строку каждого файла в каталоге.
Как мне это сделать?
Ответы:
Вы можете использовать этот хороший oneliner, если у вас есть GNU sed.
sed -i '$ d' ./*
Он удалит последнюю строку каждого не скрытого файла в текущем каталоге. Переключатель -iдля GNU sedозначает, что он работает на месте и '$ d'выдает команду sedна удаление последней строки (что $означает последнюю и dозначает удаление).
-iGNUism, так что это спорный вопрос, но я потерял бы свою старую бороду, если бы не указал, что некоторые старые версии sedне позволяют вам ставить пробелы между $и d(или, в общем между шаблоном и командой).
*(.)для перемещения обычных файлов, я не знаю о других оболочках.
Все остальные ответы имеют проблемы, если каталог содержит что-то отличное от обычного файла или файл с пробелами / символами новой строки в имени файла. Вот то, что работает независимо от:
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.
findэтом написано более эффективно find $dir -type f -exec sed -i '$d' '{}' '+'.)
-print0нет в полной команде, зачем ставить это в объяснение?
-depth 0не работает (findutils 4.4.2), вместо этого должно быть -maxdepth 1.
-exec.
Использование 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})
${#length}не будет работать, так как он подсчитывает символы, а не байты, и $(...)удаляет завершающий символ новой строки, поэтому ${#...}отключается на единицу, даже если все символы являются однобайтовыми.
Более портативный подход:
for f in ./*
do
test -f "$f" && ed -s "$f" <<\IN
d
w
q
IN
done
Я не думаю , что это нуждается в каких - либо объяснениях ... кроме того, возможно , что в этом случае dтакой же , как с $dтех пор edпо умолчанию выбирает последнюю строку.
Это не будет выполнять рекурсивный поиск и не будет обрабатывать скрытые файлы (также называемые точечными файлами).
Если вы хотите отредактировать их, также смотрите Как сопоставить * со скрытыми файлами в каталоге
[[]]на, []то это будет полностью POSIX-совместимым. ( [[ ... ]]это Bashism.)
[[это не чушь )
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 {} +
Также см: