28

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.

5 Answers 5

38

basename 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
1
  • 3
    Using sed 's/\.[^.]*$//' as you have, will fail for (hidden) .filename and . and .. directories
    – Peter.O
    Aug 4, 2012 at 13:47
13

The easiest solution is remove everything until last appearance of /:

echo /root/video.mp4 | sed 's/.*\///'

1
  • Note that 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.
    – Kusalananda
    Nov 3, 2020 at 17:54
5

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 }'
4
  • But with any of those methods I get filename with the format and I need to get only the filename and put a new format manually.
    – Shixons
    Aug 4, 2012 at 6:30
  • Ah, that makes sense. I've updated my answer.
    – rush
    Aug 4, 2012 at 6:43
  • @rush: There will be edge cases, e.g. for a file named my.file.tar.gz. Aug 4, 2012 at 7:02
  • @donothingsuccessfully there was a missing dot symbol in the last sed and awk. Fixed. Thank you.
    – rush
    Aug 4, 2012 at 7:09
3

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.

2

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:

  • in bash, that's only available in history expansion, so not useful when you need to apply it to the contents of a variable.
  • you'll get different results from that rootname function above for corner case values like /, .. or for hidden files without extension (like ~/.cshrc).
  • Except in 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/\.[^.]*$//'
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .