如何从Bash中的路径字符串中删除文件后缀和路径部分?


396

给定一个字符串文件路径(例如)/foo/fizzbuzz.bar,我将如何使用bash提取fizzbuzz字符串的一部分?


您可以在Bash手册中找到信息,${parameter%word}以及${parameter%%word}在尾部匹配部分中找到。
1ac0

Answers:


573

这是使用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}尾随部分匹配的部分。


我最终使用了它,因为它是最灵活的,并且我还想做其他几件事,所以效果很好。
劳伦斯·约翰斯顿

这可能是发布的所有答案中最灵活的,但我认为建议basename和dirname命令的答案也应引起注意。如果您不需要其他任何精美的样式匹配,它们可能只是技巧。
mgadda

7
这叫做$ {x%.bar}?我想了解更多。
罗勒

14
@Basil:参数扩展。在控制台上,键入“ man bash”,然后键入“ / parameter expand”
Zan Lynx

1
我想如果您已经知道它的作用,或者如果您尝试过困难的尝试,那么“ man bash”的解释就很有意义。这几乎和git reference一样糟糕。我只是用谷歌代替。
Triplebig

225

看一下basename命令:

NAME="$(basename /foo/fizzbuzz.bar .bar)"

11
可能是当前提供的所有解决方案中最简单的一个...尽管我会使用$(...)而不是反引号。
Michael Johnson

6
最简单,但是增加了一个依赖项(我承认,这不是一个巨大或怪异的依赖项)。它还需要知道后缀。
Vinko Vrsalovic

并且可以用于从末尾删除任何内容,基本上它只是从末尾删除字符串。
Smar

2
问题是时间紧迫。在看着bash使用basename处理800个文件大约需要5分钟之后,我只是搜索了这个讨论的问题。使用上述正则表达式方法,时间减少到大约7秒。尽管这个答案对于程序员来说更容易执行,但是所花费的时间却太多了。想象一下其中有数千个文件的文件夹!我有一些这样的文件夹。
xizdaqrian

@xizdaqrian这绝对是错误的。这是一个简单的程序,不需要半秒钟就可以返回。我刚刚执行时间找到了/ home / me / dev -name“ * .py” .py -exec basename {} \; 并在1秒钟内总共删除了1500个文件的扩展名和目录。
Laszlo Treszkai

40

纯bash,可通过两个单独的操作完成:

  1. 从路径字符串中删除路径:

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
  2. 从路径字符串中删除扩展名:

    base=${file%.*}
    #${base} is now 'file'.

18

使用基本名,我使用以下方法来实现此目的:

for file in *; do
    ext=${file##*.}
    fname=`basename $file $ext`

    # Do things with $fname
done;

这不需要文件扩展名的先验知识,即使您的文件名的文件名中带有点(在文件扩展名的前面)也可以使用。尽管确实需要该程序basename,但这是GNU coreutils的一部分,因此它应该随发行版一起提供。


1
很好的答案!以非常干净的方式删除扩展名,但不会删除。在文件名的末尾。
metrix 2014年

3
@metrix只需添加“。” 在$ ext之前,即: fname=`basename $file .$ext`
Carlos Troncoso

13

纯bash方式:

~$ x="/foo/bar/fizzbuzz.bar.quux.zoom"; 
~$ y=${x/\/*\//}; 
~$ echo ${y/.*/}; 
fizzbuzz

在man bash的“参数扩展”下解释了此功能。非bash方式比比皆是:awk,perl,sed等。

编辑:使用文件后缀中的点,并且不需要知道后缀(扩展名),但不适用于名称本身中的点。


11

basename和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

1
这将有助于解决这里的报价-也许通过运行该shellcheck.netmystring=$1,而不是电流恒定值(这将抑制一些警告,是一定不能包含空格/水珠字符/等),以及解决这些问题的这发现?
查尔斯·达菲

好吧,我做了一些适当的更改以支持$ mystring中的引号。天哪,这是很久以前我写的:)
杰鲁布

将进一步完善报价结果:echo "basename: $(basename "$mystring")"-如果这样mystring='/foo/*',你没有得到*替换在当前目录中的文件列表 basename完成。
查尔斯·达菲,

6

使用basename假设您知道文件扩展名是不是?

而且我相信,各种正则表达式建议都不能解决一个文件名包含多个“”的问题。

以下似乎可以应付双点。哦,文件名本身也包含“ /”(仅用于踢)

用帕斯卡(Pascal)来解释:“很抱歉,这个脚本太长了。我没有时间将其缩短。”


  #!/usr/bin/perl
  $fullname = $ARGV[0];
  ($path,$name) = $fullname =~ /^(.*[^\\]\/)*(.*)$/;
  ($basename,$extension) = $name =~ /^(.*)(\.[^.]*)$/;
  print $basename . "\n";
 

1
这很好而且很健壮
Gaurav Jain

4

除了此答案中使用的符合POSIX的语法外

basename string [suffix]

basename /foo/fizzbuzz.bar .bar

GNUbasename支持另一种语法:

basename -s .bar /foo/fizzbuzz.bar

结果相同。区别和优点是-s暗含-a,它支持多个参数:

$ basename -s .bar /foo/fizzbuzz.bar /baz/foobar.bar
fizzbuzz
foobar

甚至可以通过使用-z选项将输出与NUL字节分开来使文件名安全,例如对于包含空白,换行符和glob字符(用引号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*)

此外,指定的后缀在输出被去除,如果存在(并且忽略其他)。
aksh1618


3

如果您不能按照其他文章中的建议使用基本名称,则可以始终使用sed。这是一个(丑陋的)例子。它不是最大的函数,但是它可以通过提取所需的字符串并将输入替换为所需的字符串来工作。

echo '/foo/fizzbuzz.bar' | sed 's|.*\/\([^\.]*\)\(\..*\)$|\1|g'

这将为您提供输出

嘶嘶声


尽管这是对原始问题的解答,但是当我在文件中包含路径行以提取基本名称以将其打印到屏幕时,此命令很有用。
桑哲崔

2

注意建议的perl解决方案:它会删除第一个点之后的所有内容。

$ echo some.file.with.dots | perl -pe 's/\..*$//;s{^.*/}{}'
some

如果要使用perl进行操作,则可以使用:

$ echo some.file.with.dots | perl -pe 's/(.*)\..*$/$1/;s{^.*/}{}'
some.file.with

但是,如果您使用的是Bash,则解决方案y=${x%.*}(或者basename "$x" .ext您知道扩展名)要简单得多。


1

基本名称即会删除路径。如果给定后缀并且与文件的后缀匹配,它也会删除后缀,但是您需要知道后缀才能赋予命令。否则,您可以使用mv并以其他方式找出新名称。


1

将排名靠前的答案与排名靠后的答案相结合,以获得不带完整路径的文件名:

$ x="/foo/fizzbuzz.bar.quux"
$ y=(`basename ${x%%.*}`)
$ echo $y
fizzbuzz

为什么在这里使用数组?另外,为什么还要使用basename?
codeforester
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.