Extract file basename without path and extension i

2019-01-05 20:05发布

问题:

This question already has an answer here:

  • Extract filename and extension in Bash 36 answers

Given file names like these:

/the/path/foo.txt
bar.txt

I hope to get:

foo
bar

Why this doesn't work?

#!/bin/bash

fullfile=$1
fname=$(basename $fullfile)
fbname=${fname%.*}
echo $fbname

What's the right way to do it?

回答1:

You don't have to call the external basename command. Instead, you could use the following commands:

$ s=/the/path/foo.txt
$ echo ${s##*/}
foo.txt
$ s=${s##*/}
$ echo ${s%.txt}
foo
$ echo ${s%.*}
foo

Note that this solution should work in all recent (post 2004) POSIX compliant shells, (e.g. bash, dash, ksh, etc.).

Source: Shell Command Language 2.6.2 Parameter Expansion

More on bash String Manipulations: http://tldp.org/LDP/LG/issue18/bash.html



回答2:

The basename command has two different invocations; in one, you specify just the path, in which case it gives you the last component, while in the other you also give a suffix that it will remove. So, you can simplify your example code by using the second invocation of basename. Also, be careful to correctly quote things:

fbname=$(basename "$1" .txt)
echo "$fbname"


回答3:

A combination of basename and cut works fine, even in case of double ending like .tar.gz:

fbname=$(basename "$fullfile" | cut -d. -f1)

Would be interesting if this solution needs less arithmetic power than Bash Parameter Expansion.



回答4:

Pure bash, no basename, no variable juggling. Set a string and echo:

s=/the/path/foo.txt
echo ${s//+(*\/|.*)}

Output:

foo

Note: the bash extglob option must be "on", (on Ubuntu it's "on" by default), if it's not, do:

shopt -s extglob

Walking through the ${s//+(*\/|.*)}:

  1. ${s -- start with $s.
  2. // substitute every instance of the pattern.
  3. +( match one or more of the pattern list in parenthesis.
  4. *\/ matches anything before /. (1st pattern)
  5. | Or. (pattern separator.)
  6. .* matches anything after .. (2nd pattern)
  7. ) end pattern list.
  8. } end parameter expansion -- since there's no / (which would precede a string substitute), the matched patterns are deleted.

Relevant man bash background:

  1. pattern substitution:
  ${parameter/pattern/string}
          Pattern substitution.  The pattern is expanded to produce a pat‐
          tern just as in pathname expansion.  Parameter is  expanded  and
          the  longest match of pattern against its value is replaced with
          string.  If pattern begins with /, all matches  of  pattern  are
          replaced   with  string.   Normally  only  the  first  match  is
          replaced.  If pattern begins with #, it must match at the begin‐
          ning of the expanded value of parameter.  If pattern begins with
          %, it must match at the end of the expanded value of  parameter.
          If string is null, matches of pattern are deleted and the / fol‐
          lowing pattern may be omitted.  If parameter is @ or *, the sub‐
          stitution  operation  is applied to each positional parameter in
          turn, and the expansion is the resultant list.  If parameter  is
          an  array  variable  subscripted  with  @ or *, the substitution
          operation is applied to each member of the array  in  turn,  and
          the expansion is the resultant list.
  1. extended pattern matching:
  If the extglob shell option is enabled using the shopt builtin, several
   extended  pattern  matching operators are recognized.  In the following
   description, a pattern-list is a list of one or more patterns separated
   by a |.  Composite patterns may be formed using one or more of the fol‐
   lowing sub-patterns:

          ?(pattern-list)
                 Matches zero or one occurrence of the given patterns
          *(pattern-list)
                 Matches zero or more occurrences of the given patterns
          +(pattern-list)
                 Matches one or more occurrences of the given patterns
          @(pattern-list)
                 Matches one of the given patterns
          !(pattern-list)
                 Matches anything except one of the given patterns


回答5:

Here are oneliners:

  1. $(basename ${s%.*})
  2. $(basename ${s} .${s##*.})

I needed this, the same as asked by bongbang and w4etwetewtwet.



回答6:

Here is another (more complex) way of getting either the filename or extension, first use the rev command to invert the file path, cut from the first . and then invert the file path again, like this:

filename=`rev <<< "$1" | cut -d"." -f2- | rev`
fileext=`rev <<< "$1" | cut -d"." -f1 | rev`


回答7:

If you want to play nice with Windows file paths (under Cygwin) you can also try this:

fname=${fullfile##*[/|\\]}

This will account for backslash separators when using BaSH on Windows.



回答8:

Just an alternative that I came up with to extract an extension, using the posts in this thread with my own small knowledge base that was more familiar to me.

ext="$(rev <<< "$(cut -f "1" -d "." <<< "$(rev <<< "file.docx")")")"

Note: Please advise on my use of quotes; it worked for me but I might be missing something on their proper use (I probably use too many).



回答9:

Use the basename command. Its manpage is here: http://unixhelp.ed.ac.uk/CGI/man-cgi?basename



标签: