问:
我想分别获取文件名(不带扩展名)和扩展名。
到目前为止我发现的最佳解决方案是:
NAME=`echo "$FILE" | cut -d'.' -f1`
EXTENSION=`echo "$FILE" | cut -d'.' -f2`
这是错误的,因为如果文件名包含多个 . 字符,它将不起作用。如果,假设我有 a.b.js,它将考虑 a 和 b.js,而不是 a.b 和 js。
它可以很容易地在 Python 中完成
file, ext = os.path.splitext(path)
但如果可能的话,我不希望为此启动 Python 解释器。
有更好的想法吗?
答1:
保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com
首先,获取不带路径的文件名:
filename=$(basename -- "$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"
或者,您可以关注路径的最后一个“/”而不是“。”即使您有不可预测的文件扩展名,它也应该工作:
filename="${fullfile##*/}"
您可能需要查看文档:
在网络上的“3.5.3 Shell 参数扩展”部分
在名为“参数扩展”部分的 bash 手册页中
查看 gnu.org/software/bash/manual/html_node/… 了解完整的功能集。
在 "$fullfile" 中添加一些引号,否则您将面临破坏文件名的风险。
哎呀,您甚至可以编写 filename="${fullfile##*/}" 并避免调用额外的 basename
如果文件没有扩展名,则此“解决方案”不起作用-相反,会输出整个文件名,考虑到无扩展名的文件无处不在,这非常糟糕。
修复处理不带扩展名的文件名:extension=$([[ "$filename" = *.* ]] && echo ".${filename##*.}" || echo '')。请注意,如果存在扩展,则将返回包括初始.,例如.txt。
答2:
保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com
~% FILE="example.tar.gz"
~% echo "${FILE%%.*}"
example
~% echo "${FILE%.*}"
example.tar
~% echo "${FILE#*.}"
tar.gz
~% echo "${FILE##*.}"
gz
有关详细信息,请参阅 Bash 手册中的 shell parameter expansion。
您(也许是无意的)提出了一个很好的问题,即如果文件名的“扩展名”部分中有 2 个点,例如 .tar.gz,该怎么办......我从来没有考虑过这个问题,我怀疑它是如果不预先知道所有可能的有效文件扩展名,则无法解决。
为什么不能解决?在我的示例中,应该认为该文件包含两个扩展名,而不是带有两个点的扩展名。您分别处理这两个扩展。
在词汇基础上无法解决,您需要检查文件类型。考虑一下您是否有一个名为 dinosaurs.in.tar 的游戏并将其压缩到 dinosaurs.in.tar.gz :)
如果您在完整路径中传递,这会变得更加复杂。我的一个有一个'。在路径中间的目录中,但文件名中没有。示例“a/bc/d/e/filename”将结束“.c/d/e/filename”
显然没有 x.tar.gz 的扩展名是 gz,文件名是 x.tar 就是这样。没有双重扩展之类的东西。我很确定 boost::filesystem 就是这样处理的。 (分割路径,change_extension ...)如果我没记错的话,它的行为是基于python的。
答3:
huntsbot.com精选全球7大洲远程工作机会,涵盖各领域,帮助想要远程工作的数字游民们能更精准、更高效的找到对方。
通常您已经知道扩展名,因此您可能希望使用:
basename filename .extension
例如:
basename /path/to/dir/filename.txt .txt
我们得到
filename
basename 的第二个论点令人大开眼界,亲切的先生/女士 :)
以及如何使用这种技术提取扩展名? ;) 等一下!我们实际上并不事先知道。
假设您有一个以 .zip 或 .ZIP 结尾的压缩目录。有没有办法可以做类似 basename $file {.zip,.ZIP} 的事情?
虽然这只回答了部分 OPs 问题,但它确实回答了我输入谷歌的问题。 :-) 非常漂亮!
简单且符合 POSIX
答4:
huntsbot.com汇聚了国内外优秀的初创产品创意,可按收入、分类等筛选,希望这些产品与实践经验能给您带来灵感。
您可以使用 POSIX 参数扩展的魔力:
bash-3.2$ FILENAME=somefile.tar.gz
bash-3.2$ echo "${FILENAME%%.*}"
somefile
bash-3.2$ echo "${FILENAME%.*}"
somefile.tar
需要注意的是,如果您的文件名采用 ./somefile.tar.gz 形式,那么 echo ${FILENAME%%.*} 会贪婪地删除与 . 的最长匹配项,并且您将获得空字符串。
(您可以使用临时变量解决此问题:
FULL_FILENAME=$FILENAME
FILENAME=${FULL_FILENAME##*/}
echo ${FILENAME%%.*}
)
此site解释更多。
${variable%pattern}
Trim the shortest match from the end
${variable##pattern}
Trim the longest match from the beginning
${variable%%pattern}
Trim the longest match from the end
${variable#pattern}
Trim the shortest match from the beginning
比 Joachim 的答案简单得多,但我总是要查找 POSIX 变量替换。此外,这在 Max OSX 上运行,其中 cut 没有 --complement 并且 sed 没有 -r。
答5:
huntsbot.com聚合了超过10+全球外包任务平台的外包需求,寻找外包任务与机会变的简单与高效。
如果文件没有扩展名或没有文件名,这似乎不起作用。这是我正在使用的;它只使用内置函数并处理更多(但不是全部)病态文件名。
#!/bin/bash
for fullpath in "$@"
do
filename="${fullpath##*/}" # Strip longest match of */ from start
dir="${fullpath:0:${#fullpath} - ${#filename}}" # Substring from 0 thru pos of filename
base="${filename%.[^.]*}" # Strip shortest match of . plus at least one non-dot char from end
ext="${filename:${#base} + 1}" # Substring from len of base thru end
if [[ -z "$base" && -n "$ext" ]]; then # If we have an extension and no base, it's really the base
base=".$ext"
ext=""
fi
echo -e "$fullpath:\n\tdir = \"$dir\"\n\tbase = \"$base\"\n\text = \"$ext\""
done
这里有一些测试用例:
$ basename-and-extension.sh / /home/me/ /home/me/file /home/me/file.tar /home/me/file.tar.gz /home/me/.hidden /home/me/.hidden.tar /home/me/.. .
/:
dir = "/"
base = ""
ext = ""
/home/me/:
dir = "/home/me/"
base = ""
ext = ""
/home/me/file:
dir = "/home/me/"
base = "file"
ext = ""
/home/me/file.tar:
dir = "/home/me/"
base = "file"
ext = "tar"
/home/me/file.tar.gz:
dir = "/home/me/"
base = "file.tar"
ext = "gz"
/home/me/.hidden:
dir = "/home/me/"
base = ".hidden"
ext = ""
/home/me/.hidden.tar:
dir = "/home/me/"
base = ".hidden"
ext = "tar"
/home/me/..:
dir = "/home/me/"
base = ".."
ext = ""
.:
dir = ""
base = "."
ext = ""
我经常看到 dir="${fullpath%$filename}" 而不是 dir="${fullpath:0:${#fullpath} - ${#filename}}"。写起来更简单。不确定是否有任何真正的速度差异或陷阱。
这使用 #!/bin/bash 这几乎总是错误的。如果可能,首选 #!/bin/sh 或 #!/usr/bin/env bash 如果不是。
@Good Person:我不知道这几乎总是错误的:which bash -> /bin/bash ;也许这是你的发行版?
@vol7ron - 在许多发行版中,bash 位于 /usr/local/bin/bash 中。在 OSX 上,许多人在 /opt/local/bin/bash 中安装更新的 bash。因此 /bin/bash 是错误的,应该使用 env 来找到它。更好的是使用 /bin/sh 和 POSIX 结构。除了在 solaris 上,这是一个 POSIX shell。
@GoodPerson,但如果您更喜欢 bash,为什么要使用 sh?这不是说,既然可以使用 sh,为什么还要使用 Perl?
答6:
huntsbot.com高效搞钱,一站式跟进超10+任务平台外包需求
pax> echo a.b.js | sed 's/\.[^.]*$//'
a.b
pax> echo a.b.js | sed 's/^.*\.//'
js
工作正常,所以你可以使用:
pax> FILE=a.b.js
pax> NAME=$(echo "$FILE" | sed 's/\.[^.]*$//')
pax> EXTENSION=$(echo "$FILE" | sed 's/^.*\.//')
pax> echo $NAME
a.b
pax> echo $EXTENSION
js
顺便说一下,这些命令的工作方式如下。
NAME 的命令替换了一个 “.” 字符,后跟任意数量的非 “.” 字符,直到行尾,没有任何内容(即,它删除从最后的 “.” 到行尾的所有内容线,包括)。这基本上是使用正则表达式技巧的非贪婪替换。
EXTENSION 的命令在行首替换任意数量的字符,后跟一个 “.” 字符,没有任何内容(即,它删除从行首到最后一个点的所有内容,包括在内)。这是一个贪婪的替换,它是默认操作。
对于没有扩展名的文件,此中断会打印相同的名称和扩展名。所以我使用 sed 's,\.[^\.]*$,,' 作为名称,使用 sed 's,.*\.,., ;t ;g' 作为扩展名(使用非典型的 test 和 get 命令,以及典型的 substitute 命令)。
您可以在计算 NAME 之后测试它和 FILE 是否相等,如果相等,请将 EXTENSION 设置为空字符串。
从根本上说,将外部进程用于 shell 可以自己做的事情是一种反模式。
Tripleee:shell 可以在一百行内完成很多事情,而像 awk 这样的外部进程可以在五行内完成 :-)
答7:
与HuntsBot一起,探索全球自由职业机会–huntsbot.com
您可以使用 basename。
例子:
$ basename foo-bar.tar.gz .tar.gz
foo-bar
您确实需要为 basename 提供应删除的扩展名,但是如果您始终使用 -z 执行 tar,那么您知道扩展名将是 .tar.gz。
这应该做你想要的:
tar -zxvf $1
cd $(basename $1 .tar.gz)
我想 cd $(basename $1 .tar.gz) 适用于 .gz 文件。但在问题中他提到了Archive files have several extensions: tar.gz, tat.xz, tar.bz2
Tomi Po 在 2 年前发布了同样的内容。
嗨 Blauhirn,哇,这是一个老问题。我认为日期发生了一些事情。我清楚地记得在被问到这个问题后不久就回答了这个问题,而且那里只有几个其他答案。难道是这个问题与另一个问题合并了,这样做吗?
是的,我没记错。我最初在被问到的同一天回答了这个问题 stackoverflow.com/questions/14703318/…,2 年后它被合并到这个问题中。当我的答案以这种方式移动时,我几乎不能因为重复的答案而受到指责。
答8:
huntsbot.com全球7大洲远程工作机会,探索不一样的工作方式
梅伦在一篇博文的评论中写道:
使用 Bash,还有 ${file%.} 可以获取不带扩展名的文件名,而 ${file##.} 可以单独获取扩展名。那是,
file="thisfile.txt"
echo "filename: ${file%.*}"
echo "extension: ${file##*.}"
输出:
filename: thisfile
extension: txt
@REACHUS:见 gnu.org/software/bash/manual/html_node/…
答9:
huntsbot.com高效搞钱,一站式跟进超10+任务平台外包需求
对于这个简单的任务,无需费心使用 awk 或 sed 甚至 perl。有一个纯 Bash、os.path.splitext() 兼容的解决方案,它只使用参数扩展。
参考实现
os.path.splitext(path) 的文档:
将路径名路径拆分为一对 (root, ext),使得 root + ext == path,并且 ext 为空或以句点开头并且最多包含一个句点。基本名称上的前导句点被忽略; splitext(‘.cshrc’) 返回 (‘.cshrc’, ‘’)。
Python代码:
root, ext = os.path.splitext(path)
Bash 实现
表彰领先时期
root="${path%.*}"
ext="${path#"$root"}"
忽略领先时期
root="${path#.}";root="${path%"$root"}${root%.*}"
ext="${path#"$root"}"
测试
以下是忽略前导句点实现的测试用例,它应该与每个输入上的 Python 参考实现相匹配。
|---------------|-----------|-------|
|path |root |ext |
|---------------|-----------|-------|
|' .txt' |' ' |'.txt' |
|' .txt.txt' |' .txt' |'.txt' |
|' txt' |' txt' |'' |
|'*.txt.txt' |'*.txt' |'.txt' |
|'.cshrc' |'.cshrc' |'' |
|'.txt' |'.txt' |'' |
|'?.txt.txt' |'?.txt' |'.txt' |
|'\n.txt.txt' |'\n.txt' |'.txt' |
|'\t.txt.txt' |'\t.txt' |'.txt' |
|'a b.txt.txt' |'a b.txt' |'.txt' |
|'a*b.txt.txt' |'a*b.txt' |'.txt' |
|'a?b.txt.txt' |'a?b.txt' |'.txt' |
|'a\nb.txt.txt' |'a\nb.txt' |'.txt' |
|'a\tb.txt.txt' |'a\tb.txt' |'.txt' |
|'txt' |'txt' |'' |
|'txt.pdf' |'txt' |'.pdf' |
|'txt.tar.gz' |'txt.tar' |'.gz' |
|'txt.txt' |'txt' |'.txt' |
|---------------|-----------|-------|
试验结果
所有测试都通过了。
不,text.tar.gz 的基本文件名应该是 text,扩展名是 .tar.gz
@frederick99 正如我所说,这里的解决方案与 Python 中 os.path.splitext 的实现相匹配。对于可能有争议的输入,该实现是否合理是另一个话题。
模式 ("$root") 中的引号是如何工作的?如果它们被省略会发生什么? (我找不到有关此事的任何文档。)此外,这如何处理其中包含 * 或 ? 的文件名?
好的,测试表明引号使模式成为文字,即 * 和 ? 并不特殊。所以我的问题的两个部分互相回答。我是否正确,这没有记录?还是应该从引号通常禁用全局扩展这一事实来理解?
绝妙的答案!我将建议一个稍微简单的变体来计算根:root="${path#?}";root="${path::1}${root%.*}" - 然后继续进行相同的操作以提取扩展名。
答10:
huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。
您可以使用 cut 命令删除最后两个扩展名(“.tar.gz” 部分):
$ echo "foo.tar.gz" | cut -d'.' --complement -f2-
foo
正如克莱顿休斯在评论中指出的那样,这不适用于问题中的实际示例。因此,作为替代方案,我建议使用带有扩展正则表达式的 sed,如下所示:
$ echo "mpc-1.0.1.tar.gz" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'
mpc-1.0.1
它通过无条件地删除最后两个(字母数字)扩展来工作。
[在安德斯·林达尔发表评论后再次更新]
这仅适用于文件名/路径不包含任何其他点的情况: echo "mpc-1.0.1.tar.gz" |切-d'。 --complement -f2- 产生“mpc-1”(仅由 . 分隔后的前 2 个字段。)
@ClaytonHughes你是对的,我应该更好地测试它。添加了另一个解决方案。
sed 表达式应使用 $ 来检查匹配的扩展名是否位于文件名的末尾。否则,像 i.like.tar.gz.files.tar.bz2 这样的文件名可能会产生意想不到的结果。
@AndersLindahl 如果扩展的顺序与 sed 链顺序相反,它仍然会。即使以 $ 结尾,像 mpc-1.0.1.tar.bz2.tar.gz 这样的文件名也会删除 .tar.gz 和 .tar.bz2。
$ echo "foo.tar.gz" |切-d'。 -f2- WITHOUT --complement 将第二个拆分项放到字符串的末尾 $ echo "foo.tar.gz" |切-d'。 -f2- tar.gz
答11:
huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。
accepted answer 在典型 情况 中运行良好,但在边缘 情况下失败,即:
对于没有扩展名的文件名(在此答案的其余部分中称为后缀), extension=${filename##*.} 返回输入文件名而不是空字符串。
extension=${filename##*.} 不包含开头的 .,这与约定相反。盲目的前置。不适用于没有后缀的文件名。
盲目的前置。不适用于没有后缀的文件名。
如果输入文件名以 .并且不包含进一步的内容。字符(例如,.bash_profile) - 违反约定。
因此,涵盖所有边缘情况的强大解决方案的复杂性需要一个函数 - 请参见下面的定义;它可以返回路径的所有组件。
示例调用:
splitPath '/etc/bash.bashrc' dir fname fnameroot suffix
# -> $dir == '/etc'
# -> $fname == 'bash.bashrc'
# -> $fnameroot == 'bash'
# -> $suffix == '.bashrc'
请注意,输入路径之后的参数是自由选择的,位置变量 names。 要跳过那些之前不感兴趣的变量,请指定 (使用一次性变量$) 或 ‘’;例如,要仅提取文件名根和扩展名,请使用 splitPath ‘/etc/bash.bashrc’ _ _ fnameroot extension。
# SYNOPSIS
# splitPath path varDirname [varBasename [varBasenameRoot [varSuffix]]]
# DESCRIPTION
# Splits the specified input path into its components and returns them by assigning
# them to variables with the specified *names*.
# Specify '' or throw-away variable _ to skip earlier variables, if necessary.
# The filename suffix, if any, always starts with '.' - only the *last*
# '.'-prefixed token is reported as the suffix.
# As with `dirname`, varDirname will report '.' (current dir) for input paths
# that are mere filenames, and '/' for the root dir.
# As with `dirname` and `basename`, a trailing '/' in the input path is ignored.
# A '.' as the very first char. of a filename is NOT considered the beginning
# of a filename suffix.
# EXAMPLE
# splitPath '/home/jdoe/readme.txt' parentpath fname fnameroot suffix
# echo "$parentpath" # -> '/home/jdoe'
# echo "$fname" # -> 'readme.txt'
# echo "$fnameroot" # -> 'readme'
# echo "$suffix" # -> '.txt'
# ---
# splitPath '/home/jdoe/readme.txt' _ _ fnameroot
# echo "$fnameroot" # -> 'readme'
splitPath() {
local _sp_dirname= _sp_basename= _sp_basename_root= _sp_suffix=
# simple argument validation
(( $# >= 2 )) || { echo "$FUNCNAME: ERROR: Specify an input path and at least 1 output variable name." >&2; exit 2; }
# extract dirname (parent path) and basename (filename)
_sp_dirname=$(dirname "$1")
_sp_basename=$(basename "$1")
# determine suffix, if any
_sp_suffix=$([[ $_sp_basename = *.* ]] && printf %s ".${_sp_basename##*.}" || printf '')
# determine basename root (filemane w/o suffix)
if [[ "$_sp_basename" == "$_sp_suffix" ]]; then # does filename start with '.'?
_sp_basename_root=$_sp_basename
_sp_suffix=''
else # strip suffix from filename
_sp_basename_root=${_sp_basename%$_sp_suffix}
fi
# assign to output vars.
[[ -n $2 ]] && printf -v "$2" "$_sp_dirname"
[[ -n $3 ]] && printf -v "$3" "$_sp_basename"
[[ -n $4 ]] && printf -v "$4" "$_sp_basename_root"
[[ -n $5 ]] && printf -v "$5" "$_sp_suffix"
return 0
}
test_paths=(
'/etc/bash.bashrc'
'/usr/bin/grep'
'/Users/jdoe/.bash_profile'
'/Library/Application Support/'
'readme.new.txt'
)
for p in "${test_paths[@]}"; do
echo ----- "$p"
parentpath= fname= fnameroot= suffix=
splitPath "$p" parentpath fname fnameroot suffix
for n in parentpath fname fnameroot suffix; do
echo "$n=${!n}"
done
done
执行该功能的测试代码:
test_paths=(
'/etc/bash.bashrc'
'/usr/bin/grep'
'/Users/jdoe/.bash_profile'
'/Library/Application Support/'
'readme.new.txt'
)
for p in "${test_paths[@]}"; do
echo ----- "$p"
parentpath= fname= fnameroot= suffix=
splitPath "$p" parentpath fname fnameroot suffix
for n in parentpath fname fnameroot suffix; do
echo "$n=${!n}"
done
done
预期输出 - 注意边缘情况:
没有后缀的文件名
以 . 开头的文件名(不考虑后缀的开始)
以 / 结尾的输入路径(尾随 / 被忽略)
仅作为文件名的输入路径(. 作为父路径返回)
具有多个 .-prefixed 标记的文件名(仅最后一个被视为后缀):
----- /etc/bash.bashrc
parentpath=/etc
fname=bash.bashrc
fnameroot=bash
suffix=.bashrc
----- /usr/bin/grep
parentpath=/usr/bin
fname=grep
fnameroot=grep
suffix=
----- /Users/jdoe/.bash_profile
parentpath=/Users/jdoe
fname=.bash_profile
fnameroot=.bash_profile
suffix=
----- /Library/Application Support/
parentpath=/Library
fname=Application Support
fnameroot=Application Support
suffix=
----- readme.new.txt
parentpath=.
fname=readme.new.txt
fnameroot=readme.new
suffix=.txt
原文链接:https://www.huntsbot.com/qa/A8k3/extract-filename-and-extension-in-bash?lang=zh_CN&from=csdn
打造属于自己的副业,开启自由职业之旅,从huntsbot.com开始!