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).
-i
which is a GNUism, so this is moot, but I would lose my old beard standing if I failed to point out that some older versions of sed
do not allow you to put any whitespace between the $
and the d
(or, in general, between the pattern and the command).
– zwol
Jan 31 '17 at 15:02
*(.)
to glob for regular files, I don't know about other shells.
– N.I.
Jan 31 '17 at 15:05
sed
doesn't support sed '$ d'
? AFAICT the very first implementation from the late 70s in Unix V7 did support it. GNU sed
1.18 at least (from 1993) does support it.
– Stéphane Chazelas
Jan 31 '17 at 17:41
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
.
find
this is more efficiently written find $dir -type f -exec sed -i '$d' '{}' '+'
.)
– zwol
Jan 31 '17 at 15:14
-print0
isn't present in the full command, why put it into the explanation?
– Ruslan
Jan 31 '17 at 16:58
-depth 0
doesn't work (findutils 4.4.2), it should instead be -maxdepth 1
.
– Ruslan
Jan 31 '17 at 17:02
-exec
.
– N.I.
Jan 31 '17 at 17:15
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.
${#length}
would not work as it counts characters, not bytes and $(...)
would remove the tailing newline character so ${#...}
would be off by one even if all the characters were single-byte.
– Stéphane Chazelas
Jan 31 '17 at 20:09
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
[[]]
to []
then it will be fully POSIX compliant. ([[ ... ]]
is a Bashism.)
– Wildcard
Feb 01 '17 at 03:50
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: