У меня есть много текстовых файлов в каталоге, и я хочу удалить последнюю строку каждого файла в каталоге.
Как мне это сделать?
У меня есть много текстовых файлов в каталоге, и я хочу удалить последнюю строку каждого файла в каталоге.
Как мне это сделать?
Ответы:
Вы можете использовать этот хороший oneliner, если у вас есть GNU sed
.
sed -i '$ d' ./*
Он удалит последнюю строку каждого не скрытого файла в текущем каталоге. Переключатель -i
для GNU sed
означает, что он работает на месте и '$ d'
выдает команду sed
на удаление последней строки (что $
означает последнюю и d
означает удаление).
-i
GNUism, так что это спорный вопрос, но я потерял бы свою старую бороду, если бы не указал, что некоторые старые версии 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 {} +
Также см: