给定一个字符串文件路径,例如 /foo/fizzbuzz.bar
,如何使用 bash 提取该字符串中的 fizzbuzz
部分?
以下是如何在Bash中使用 # 和 % 运算符进行操作。
$ x="/foo/fizzbuzz.bar"
$ y=${x%.bar}
$ echo ${y##*/}
fizzbuzz
${x%.bar}
也可以写成${x%.*}
,用于删除点号之后的所有内容,或者使用${x%%.*}
来删除第一个点号之后的所有内容。
示例:
$ x="/foo/fizzbuzz.bar.quux"
$ y=${x%.*}
$ echo $y
/foo/fizzbuzz.bar
$ y=${x%%.*}
$ echo $y
/foo/fizzbuzz
参考Bash手册,查找${parameter%word}
和${parameter%%word}
匹配末尾部分的章节。
x
变量的方法来实现这个?如果我尝试 echo ${"/foo/fizzbuzz.bar.quux"%.*}
,会得到 -bash: ${"/foo/fizzbuzz.bar.quux"%.*}: bad substitution
的错误提示。 - Alec Jacobson看一下basename命令:
NAME="$(basename /foo/fizzbuzz.bar .bar)"
指示它删除后缀.bar
,导致NAME=fizzbuzz
纯Bash实现,分为两个独立的操作:
从路径字符串中删除路径:
path=/foo/bar/bim/baz/file.gif
file=${path##*/}
#$file is now 'file.gif'
从路径字符串中删除扩展名:
base=${file%.*}
#${base} is now 'file'.
使用basename,我使用以下代码来实现这个目标:
for file in *; do
ext=${file##*.}
fname=`basename $file $ext`
# Do things with $fname
done;
这不需要先验的文件扩展名知识,即使您有一个文件名中有点的文件名(在其扩展名之前),它也可以工作;但是它确实需要程序basename
,但这是GNU核心实用程序的一部分,因此它应该随任何发行版一起提供。
fname=\
basename $file .$ext`` - Carlos Troncoso$file
、$ext
和反引号部分(包括反引号本身)用双引号括起来。 - mwfearnleybasename 和 dirname 函数是你需要的:
mystring=/foo/fizzbuzz.bar
echo basename: $(basename "${mystring}")
echo basename + remove .bar: $(basename "${mystring}" .bar)
echo dirname: $(dirname "${mystring}")
有输出:
basename: fizzbuzz.bar
basename + remove .bar: fizzbuzz
dirname: /foo
mystring=$1
在http://shellcheck.net/上运行而不是当前的常量值(这将抑制几个警告,确保不包含空格/ glob字符/等),并解决它发现的问题? - Charles Duffyecho“basename:$(basename”$ mystring“)”
- 这样,如果 mystring ='/ foo / *'
,则在 basename
完成后,您不会将 *
替换为当前目录中的文件列表。 - Charles Duffy纯 bash 方式:
~$ x="/foo/bar/fizzbuzz.bar.quux.zoom";
~$ y=${x/\/*\//};
~$ echo ${y/.*/};
fizzbuzz
这个功能在“参数扩展”下的bash手册中有解释。也可使用其他非bash的方法,如awk、perl、sed等。
编辑:适用于文件后缀名中包含点号,并且不需要知道后缀(扩展名),但是对于名称本身中包含点号则不起作用。
使用basename
假设您知道文件扩展名,是吗?
我认为各种正则表达式建议不能处理文件名包含多个“。”的情况。
以下似乎可以处理双点。哦,还有包含“/”本身的文件名(只是为了好玩)
用Pascal的话来说,“对不起,这个脚本太长了。我没时间把它缩短”
#!/usr/bin/perl
$fullname = $ARGV[0];
($path,$name) = $fullname =~ /^(.*[^\\]\/)*(.*)$/;
($basename,$extension) = $name =~ /^(.*)(\.[^.]*)$/;
print $basename . "\n";
除了POSIX兼容的语法,在这个答案中使用的语法外,
basename <i>string</i> [<i>suffix</i>]
如同
basename /foo/fizzbuzz.bar .bar
basename
命令支持另一种语法:
basename -s .bar /foo/fizzbuzz.bar
使用相同的结果。不同之处和优势在于-s
暗示了-a
,支持多个参数:
$ basename -s .bar /foo/fizzbuzz.bar /baz/foobar.bar
fizzbuzz
foobar
-z
选项,可以通过使用NUL字节将输出分隔开来,使其成为适合文件名的内容。例如,对于包含空格、换行符和通配符字符(由ls
引用)的这些文件。$ ls has*
'has'$'\n''newline.bar' 'has space.bar' 'has*.bar'
将内容读入数组中:
$ readarray -d $'\0' arr < <(basename -zs .bar has*)
$ declare -p arr
declare -a arr=([0]=$'has\nnewline' [1]="has space" [2]="has*")
readarray -d
命令需要 Bash 4.4 或更高版本。对于旧版本,我们需要使用循环:
while IFS= read -r -d '' fname; do arr+=("$fname"); done < <(basename -zs .bar has*)
如果您不能像其他帖子中建议的那样使用basename,您始终可以使用sed。这是一个(丑陋的)示例。它不是最好的,但它通过提取所需的字符串并将输入替换为所需的字符串来工作。
echo '/foo/fizzbuzz.bar' | sed 's|.*\/\([^\.]*\)\(\..*\)$|\1|g'
这将为您提供输出
fizzbuzz
perl -pe 's/\..*$//;s{^.*/}{}'