I have many text files in a directory, and I want to remove the last line of every file in the directory.
How can I do it?
I have many text files in a directory, and I want to remove the last line of every file in the directory.
How can I do it?
You can use this nice oneliner if you have GNU sed.
sed -i '$ d' ./*
It will remove the last line of each non-hidden file in the current directory. Switch -i for GNU sed means to operate in place and '$ d' commands the sed to delete the last line ($ meaning last and d meaning delete).
The other answers all have problems if the directory contains something other than a regular file, or a file with spaces/newlines in the file name. Here's something that works regardless:
find "$dir" -type f -exec sed -i '$d' '{}' '+'
find "$dir" -type f: find the files in the directory $dir
-type f which are regular files;-exec execute the command on each file foundsed -i: edit the files in place;'$d': delete (d) the last ($) line.'+': tells find to keep adding arguments to sed (a bit more efficient than running the command for each file separately, thanks to @zwol).If you don't want to descend into subdirectories, then you can add the argument -maxdepth 1 to find.
Using GNU sed -i '$d' means reading the full file and making a copy of it without the last line, while it would be a lot more efficient to just truncate the file in place (at least for big files).
With GNU truncate, you can do:
for file in ./*; do
[ -f "$file" ] &&
length=$(tail -n 1 "$file" | wc -c) &&
[ "$length" -gt 0 ] &&
truncate -s "-$length" "$file"
done
If the files are relatively small, that would probably be less efficient though as it runs several commands per file.
Note that for files that contain extra bytes after the last newline character (after the last line) or in other words if they have a non-delimited last line, then depending on the tail implementation, tail -n 1 will return only those extra bytes (like GNU tail), or the last (properly delimited) line and those extra bytes.
A more portable approach:
for f in ./*
do
test -f "$f" && ed -s "$f" <<\IN
d
w
q
IN
done
I don't think this needs any explanation... except maybe that in this case d is the same as $d since ed by default selects the last line.
This will not search recursively and will not process hidden files (aka dotfiles).
If you want to edit those too see How to match * with hidden files inside a directory
If you have access to vim, you can use:
for file in ./*
do
if [ -f "${file}" ]
then
vim -c '$d' -c "wq" "${file}"
fi
done
POSIX-compliant one-liner for all files recursively starting in current directory, including dot-files:
find . -type f -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +
For .txt files only, non-recursively:
find . -path '*/*/*' -prune -o -type f -name '*.txt' -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +
Also see: