在Bash中提取文件名和扩展名

2794

我想要分别获取文件名(不含扩展名)和扩展名。

到目前为止,我找到的最佳解决方案是:

NAME=`echo "$FILE" | cut -d'.' -f1`
EXTENSION=`echo "$FILE" | cut -d'.' -f2`
这是错误的,因为如果文件名包含多个 . 字符,则无法正常工作。例如,如果我有a.b.js,它会考虑ab.js,而不是a.bjs。可以使用以下Python代码轻松解决:
file, ext = os.path.splitext(path)

但如果可能的话,我不想为此启动Python解释器。

有更好的主意吗?


41
在应用以下的优秀答案时,不要像我曾经做过的那样,简单地将变量粘贴进去,例如:错误示例: extension="{$filename##*.}"!请将 $ 符号移动到大括号外面,示例正确的写法为: 正确示例: extension="${filename##*.}" - Krista K
4
这显然是一个不容易解决的问题,对我来说很难判断下面的答案是否完全正确。令人惊奇的是,这不是(ba)sh的内置操作(答案似乎是使用模式匹配实现该函数)。因此,我决定像上面那样使用Python的os.path.splitext... - Peter Gibson
1
由于扩展名必须代表文件的性质,因此有一个“魔法”命令可以检查文件以确定其性质并提供标准扩展名。请参见我的回答 - F. Hauri - Give Up GitHub
6
这个问题本身存在问题,因为从操作系统和Unix文件系统的角度来看,不存在所谓的文件扩展名。使用"."分隔部分是一种人类约定,只有在人类同意遵循它的情况下才能起作用。例如,在“tar”程序中,可以决定使用“tar.”前缀而不是“.tar”后缀来命名输出文件--使用“tar.somedir”代替“somedir.tar”。由于这个原因,没有“通用,总是起作用”的解决方案--您必须编写与您特定需求和预期文件名匹配的代码。 - C. M.
1
文件 xyzzy.tar.gz 的扩展名是什么?或者 plugh.cfg.saved 呢?换句话说,您是将扩展名视为简单的技术问题还是语义问题? - paxdiablo
显示剩余4条评论
38个回答

1

使用示例文件 /Users/Jonathan/Scripts/bash/MyScript.sh,此代码:

MY_EXT=".${0##*.}"
ME=$(/usr/bin/basename "${0}" "${MY_EXT}")

将导致${ME}MyScript${MY_EXT}.sh


脚本:

#!/bin/bash
set -e

MY_EXT=".${0##*.}"
ME=$(/usr/bin/basename "${0}" "${MY_EXT}")

echo "${ME} - ${MY_EXT}"

一些测试:

$ ./MyScript.sh 
MyScript - .sh

$ bash MyScript.sh
MyScript - .sh

$ /Users/Jonathan/Scripts/bash/MyScript.sh
MyScript - .sh

$ bash /Users/Jonathan/Scripts/bash/MyScript.sh
MyScript - .sh

2
不确定为什么这个回答会有这么多的踩 - 它实际上比被接受的回答更有效率。(与后者相比,它还可以处理没有扩展名的输入文件名)。使用显式路径到 basename 可能有点过度设计。 - mklement0

1

从上面的答案中,模仿Python最短的一行代码是:

file, ext = os.path.splitext(path)

假设您的文件确实有扩展名,则为

EXT="${PATH##*.}"; FILE=$(basename "$PATH" .$EXT)

我在这个回答上得到了负评。考虑删除该回答,人们似乎不喜欢它。 - commonpike
basename不会删除扩展名,只会删除路径。 - user3657941
我已经很久没有看过man页面了,以至于我忘记了SUFFIX选项。 - user3657941
你必须知道您想要去掉哪个扩展名,然后才能知道要在 EXT 中放什么,这就是一只乌龟不停地叠加。(此外,应避免使用所有大写字母作为私有变量名称;它们保留用于系统变量。) - tripleee

0
也许在 tar 中有这样的选项,你检查过手册了吗?否则,您可以使用Bash 字符串扩展
test="mpc-1.0.1.tar.gz"
noExt="${test/.tar.gz/}" # Remove the string '.tar.gz'
echo $noExt

cd $(tar tf $1 | sed -n 1p) - Brent

0
为了使dir更有用(在指定没有路径的本地文件作为输入的情况下),我做了以下操作:
# Substring from 0 thru pos of filename
dir="${fullpath:0:${#fullpath} - ${#filename}}"
if [[ -z "$dir" ]]; then
    dir="./"
fi

这可以让您做一些有用的事情,比如将后缀添加到输入文件的基本名称中:

outfile=${dir}${base}_suffix.${ext}

testcase: foo.bar
dir: "./"
base: "foo"
ext: "bar"
outfile: "./foo_suffix.bar"

testcase: /home/me/foo.bar
dir: "/home/me/"
base: "foo"
ext: "bar"
outfile: "/home/me/foo_suffix.bar"

0

您可以使用

sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2-

获取文件名和

sed 's/^/./' | rev | cut -d. -f1  | rev

获取扩展名。

测试用例:

echo "filename.gz"     | sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2-
echo "filename.gz"     | sed 's/^/./' | rev | cut -d. -f1  | rev
echo "filename"        | sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2-
echo "filename"        | sed 's/^/./' | rev | cut -d. -f1  | rev
echo "filename.tar.gz" | sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2-
echo "filename.tar.gz" | sed 's/^/./' | rev | cut -d. -f1  | rev

0

这里有一个 sed 解决方案,可以提取多种形式的路径组件,并处理大多数边缘情况:

## Enter the input path and field separator character, for example:
## (separatorChar must not be present in inputPath)

inputPath="/path/to/Foo.bar"
separatorChar=":"

## sed extracts the path components and assigns them to output variables

oldIFS="$IFS"
IFS="$separatorChar"
read dirPathWithSlash dirPath fileNameWithExt fileName fileExtWithDot fileExt <<<"$(sed -En '
s/^[[:space:]]+//
s/[[:space:]]+$//
t l1
:l1
s/^([^/]|$)//
t
s/[/]+$//
t l2
:l2
s/^$/filesystem\/\
filesystem/p
t
h
s/^(.*)([/])([^/]+)$/\1\2\
\1\
\3/p
g
t l3
:l3
s/^.*[/]([^/]+)([.])([a-zA-Z0-9]+)$/\1\
\2\3\
\3/p
t
s/^.*[/](.+)$/\1/p
' <<<"$inputPath" | tr "\n" "$separatorChar")"
IFS="$oldIFS"

## Results (all use separatorChar=":")

## inputPath        = /path/to/Foo.bar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = Foo.bar
## fileName         = Foo
## fileExtWithDot   = .bar
## fileExt          = bar

## inputPath        = /path/to/Foobar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = Foobar
## fileName         = Foobar
## fileExtWithDot   =
## fileExt          =

## inputPath        = /path/to/...bar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = ...bar
## fileName         = ..
## fileExtWithDot   = .bar
## fileExt          = bar

## inputPath        = /path/to/..bar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = ..bar
## fileName         = .
## fileExtWithDot   = .bar
## fileExt          = bar

## inputPath        = /path/to/.bar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = .bar
## fileName         = .bar
## fileExtWithDot   = 
## fileExt          = 

## inputPath        = /path/to/...
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = ...
## fileName         = ...
## fileExtWithDot   =
## fileExt          =

## inputPath        = /path/to/Foo.
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = Foo.
## fileName         = Foo.
## fileExtWithDot   =
## fileExt          =

## inputPath        = / (the root directory)
## dirPathWithSlash = filesystem/
## dirPath          = filesystem
## fileNameWithExt  =
## fileName         =
## fileExtWithDot   =
## fileExt          =

## inputPath        =  (invalid because empty)
## dirPathWithSlash =
## dirPath          =
## fileNameWithExt  =
## fileName         =
## fileExtWithDot   =
## fileExt          =

## inputPath        = Foo/bar (invalid because doesn't start with a forward slash)
## dirPathWithSlash =
## dirPath          =
## fileNameWithExt  =
## fileName         =
## fileExtWithDot   =
## fileExt          =

下面是它的工作原理:

sed解析输入路径,并按顺序在单独的行上打印以下路径组件:

  • 带有尾部斜杠字符的目录路径
  • 不带尾部斜杠字符的目录路径
  • 带扩展名的文件名
  • 没有扩展名的文件名
  • 带有前导点字符的文件扩展名
  • 没有前导点字符的文件扩展名

trsed输出转换为以上路径组件的分隔符分隔字符串。

read使用分隔符作为字段分隔符(IFS="$separatorChar"),并将每个路径组件分配给其各自的变量。

下面是sed结构的工作原理:

  • s/^[[:space:]]+//s/[[:space:]]+$//会去除任何前导和/或尾随的空格字符。
  • t l1:l1刷新下一个s函数的t函数。
  • s/^([^/]|$)//t测试无效的输入路径(不以正斜杠开头的路径),在这种情况下,它将所有输出行留空并退出sed命令。
  • s/[/]+$//去除任何尾随斜杠。
  • t l2:l2刷新下一个s函数的t函数。
  • s/^$/filesystem\/\\[newline]filesystem/pt测试特殊情况,即输入路径由根目录/组成,在这种情况下,它打印filesystem/filesystem作为dirPathWithSlashdirPath输出行,将所有其他输出行留空,并退出sed命令。
  • h将输入路径保存在保留空间中。
  • s/^(.*)([/])([^/]+)$/\1\2\\[newline]\1\\[newline]\3/p打印dirPathWithSlashdirPathfileNameWithExt输出行。
  • g从保留空间中检索输入路径。
  • t l3:l3刷新下一个s函数的t函数。
  • s/^.*\[/]([^/]+)([.])([a-zA-Z0-9]+)$/\1\\[newline]\2\3\\[newline]\3/pt为存在文件扩展名(假定仅由字母数字字符组成)的情况打印fileNamefileExtWithDotfileExt输出行,然后退出sed命令。
  • s/^.*\[/](.+)$/\1/p为不存在文件扩展名的情况打印fileName但不包括fileExtWithDotfileExt输出行,然后退出sed命令。

-1
你也可以使用for循环和tr从路径中提取文件名...
for x in `echo $path | tr "/" " "`; do filename=$x; done

tr函数将路径中所有的"/"定界符替换为空格,从而形成一个字符串列表,for循环扫描这个列表并将最后一个元素存入变量filename中。


3
如果您打算这样做,可以尝试使用以下代码以避免产生不必要的问题:(IFS=/; for x in $path; do filename=$x; done)(...) 子 shell 是必需的,以便将赋值限定在 IFS 范围内。 - dubiousjim

-2
一个简单的Bash一行代码。我用它来从pwd中的所有文件中删除rst扩展名。
for each in `ls -1 *.rst`
do
     a=$(echo $each | wc -c)
     echo $each | cut -c -$(( $a-5 )) >> blognames
done

它是做什么的?

1)ls -1 *.rst 将在新行上将所有文件列出到 stdout(尝试一下)。

2)echo $each | wc -c 计算每个文件名中的字符数。

3)echo $each | cut -c -$(( $a-5 )) 选择最后4个字符,即 .rst


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接