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

Posted

技术标签:

【中文标题】如何从 Bash 中的路径字符串中删除文件后缀和路径部分?【英文标题】:How do I remove the file suffix and path portion from a path string in Bash? 【发布时间】:2010-09-12 14:55:41 【问题描述】:

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

【问题讨论】:

您可以在Bash manual 中找到的信息,在尾部匹配部分查找$parameter%word$parameter%%word 【参考方案1】:

你可以使用

mv *<PATTERN>.jar "$(basename *<PATTERN>.jar <PATTERN>.jar).jar"

例如:- 我想从我的文件名中删除 -SNAPSHOT。对于下面使用的命令

 mv *-SNAPSHOT.jar "$(basename *-SNAPSHOT.jar -SNAPSHOT.jar).jar"

【讨论】:

这里的通配符非常错误,除非你只有一个匹配的文件。【参考方案2】:

查看basename命令:

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

指示它删除后缀.bar,结果为NAME=fizzbuzz

【讨论】:

可能是目前提供的所有解决方案中最简单的...虽然我会使用 $(...) 而不是反引号。 最简单但增加了一个依赖(我承认不是一个巨大或奇怪的依赖)。它还需要知道后缀。 问题是命中时间。在观看 bash 使用 basename 处理 800 个文件需要近 5 分钟后,我刚刚搜索了这个讨论的问题。使用上述正则表达式方法,时间减少到大约 7 秒。虽然这个答案对程序员来说更容易执行,但时间太长了。想象一个包含几千个文件的文件夹!我有一些这样的文件夹。 @xizdaqrian 这绝对是错误的。这是一个简单的程序,它不应该花半秒来返回。我刚刚执行 time find /home/me/dev -name "*.py" .py -exec basename \;它在 1 秒内剥离了 1500 个文件的扩展名和目录。 不过,尽可能避免外部进程的总体思路是合理的。和 shell 编程的基本原则。【参考方案3】:

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

【讨论】:

修复这里的引用会很有帮助——也许通过shellcheck.net 和mystring=$1 运行这个而不是当前的常量值(这将抑制几个警告,确保不包含空格/ glob 字符/等),并解决它发现的问题? 好吧,我做了一些适当的更改以支持 $mystring 中的引号。天哪,这是很久以前我写的:) 将进一步改进以引用结果:echo "basename: $(basename "$mystring")" - 如果mystring='/foo/*' 你没有将* 替换为当前目录中的文件列表 basename 完成之后。【参考方案4】:

除了this answer中使用的POSIX conformant syntax,

basename <i>string</i> [<i>suffix</i>]

basename /foo/fizzbuzz.bar .bar

GNU 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*)

【讨论】:

此外,指定的后缀在输出 if 存在时被删除(否则忽略)。【参考方案5】:

下面是如何在 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 manual 中找到。查找$parameter%word$parameter%%word 尾随部分匹配部分。

【讨论】:

我最终使用了这个,因为它最灵活,而且我还想做一些其他类似的事情,而且效果很好。 这可能是所有发布的答案中最灵活的,但我认为建议 basename 和 dirname 命令的答案也值得关注。如果您不需要任何其他花哨的模式匹配,它们可能只是诀窍。 这叫什么$x%.bar?我想了解更多。 @Basil:参数扩展。在控制台上输入“man bash”,然后输入“/参数扩展” 如果您已经知道它的作用或者您自己尝试过,我想“man bash”的解释是有道理的。它几乎和 git 参考一样糟糕。我只是用谷歌搜索它。【参考方案6】:

纯 bash,在两个单独的操作中完成:

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

    path=/foo/bar/bim/baz/file.gif
    
    file=$path##*/  
    #$file is now 'file.gif'
    

    从路径字符串中删除扩展名:

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

【讨论】:

【参考方案7】:

将评分最高的答案与第二名的答案结合起来,得到没有完整路径的文件名:

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

【讨论】:

为什么在这里使用数组?另外,为什么要使用 basename? 另外,broken quoting.【参考方案8】:

使用 basename 我使用以下方法来实现:

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

    # Do things with $fname
done;

这不需要文件扩展名的先验知识,即使您的文件名在其文件名中包含点(在其扩展名前面)也可以工作;虽然它确实需要程序 basename,但这是 GNU coreutils 的一部分,因此它应该随任何发行版一起提供。

【讨论】:

优秀的答案!以非常干净的方式删除扩展名,但不会删除 .在文件名的末尾。 @metrix 只需添加“。” $ext之前,即:fname=`basename $file .$ext` 如果文件名中有空格,这可能会做坏事。您应该将$file$ext 和反引号部分(包括反引号本身)用双引号括起来。【参考方案9】:

纯 bash 方式:

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

此功能在 man bash 中的“参数扩展”下进行了说明。非 bash 方式比比皆是:awk、perl、sed 等等。

编辑:使用文件后缀中的点并且不需要知道后缀(扩展名),但使用中的点自己命名

【讨论】:

【参考方案10】:

注意建议的 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,如果您知道扩展名)的解决方案要简单得多。

【讨论】:

【参考方案11】:

基本名称会这样做,删除路径。如果给定后缀,并且它与文件的后缀匹配,它也会删除后缀,但您需要知道给命令提供的后缀。否则,您可以使用 mv 并以其他方式找出新名称应该是什么。

【讨论】:

【参考方案12】:

如果您不能按照其他帖子中的建议使用 basename,则始终可以使用 sed。这是一个(丑陋的)例子。它不是最好的,但它通过提取想要的字符串并将输入替换为想要的字符串来工作。

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

这将为您提供输出

嘶嘶声

【讨论】:

虽然这是原始问题的答案,但当我在文件中有多行路径以提取基本名称以将它们打印到屏幕上时,此命令很有用。【参考方案13】:

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

而且我相信各种正则表达式建议无法处理包含多个“。”的文件名

以下似乎可以处理双点。哦,还有包含“/”的文件名(只是为了好玩)

套用 Pascal 的话说,“抱歉,这个脚本太长了。我没有时间把它缩短”


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

【讨论】:

这很好,很健壮【参考方案14】:
perl -pe 's/\..*$//;s^.*/'

【讨论】:

以上是关于如何从 Bash 中的路径字符串中删除文件后缀和路径部分?的主要内容,如果未能解决你的问题,请参考以下文章

使用'basename -s'从bash中的路径中提取文件名

如何从bash中的ispell .mwl文件中提取所有前缀词

如何从bash中的多个文件名中删除公共前缀模式[重复]

如何从 C++ 中的字符串中删除前缀或后缀? [复制]

Bash:从作为输入给出的相对路径中获取绝对路径

从文本文件中删除 Unicode 字符 - sed ,其他 Bash/shell 方法