在Bash脚本中从路径中获取文件名

485

如何只获取文件名而不包括扩展名和路径?

以下代码可以返回没有扩展名的文件名,但仍保留了路径:

source_file_filename_no_ext=${source_file%.*}

26
echo $(basename "${filepath%.*}") - signal
3
返回翻译后的文本:/路径/到/文件 - Tekz
6个回答

825

许多类UNIX操作系统都有一个basename可执行文件,用于非常类似的目的(以及路径的dirname):

pax> full_name=/tmp/file.txt
pax> base_name=$(basename ${full_name})
pax> echo ${base_name}
file.txt

不幸的是,这只会给你文件名,包括扩展名,所以你需要找到一种方法来去掉它。

因此,既然你必须这样做,你可以找到一种可以剥离路径扩展名的方法。

其中一种方法(这是一个仅限于bash的解决方案,不需要其他可执行文件):

pax> full_name=/tmp/xx/file.tar.gz
pax> xpath=${full_name%/*} 
pax> xbase=${full_name##*/}
pax> xfext=${xbase##*.}
pax> xpref=${xbase%.*}
pax> echo "path='${xpath}', pref='${xpref}', ext='${xfext}'"

path='/tmp/xx', pref='file.tar', ext='gz'

这段小代码设置了xpath(文件路径),xpref(文件前缀,你特别提出的内容)和xfext(文件扩展名)。


3
对于路径名,请使用path=$(dirname $filename);并没有一个专门提供扩展名的命令,但是@paxdiablo向您展示了shell如何实现。 - Jonathan Leffler
@jcubic你的方法是最慢的。paxdiablo的方法是完美且最快的。 - Zibri
3
您可以在手册参数扩展一节中了解更多有关仅使用bash解决方案的信息。 - Iain Samuel McLean Elder
1
@Startec,这是运行可执行文件(如basname)的方法,捕获其输出,并在bash命令中使用该输出,就像您明确输入一样。它类似于使用反引号,但更容易嵌套必要的内容。 - paxdiablo
pax> xpref=${xbase%.} 提取文件名第一次出现的扩展名。 如果我们需要提取多个文件名扩展名,那么这对我很有用 pax> xpref=${xbase%.$}(请注意附加的 $ 以进行字符串提取直到行末)。 - AjayKumarBasuthkar
显示剩余12条评论

87

basenamedirname命令更方便。这些是替代命令:

basenamedirname解决方案更加方便。这些是替代命令:

FILE_PATH="/opt/datastores/sda2/test.old.img"
echo "$FILE_PATH" | sed "s/.*\///"

这将返回类似于 basenametest.old.img

这是没有扩展名的salt文件名:

echo "$FILE_PATH" | sed -r "s/.+\/(.+)\..+/\1/"

它返回test.old

以下语句提供类似于dirname命令的完整路径。

echo "$FILE_PATH" | sed -r "s/(.+)\/.+/\1/"

它返回/opt/datastores/sda2


酷,如果有参数怎么办? - Pavel Niedoba
如果你的路径只是file.txt,那么这是行不通的。你的第二段代码应该是sed -r "s/(.+\/)?(.+)\..+/\2/ - Cardin
我已经测试了@Cardin,似乎它可以工作。FILE_PATH="file.txt"; echo "$FILE_PATH" | sed "s/.*\///"; echo "$FILE_PATH" | sed -r "s/.+\/(.+)\..+/\1/"; echo "$FILE_PATH" | sed -r "s/(.+)\/.+/\1/" 返回3次 file.txt - Fırat Küçük
1
@FıratKÜÇÜK 你是对的,我可能把它和其他东西混淆了。对此感到抱歉! - Cardin

27

这里有一种从路径中获取文件名的简单方法:

echo "$PATH" | rev | cut -d"/" -f1 | rev

如果文件名仅有一个点(即扩展名点),您可以使用以下方法删除扩展名:

cut -d"." -f1

9
这不是一个好的假设,而且有专门设计的工具和命令来正确地完成这个任务。 - Tony
2
此外,我不建议使用变量名PATH,因为这可能会与系统的PATH变量发生冲突。 - TabeaKischka

22
$ file=${$(basename $file_path)%.*}

11
在bash v4.4.7版本中,该语句返回“bad substitution”。我认为Fırat KÜÇÜK的sed解决方案更好,即$(basename $the_file_path) | sed "s/..*//"。 - Marshal
3
我的意思是 echo $(basename $the_file_path) | sed "s/\..*//",将其翻译为“将文件路径中的文件名提取出来,并通过sed命令去掉文件名中最后一个点及其后面的内容。” - Marshal
1
文件名中包含空格和点号,是否能正常工作?如果不能,应该如何处理? - 16851556
从您的文件名中删除空格和点。 - mihai
1
对于我来说,在Bash 4.2.46中也会出现“错误的替代”,但如果我改变运算符的顺序,即用file=$(basename ${file_path%.*})替换它,则可以正常工作。 - nt86

16

因为正则表达式很棒,所以这里提供一些更多的替代选项!

这里有一个简单的正则表达式可以完成工作:

 regex="[^/]*$"

例子(grep):

 FP="/hello/world/my/file/path/hello_my_filename.log"
 echo $FP | grep -oP "$regex"
 #Or using standard input
 grep -oP "$regex" <<< $FP

示例(awk):

 echo $FP | awk '{match($1, "$regex",a)}END{print a[0]}
 #Or using stardard input
 awk '{match($1, "$regex",a)}END{print a[0]} <<< $FP

如果您需要更复杂的正则表达式:例如,您的路径被包裹在一个字符串中。

 StrFP="my string is awesome file: /hello/world/my/file/path/hello_my_filename.log sweet path bro."

 #this regex matches a string not containing / and ends with a period 
 #then at least one word character 
 #so its useful if you have an extension

 regex="[^/]*\.\w{1,}"

 #usage
 grep -oP "$regex" <<< $StrFP

 #alternatively you can get a little more complicated and use lookarounds
 #this regex matches a part of a string that starts with /  that does not contain a / 
 ##then uses the lazy operator ? to match any character at any amount (as little as possible hence the lazy)
 ##that is followed by a space
 ##this allows use to match just a file name in a string with a file path if it has an exntension or not
 ##also if the path doesnt have file it will match the last directory in the file path 
 ##however this will break if the file path has a space in it.

 regex="(?<=/)[^/]*?(?=\s)"

 #to fix the above problem you can use sed to remove spaces from the file path only
 ## as a side note unfortunately sed has limited regex capibility and it must be written out in long hand.
 NewStrFP=$(echo $StrFP | sed 's:\(/[a-z]*\)\( \)\([a-z]*/\):\1\3:g')
 grep -oP "$regex" <<< $NewStrFP

使用正则表达式的完整解决方案:

即使文件名中有多个“.”,此函数可以提供Linux文件路径的带或不带扩展名的文件名。它还可以处理文件路径中的空格,以及如果文件路径嵌入或包含在字符串中。

#you may notice that the sed replace has gotten really crazy looking
#I just added all of the allowed characters in a linux file path
function Get-FileName(){
    local FileString="$1"
    local NoExtension="$2"
    local FileString=$(echo $FileString | sed 's:\(/[a-zA-Z0-9\<\>\|\\\:\)\(\&\;\,\?\*]*\)\( \)\([a-zA-Z0-9\<\>\|\\\:\)\(\&\;\,\?\*]*/\):\1\3:g')

    local regex="(?<=/)[^/]*?(?=\s)"

    local FileName=$(echo $FileString | grep -oP "$regex")

    if [[ "$NoExtension" != "" ]]; then
        sed 's:\.[^\.]*$::g' <<< $FileName
    else
        echo "$FileName"
    fi
}

## call the function with extension
Get-FileName "my string is awesome file: /hel lo/world/my/file test/path/hello_my_filename.log sweet path bro."

##call function without extension
Get-FileName "my string is awesome file: /hel lo/world/my/file test/path/hello_my_filename.log sweet path bro." "1"

如果你必须要处理一个Windows路径,你可以从这个开始:

 [^\\]*$       

13
$ source_file_filename_no_ext=${source_file%.*}
$ echo ${source_file_filename_no_ext##*/}

我只能用这个扩展名来获取通配符。 - Kebman
我更喜欢这种方式,因为它可以在纯Bash环境下完成,而无需运行任何其他可执行文件。但是,在这里有两个步骤,我认为只需要一个步骤,就像当前标记的重复答案中得票最高的答案一样:filename="${fileNameWithPath##*/}" - Alex Hall

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