How can I get only the filename using sed? I've this
out_file=$(echo $in_file|sed "s/\(.*\.\).*/\1mp4/g")
But I get the path too /root/video.mp4
, and I want only video.mp4
.
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It only takes a minute to sign up.
Sign up to join this communitybasename
from the GNU coreutils can help you doing this job:
$ basename /root/video.mp4
video.mp4
If you already know the extension of the file, you can invoke basename
using the syntax basename NAME [SUFFIX]
in order to remove it:
$ basename /root/video.mp4 .mp4
video
Or another option would be cutting everything after the last dot using sed
:
$ basename /root/video.old.mp4 | sed 's/\.[^.]*$//'
video.old
sed 's/\.[^.]*$//'
as you have, will fail for (hidden) .filename
and .
and ..
directories
The easiest solution is remove everything until last appearance of /
:
echo /root/video.mp4 | sed 's/.*\///'
echo
under some circumstances changes the strings that it is given as arguments, in particular, backslash sequences may be interpreted as C escape sequences. Also, newlines in filenames, although uncommon, would not be handled well by sed
.
Use any of the followings ways:
out_file="${in_file##*/}"
out_file="$(basename $in_file)"
out_file="$(echo $in_file | sed 's=.*/==')"
out_file="$(echo $in_file | awk -F"/" '{ print $NF }')"
ps. You get the same string because in your statement \(.*\.\)
matches to the string from the beginning until dot (/root/video.
) and then you manually add .mp4
which is the same as in you original string. You should use s=.*\([^/]*\)=\1=
instead.
Update: (First one is fixed now)
To get the only filename without extension you can :
out_file="$(echo $in_file | sed 's=.*/==;s/\.[^.]*$/.new_ext/')"
out_file="$(echo $in_file | sed 's=\([^/]*\)\.[^./]*$=\1.new_ext=')"
out_file="$(echo $in_file | awk -F"/" '{ gsub (/\.[^/.]*$/,".new_ext",$NF);print $NF }'
my.file.tar.gz
.
Aug 4, 2012 at 7:02
sed
and awk
. Fixed. Thank you.
One of the fundamentals of using regex is that patterns are greedy by nature when specifying the wild card. While the answer proposed by @uloBasEI is certainly a working answer, it also requires the use of the basename command. The original question from @Shixons requests a solution using only sed.
Before continuing, it's always helpful to know which version of sed is the target. I'm assuming BSD (as shipped with OSX).
First of all, the pattern proposed in the original question doesn't work because it captures everything from the beginning of the input string up to and including the last dot. Without anchors, this search will swallow up everything from left to right. The "/1" matched pattern, therefore, is everything up to and including the last dot. Even a filename with multiple dots will be swallowed up whole. Not the desired result at all.
The first step is to establish a strategy for identifying patterns. Here, you'd like to get rid of everything to the left of the filename (we'll deal with the extension later):
out_file="$(echo $in_file | sed 's/^\(\/.*\/\)*.*/\1/')"
The search matches from the beginning of the string. It matches a pattern of "/.*" zero or more times and deletes everything afterwards. We print the matched patterns with "\1". We are not searching globally; we are searching from the beginning of the string by specifying the ^ anchor.
We get better clarity by enabling the "-E" option so we don't have to escape parentheses:
out_file="$(echo $in_file | sed -E 's/^(\/.*\/)*.*/\1/')"
So now we have the part on the left. Let's add the part on the right. Note that we need to keep the left part as a pattern because that's how we can specify that it appears zero or more times. All we do now is add a pattern for the part on the right:
out_file="$(echo $in_file | sed -E 's/^(\/.*\/)*(.*)/\2/')"
We only print out the second match, thereby discarding everything but the filename. But we still need to remove the filename extension.
out_file="$(echo $in_file | sed -E 's/^(\/.*\/)*(.*)\..*$/\2/')"
The "$" at the end is optional.
Finally, to add the new extension you just revise like so:
out_file="$(echo $in_file | sed -E 's/^(\/.*\/)*(.*)\..*$/\2.mp4/')"
An additional optimization is to make the first forward slash optional to handle relative paths:
out_file="$(echo $in_file | sed -E 's/^([\/]?.*\/)*(.*)\..*$/\2.mp4/')"
I came across this question by being lazy while looking for a sed pattern to replace basename. I'm working on a stripped system that doesn't have that command installed.
POSIXly, to remove the directory name and extension:
rootname() {
LC_ALL=C awk -- 'BEGIN{
for (i = 1; i < ARGC; i++) {
file = ARGV[i]
if (file == "")
print ""
else {
sub("/*$", "", file) # remove trailing / characters if any
if (file == "")
print "/"
else {
sub(".*/", "", file) # remove dir part
if (file ~ /[^.]\./) # has a . other than as the first character
# also do not consider . and .. have an extension
sub(/\.[^.]*$/, "", file)
print file
}
}
}
}' "$@"
}
Examples:
$ rootname ~/.zshrc
.zshrc
$ rootname foo/bar/
bar
$ rootname /
/
$ rootname ""
$ rootname .foo.bar
.foo
$ rootname file.tar.gz
file.tar
$ rootname foo.d/bar
bar
$ rootname $'foo.d/\nbar.x'
bar
csh, tcsh, zsh, bash or vim have :t
(tail) and :r
(root) modifiers which can be combined as :t:r
to get the root of the tail, but note that:
bash
, that's only available in history expansion, so not useful when you need to apply it to the contents of a variable.rootname
function above for corner case values like /
, ..
or for hidden files without extension (like ~/.cshrc
).zsh
, with file=/foo/bar/
, $file:t:r
expands to the empty string.It is possible to do the same with sed
, but it's not going to be as legible as with awk
.
rootname() {
printf '%s\n' "$1" | LC_ALL=C sed '
:1
$!{
N; # load full input into pattern space for filenames with newlines
b1
}
# handle the empty string
/./!b
# remove trailing slashes
s|/*$||
/./! {
# handle slash
s|^|/|
b
}
s|.*/||; # remove dir part
/[^.]\./ s/\.[^.]*$//'
}