Extract file basename without path and extension in bash
Solution 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
Solution 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"
Solution 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.
Solution 4
Here are oneliners:
$(basename "${s%.*}")
$(basename "${s}" ".${s##*.}")
I needed this, the same as asked by bongbang and w4etwetewtwet.
Solution 5
Pure bash
, no basename
, no variable juggling. Set a string and echo
:
p=/the/path/foo.txt
echo "${p//+(*\/|.*)}"
Output:
foo
Note: the bash
extglob option must be "on", (Ubuntu sets extglob "on" by default), if it's not, do:
shopt -s extglob
Walking through the ${p//+(*\/|.*)}
:
-
${p
-- start with $p. -
//
substitute every instance of the pattern that follows. -
+(
match one or more of the pattern list in parenthesis, (i.e. until item #7 below). -
1st pattern:
*\/
matches anything before a literal "/
" char. - pattern separator
|
which in this instance acts like a logical OR. -
2nd pattern:
.*
matches anything after a literal ".
" -- that is, inbash
the ".
" is just a period char, and not a regex dot. -
)
end pattern list. -
}
end parameter expansion. With a string substitution, there's usually another/
there, followed by a replacement string. But since there's no/
there, the matched patterns are substituted with nothing; this deletes the matches.
Relevant man bash
background:
- 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.
- 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
Danf
Updated on July 17, 2022Comments
-
Danf almost 2 years
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?