我想分别获取文件名(不带扩展名)和扩展名。
到目前为止我发现的最佳解决方案是:
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 解释器。
有更好的想法吗?
extension="{$filename##*.}"
,就像我刚才所做的那样!将 $
移到花括号外:右: extension="${filename##*.}"
os.path.splitext
代替...
首先,获取不带路径的文件名:
filename=$(basename -- "$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"
或者,您可以关注路径的最后一个“/”而不是“。”即使您有不可预测的文件扩展名,它也应该工作:
filename="${fullfile##*/}"
您可能需要查看文档:
在网络上的“3.5.3 Shell 参数扩展”部分
在名为“参数扩展”部分的 bash 手册页中
~% FILE="example.tar.gz"
~% echo "${FILE%%.*}"
example
~% echo "${FILE%.*}"
example.tar
~% echo "${FILE#*.}"
tar.gz
~% echo "${FILE##*.}"
gz
有关详细信息,请参阅 Bash 手册中的 shell parameter expansion。
dinosaurs.in.tar
的游戏并将其压缩到 dinosaurs.in.tar.gz
:)
x.tar.gz
的扩展名是 gz
,文件名是 x.tar
就是这样。没有双重扩展之类的东西。我很确定 boost::filesystem 就是这样处理的。 (分割路径,change_extension ...)如果我没记错的话,它的行为是基于python的。
通常您已经知道扩展名,因此您可能希望使用:
basename filename .extension
例如:
basename /path/to/dir/filename.txt .txt
我们得到
filename
basename
的第二个论点令人大开眼界,亲切的先生/女士 :)
.zip
或 .ZIP
结尾的压缩目录。有没有办法可以做类似 basename $file {.zip,.ZIP}
的事情?
您可以使用 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
cut
没有 --complement
并且 sed
没有 -r
。
如果文件没有扩展名或没有文件名,这似乎不起作用。这是我正在使用的;它只使用内置函数并处理更多(但不是全部)病态文件名。
#!/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}}"
。写起来更简单。不确定是否有任何真正的速度差异或陷阱。
which bash
-> /bin/bash
;也许这是你的发行版?
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
命令)。
awk
这样的外部进程可以在五行内完成 :-)
您可以使用 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
梅伦在一篇博文的评论中写道:
使用 Bash,还有 ${file%.*}
可以获取不带扩展名的文件名,而 ${file##*.}
可以单独获取扩展名。那是,
file="thisfile.txt"
echo "filename: ${file%.*}"
echo "extension: ${file##*.}"
输出:
filename: thisfile
extension: txt
对于这个简单的任务,无需费心使用 awk
或 sed
甚至 perl
。有一个纯 Bash、os.path.splitext()
兼容的解决方案,它只使用参数扩展。
参考实现
将路径名路径拆分为一对 (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
os.path.splitext
的实现相匹配。对于可能有争议的输入,该实现是否合理是另一个话题。
"$root"
) 中的引号是如何工作的?如果它们被省略会发生什么? (我找不到有关此事的任何文档。)此外,这如何处理其中包含 *
或 ?
的文件名?
*
和 ?
并不特殊。所以我的问题的两个部分互相回答。我是否正确,这没有记录?还是应该从引号通常禁用全局扩展这一事实来理解?
root="${path#?}";root="${path::1}${root%.*}"
- 然后继续进行相同的操作以提取扩展名。
您可以使用 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
它通过无条件地删除最后两个(字母数字)扩展来工作。
[在安德斯·林达尔发表评论后再次更新]
$
来检查匹配的扩展名是否位于文件名的末尾。否则,像 i.like.tar.gz.files.tar.bz2
这样的文件名可能会产生意想不到的结果。
sed
链顺序相反,它仍然会。即使以 $
结尾,像 mpc-1.0.1.tar.bz2.tar.gz
这样的文件名也会删除 .tar.gz
和 .tar.bz2
。
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
以下是一些替代建议(主要在 awk
中),包括一些高级用例,例如提取软件包的版本号。
请注意,如果输入稍有不同,其中一些可能会失败,因此使用这些的任何人都应该验证他们的预期输入并在需要时调整正则表达式。
f='/path/to/complex/file.1.0.1.tar.gz'
# Filename : 'file.1.0.x.tar.gz'
echo "$f" | awk -F'/' '{print $NF}'
# Extension (last): 'gz'
echo "$f" | awk -F'[.]' '{print $NF}'
# Extension (all) : '1.0.1.tar.gz'
echo "$f" | awk '{sub(/[^.]*[.]/, "", $0)} 1'
# Extension (last-2): 'tar.gz'
echo "$f" | awk -F'[.]' '{print $(NF-1)"."$NF}'
# Basename : 'file'
echo "$f" | awk '{gsub(/.*[/]|[.].*/, "", $0)} 1'
# Basename-extended : 'file.1.0.1.tar'
echo "$f" | awk '{gsub(/.*[/]|[.]{1}[^.]+$/, "", $0)} 1'
# Path : '/path/to/complex/'
echo "$f" | awk '{match($0, /.*[/]/, a); print a[0]}'
# or
echo "$f" | grep -Eo '.*[/]'
# Folder (containing the file) : 'complex'
echo "$f" | awk -F'/' '{$1=""; print $(NF-1)}'
# Version : '1.0.1'
# Defined as 'number.number' or 'number.number.number'
echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?'
# Version - major : '1'
echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f1
# Version - minor : '0'
echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f2
# Version - patch : '1'
echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f3
# All Components : "path to complex file 1 0 1 tar gz"
echo "$f" | awk -F'[/.]' '{$1=""; print $0}'
# Is absolute : True (exit-code : 0)
# Return true if it is an absolute path (starting with '/' or '~/'
echo "$f" | grep -q '^[/]\|^~/'
所有用例都使用原始完整路径作为输入,而不依赖于中间结果。
最小和最简单的解决方案(单行)是:
$ file=/blaabla/bla/blah/foo.txt
echo $(basename ${file%.*}) # foo
echo
。一般来说,echo $(command)
最好写成 command
,除非您特别要求 shell 在显示结果之前对 command
的输出执行空白标记化和通配符扩展。测验:echo $(echo '*')
的输出是什么(如果这是您真正想要的,那么您真的只想要 echo *
)。
echo
命令。我只是用它来演示作为第二行结果出现在第三行的结果 foo
。
basename "${file%.*}"
会做同样的事情;您正在使用命令替换来捕获其输出,只是立即 echo
相同的输出。 (不引用,结果名义上是不同的;但这几乎不相关,更不用说特征了。)
basename "$file" .txt
还避免了参数替换的复杂性。
我认为如果你只需要文件名,你可以试试这个:
FULLPATH=/usr/share/X11/xorg.conf.d/50-synaptics.conf
# Remove all the prefix until the "/" character
FILENAME=${FULLPATH##*/}
# Remove all the prefix until the "." character
FILEEXTENSION=${FILENAME##*.}
# Remove a suffix, in our case, the filename. This will return the name of the directory that contains this file.
BASEDIRECTORY=${FULLPATH%$FILENAME}
echo "path = $FULLPATH"
echo "file name = $FILENAME"
echo "file extension = $FILEEXTENSION"
echo "base directory = $BASEDIRECTORY"
这就是全部=D。
您可以强制剪切以显示所有字段以及将 -
添加到字段编号的后续字段。
NAME=`basename "$FILE"`
EXTENSION=`echo "$NAME" | cut -d'.' -f2-`
因此,如果 FILE 为 eth0.pcap.gz
,则 EXTENSION 将为 pcap.gz
使用相同的逻辑,您还可以使用带有 cut 的 '-' 获取文件名,如下所示:
NAME=`basename "$FILE" | cut -d'.' -f-1`
这甚至适用于没有任何扩展名的文件名。
魔术文件识别
除了关于这个 Stack Overflow 问题的很多好的答案,我想补充一下:
在 Linux 和其他 unixen 下,有一个名为 file
的 magic 命令,它通过分析文件的一些首字节来进行文件类型检测。这是一个非常古老的工具,最初用于打印服务器(如果不是为......我不确定)。
file myfile.txt
myfile.txt: UTF-8 Unicode text
file -b --mime-type myfile.txt
text/plain
可以在 /etc/mime.types
中找到标准扩展(在我的 Debian GNU/Linux 桌面上。请参阅 man file
和 man mime.types
。也许您必须安装 file
实用程序和 mime-support
软件包):
grep $( file -b --mime-type myfile.txt ) </etc/mime.types
text/plain asc txt text pot brf srt
您可以创建一个 bash 函数来确定正确的扩展名。有一个小(不完美)示例:
file2ext() {
local _mimetype=$(file -Lb --mime-type "$1") _line _basemimetype
case ${_mimetype##*[/.-]} in
gzip | bzip2 | xz | z )
_mimetype=${_mimetype##*[/.-]}
_mimetype=${_mimetype//ip}
_basemimetype=$(file -zLb --mime-type "$1")
;;
stream )
_mimetype=($(file -Lb "$1"))
[ "${_mimetype[1]}" = "compressed" ] &&
_basemimetype=$(file -b --mime-type - < <(
${_mimetype,,} -d <"$1")) ||
_basemimetype=${_mimetype,,}
_mimetype=${_mimetype,,}
;;
executable ) _mimetype='' _basemimetype='' ;;
dosexec ) _mimetype='' _basemimetype='exe' ;;
shellscript ) _mimetype='' _basemimetype='sh' ;;
* )
_basemimetype=$_mimetype
_mimetype=''
;;
esac
while read -a _line ;do
if [ "$_line" == "$_basemimetype" ] ;then
[ "$_line[1]" ] &&
_basemimetype=${_line[1]} ||
_basemimetype=${_basemimetype##*[/.-]}
break
fi
done </etc/mime.types
case ${_basemimetype##*[/.-]} in
executable ) _basemimetype='' ;;
shellscript ) _basemimetype='sh' ;;
dosexec ) _basemimetype='exe' ;;
* ) ;;
esac
[ "$_mimetype" ] && [ "$_basemimetype" != "$_mimetype" ] &&
printf ${2+-v} $2 "%s.%s" ${_basemimetype##*[/.-]} ${_mimetype##*[/.-]} ||
printf ${2+-v} $2 "%s" ${_basemimetype##*[/.-]}
}
这个函数可以设置一个以后可以使用的 Bash 变量:
(这是来自@Petesh 正确答案的启发):
filename=$(basename "$fullfile")
filename="${filename%.*}"
file2ext "$fullfile" extension
echo "$fullfile -> $filename . $extension"
-c
标志执行此操作,但如果我被限制为仅使用 shell 脚本,我将使用此处概述的概念。谢谢!
$(python -c "'$1'.split('/')[-1]")
将使用子 shell 从路径字符串变量 $1
中获取带有扩展名的文件名(我在一些本地脚本中使用它)。我不会在 prod 中使用这种“魔法”,但是 Python 语言的这些特性对于简单的基于任务的事情来说非常棒。
$ F = "text file.test.txt"
$ echo ${F/*./}
txt
这适合文件名中的多个点和空格,但是如果没有扩展名,它会返回文件名本身。不过很容易检查;只需测试文件名和扩展名是否相同。
自然,此方法不适用于 .tar.gz 文件。然而,这可以通过两步过程来处理。如果扩展名是 gz 则再次检查是否还有 tar 扩展名。
for file in *.*; do ... ; done
+1 等常见情况下不应该出现这种情况
好的,如果我理解正确,这里的问题是如何获取具有多个扩展名的文件的名称和完整扩展名,例如 stuff.tar.gz
。
这对我有用:
fullfile="stuff.tar.gz"
fileExt=${fullfile#*.}
fileName=${fullfile%*.$fileExt}
这将为您提供 stuff
作为文件名和 .tar.gz
作为扩展名。它适用于任意数量的扩展,包括 0。希望这对遇到相同问题的人有所帮助 =)
os.path.splitext
,这是 OP 想要的)是 ('stuff.tar', '.gz')
。
只需使用 ${parameter%word}
在你的情况下:
${FILE%.*}
如果你想测试它,下面的所有工作,只需删除扩展:
FILE=abc.xyz; echo ${FILE%.*};
FILE=123.abc.xyz; echo ${FILE%.*};
FILE=abc; echo ${FILE%.*};
=
标志周围不应有空格。
这是唯一对我有用的:
path='folder/other_folder/file.js'
base=${path##*/}
echo ${base%.*}
>> file
这也可以用于字符串插值,但不幸的是您必须事先设置 base
。
我使用以下脚本
$ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev
foo
如何提取 fish 中的文件名和扩展名:
function split-filename-extension --description "Prints the filename and extension"
for file in $argv
if test -f $file
set --local extension (echo $file | awk -F. '{print $NF}')
set --local filename (basename $file .$extension)
echo "$filename $extension"
else
echo "$file is not a valid file"
end
end
end
警告:在最后一个点上拆分,这适用于其中包含点的文件名,但不适用于包含点的扩展名。请参见下面的示例。
用法:
$ split-filename-extension foo-0.4.2.zip bar.tar.gz
foo-0.4.2 zip # Looks good!
bar.tar gz # Careful, you probably want .tar.gz as the extension.
可能有更好的方法来做到这一点。随时编辑我的答案以改进它。
如果您将处理一组有限的扩展并且您知道所有这些扩展,请尝试以下操作:
switch $file
case *.tar
echo (basename $file .tar) tar
case *.tar.bz2
echo (basename $file .tar.bz2) tar.bz2
case *.tar.gz
echo (basename $file .tar.gz) tar.gz
# and so on
end
这没有作为第一个示例的警告,但是您确实必须处理每种情况,因此根据您可以预期的扩展数量,它可能会更加乏味。
这是带有 AWK 的代码。它可以做得更简单。但是我不擅长AWK。
filename$ ls
abc.a.txt a.b.c.txt pp-kk.txt
filename$ find . -type f | awk -F/ '{print $2}' | rev | awk -F"." '{$1="";print}' | rev | awk 'gsub(" ",".") ,sub(".$", "")'
abc.a
a.b.c
pp-kk
filename$ find . -type f | awk -F/ '{print $2}' | awk -F"." '{print $NF}'
txt
txt
txt
split()
来避免将 Awk 传递给 Awk。 awk -F / '{ n=split($2, a, "."); print a[n] }' uses
/` 作为顶级分隔符,然后拆分 .
上的第二个字段并打印新数组中的最后一个元素。
以前的答案没有使用 bash 正则表达式 这是一个纯 bash 解决方案,它将路径拆分为:
目录路径,当存在时,带有尾随 / 丢弃尾随 / 的正则表达式要长得多,以至于我没有发布它
文件名,不包括(最后一个)点扩展名
(最后一个)点扩展,其前导 .
该代码旨在处理所有可能的情况,欢迎您尝试。
#!/bin/bash
for path; do
####### the relevant part ######
[[ $path =~ ^(\.{1,2}|.*/\.{0,2})$|^(.*/)([^/]+)(\.[^/]*)$|^(.*/)(.+)$|^(.+)(\..*)$|^(.+)$ ]]
dirpath=${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[5]}
filename=${BASH_REMATCH[3]}${BASH_REMATCH[6]}${BASH_REMATCH[7]}${BASH_REMATCH[9]}
filext=${BASH_REMATCH[4]}${BASH_REMATCH[8]}
# dirpath should be non-null
[[ $dirpath ]] || dirpath='.'
################################
printf '%s=%q\n' \
path "$path" \
dirpath "$dirpath" \
filename "$filename" \
filext "$filext"
done
它是如何工作的?
基本上,它确保只有一个子表达式(在正则表达式中用 |
分隔)能够捕获输入。多亏了这一点,您可以连接存储在 BASH_REMATCH
中的所有相同类型的捕获组(例如,与目录路径相关的捕获组),因为最多一个将是非空的。
以下是一组扩展但并不详尽的示例的结果:
+--------------------------------------------------------+
| input dirpath filename filext |
+--------------------------------------------------------+
'' . '' ''
. . '' ''
.. .. '' ''
... . .. .
.file . .file ''
.file. . .file .
.file.. . .file. .
.file.Z . .file .Z
.file.sh.Z . .file.sh .Z
file . file ''
file. . file .
file.. . file. .
file.Z . file .Z
file.sh.Z . file.sh .Z
dir/ dir/ '' ''
dir/. dir/. '' ''
dir/... dir/ .. .
dir/.file dir/ .file ''
dir/.file. dir/ .file .
dir/.file.. dir/ .file. .
dir/.file.Z dir/ .file .Z
dir/.file.x.Z dir/ .file.x .Z
dir/file dir/ file ''
dir/file. dir/ file .
dir/file.. dir/ file. .
dir/file.Z dir/ file .Z
dir/file.x.Z dir/ file.x .Z
dir./. dir./. '' ''
dir./... dir./ .. .
dir./.file dir./ .file ''
dir./.file. dir./ .file .
dir./.file.. dir./ .file. .
dir./.file.Z dir./ .file .Z
dir./.file.sh.Z dir./ .file.sh .Z
dir./file dir./ file ''
dir./file. dir./ file .
dir./file.. dir./ file. .
dir./file.Z dir./ file .Z
dir./file.x.Z dir./ file.x .Z
dir// dir// '' ''
dir//. dir//. '' ''
dir//... dir// .. .
dir//.file dir// .file ''
dir//.file. dir// .file .
dir//.file.. dir// .file. .
dir//.file.Z dir// .file .Z
dir//.file.x.Z dir// .file.x .Z
dir//file dir// file ''
dir//file. dir// file .
dir//file.. dir// file. .
dir//file.Z dir// file .Z
dir//file.x.Z dir// file.x .Z
dir.//. dir.//. '' ''
dir.//... dir.// .. .
dir.//.file dir.// .file ''
dir.//.file. dir.// .file .
dir.//.file.. dir.// .file. .
dir.//.file.Z dir.// .file .Z
dir.//.file.x.Z dir.// .file.x .Z
dir.//file dir.// file ''
dir.//file. dir.// file .
dir.//file.. dir.// file. .
dir.//file.Z dir.// file .Z
dir.//file.x.Z dir.// file.x .Z
/ / '' ''
/. /. '' ''
/.. /.. '' ''
/... / .. .
/.file / .file ''
/.file. / .file .
/.file.. / .file. .
/.file.Z / .file .Z
/.file.sh.Z / .file.sh .Z
/file / file ''
/file. / file .
/file.. / file. .
/file.Z / file .Z
/file.sh.Z / file.sh .Z
/dir/ /dir/ '' ''
/dir/. /dir/. '' ''
/dir/... /dir/ .. .
/dir/.file /dir/ .file ''
/dir/.file. /dir/ .file .
/dir/.file.. /dir/ .file. .
/dir/.file.Z /dir/ .file .Z
/dir/.file.x.Z /dir/ .file.x .Z
/dir/file /dir/ file ''
/dir/file. /dir/ file .
/dir/file.. /dir/ file. .
/dir/file.Z /dir/ file .Z
/dir/file.x.Z /dir/ file.x .Z
/dir./. /dir./. '' ''
/dir./... /dir./ .. .
/dir./.file /dir./ .file ''
/dir./.file. /dir./ .file .
/dir./.file.. /dir./ .file. .
/dir./.file.Z /dir./ .file .Z
/dir./.file.sh.Z /dir./ .file.sh .Z
/dir./file /dir./ file ''
/dir./file. /dir./ file .
/dir./file.. /dir./ file. .
/dir./file.Z /dir./ file .Z
/dir./file.x.Z /dir./ file.x .Z
/dir// /dir// '' ''
/dir//. /dir//. '' ''
/dir//... /dir// .. .
/dir//.file /dir// .file ''
/dir//.file. /dir// .file .
/dir//.file.. /dir// .file. .
/dir//.file.Z /dir// .file .Z
/dir//.file.x.Z /dir// .file.x .Z
/dir//file /dir// file ''
/dir//file. /dir// file .
/dir//file.. /dir// file. .
/dir//file.Z /dir// file .Z
/dir//file.x.Z /dir// file.x .Z
/dir.//. /dir.//. '' ''
/dir.//... /dir.// .. .
/dir.//.file /dir.// .file ''
/dir.//.file. /dir.// .file .
/dir.//.file.. /dir.// .file. .
/dir.//.file.Z /dir.// .file .Z
/dir.//.file.x.Z /dir.// .file.x .Z
/dir.//file /dir.// file ''
/dir.//file. /dir.// file .
/dir.//file.. /dir.// file. .
/dir.//file.Z /dir.// file .Z
/dir.//file.x.Z /dir.// file.x .Z
// // '' ''
//. //. '' ''
//.. //.. '' ''
//... // .. .
//.file // .file ''
//.file. // .file .
//.file.. // .file. .
//.file.Z // .file .Z
//.file.sh.Z // .file.sh .Z
//file // file ''
//file. // file .
//file.. // file. .
//file.Z // file .Z
//file.sh.Z // file.sh .Z
//dir/ //dir/ '' ''
//dir/. //dir/. '' ''
//dir/... //dir/ .. .
//dir/.file //dir/ .file ''
//dir/.file. //dir/ .file .
//dir/.file.. //dir/ .file. .
//dir/.file.Z //dir/ .file .Z
//dir/.file.x.Z //dir/ .file.x .Z
//dir/file //dir/ file ''
//dir/file. //dir/ file .
//dir/file.. //dir/ file. .
//dir/file.Z //dir/ file .Z
//dir/file.x.Z //dir/ file.x .Z
//dir./. //dir./. '' ''
//dir./... //dir./ .. .
//dir./.file //dir./ .file ''
//dir./.file. //dir./ .file .
//dir./.file.. //dir./ .file. .
//dir./.file.Z //dir./ .file .Z
//dir./.file.sh.Z //dir./ .file.sh .Z
//dir./file //dir./ file ''
//dir./file. //dir./ file .
//dir./file.. //dir./ file. .
//dir./file.Z //dir./ file .Z
//dir./file.x.Z //dir./ file.x .Z
//dir// //dir// '' ''
//dir//. //dir//. '' ''
//dir//... //dir// .. .
//dir//.file //dir// .file ''
//dir//.file. //dir// .file .
//dir//.file.. //dir// .file. .
//dir//.file.Z //dir// .file .Z
//dir//.file.x.Z //dir// .file.x .Z
//dir//file //dir// file ''
//dir//file. //dir// file .
//dir//file.. //dir// file. .
//dir//file.Z //dir// file .Z
//dir//file.x.Z //dir// file.x .Z
//dir.//. //dir.//. '' ''
//dir.//... //dir.// .. .
//dir.//.file //dir.// .file ''
//dir.//.file. //dir.// .file .
//dir.//.file.. //dir.// .file. .
//dir.//.file.Z //dir.// .file .Z
//dir.//.file.x.Z //dir.// .file.x .Z
//dir.//file //dir.// file ''
//dir.//file. //dir.// file .
//dir.//file.. //dir.// file. .
//dir.//file.Z //dir.// file .Z
//dir.//file.x.Z //dir.// file.x .Z
如您所见,该行为不同于 basename
和 dirname
。例如 basename dir/
输出 dir
而正则表达式将为您提供一个空文件名。 .
和 ..
相同,它们被视为目录,而不是文件名。
我用 256 个字符的 10000 条路径对其进行计时,大约需要 1 秒,而等效的 POSIX shell 解决方案慢 2 倍,而基于野分叉的解决方案(for
循环内的外部调用)至少慢 60 倍。
备注: 没有必要测试包含 \n
或其他臭名昭著字符的路径,因为 bash 的正则表达式引擎以相同的方式处理所有字符。唯一能够打破当前逻辑的字符是 /
和 .
,它们以当前意想不到的方式混合或相乘。当我第一次发布我的答案时,我发现了一些我必须修复的边界案例;我不能说正则表达式是 100% 防弹的,但它现在应该非常健壮。
顺便说一句,这是产生相同输出的 POSIX shell 解决方案:
#!/bin/sh
for path; do
####### the relevant part ######
fullname=${path##*/}
case $fullname in
. | ..)
dirpath="$path"
filename=''
filext=''
;;
*)
dirpath=${path%"$fullname"}
dirpath=${dirpath:-.} # dirpath should be non-null
filename=${fullname#.}
filename="${fullname%"$filename"}${filename%.*}"
filext=${fullname#"$filename"}
;;
esac
################################
printf '%s=%s\n' \
path "$path" \
dirpath "$dirpath" \
filename "$filename" \
filext "$filext"
done
后记:有几点可能有人不同意上述代码给出的结果:
点文件的特殊情况:原因是点文件是一个 UNIX 概念。
的特殊情况。和..:恕我直言,将它们视为目录似乎很明显,但大多数库不这样做并强制用户对结果进行后处理。
不支持双扩展:这是因为您需要一个完整的数据库来存储所有有效的双扩展,最重要的是,因为文件扩展在 UNIX 中没有任何意义;例如,您可以调用 tar 存档 my_tarred_files 这完全没问题,您可以毫无问题地 tar xf my_tarred_files。
从 Petesh 答案构建,如果只需要文件名,则路径和扩展名都可以在一行中剥离,
filename=$(basename ${fullname%.*})
filename="$(basename "${fullname%.*}")"
basename
的第二个参数是可选的,但指定要剥离的扩展名。替换可能仍然有用,但 basename
实际上可能没有用,因为您实际上可以使用 shell 内置函数执行所有这些替换。
很大程度上基于@mklement0 的优秀,充满随机,有用的bashisms - 以及这个/其他问题/“那个该死的互联网”的其他答案......我把它全部包起来为我的(或您的).bash_profile
提供一点、更易于理解、可重用的函数,它负责处理(我认为)应该是更强大的 dirname
/basename
/ < em>你有什么..
function path { SAVEIFS=$IFS; IFS="" # stash IFS for safe-keeping, etc.
[[ $# != 2 ]] && echo "usage: path <path> <dir|name|fullname|ext>" && return # demand 2 arguments
[[ $1 =~ ^(.*/)?(.+)?$ ]] && { # regex parse the path
dir=${BASH_REMATCH[1]}
file=${BASH_REMATCH[2]}
ext=$([[ $file = *.* ]] && printf %s ${file##*.} || printf '')
# edge cases for extensionless files and files like ".nesh_profile.coffee"
[[ $file == $ext ]] && fnr=$file && ext='' || fnr=${file:0:$((${#file}-${#ext}))}
case "$2" in
dir) echo "${dir%/*}"; ;;
name) echo "${fnr%.*}"; ;;
fullname) echo "${fnr%.*}.$ext"; ;;
ext) echo "$ext"; ;;
esac
}
IFS=$SAVEIFS
}
使用示例...
SOMEPATH=/path/to.some/.random\ file.gzip
path $SOMEPATH dir # /path/to.some
path $SOMEPATH name # .random file
path $SOMEPATH ext # gzip
path $SOMEPATH fullname # .random file.gzip
path gobbledygook # usage: -bash <path> <dir|name|fullname|ext>
$IFS
(如果是,您可以使用 local
来本地化设置它的效果)。 - 最好使用 local
变量。 - 您的错误消息应该输出到 stderr
,而不是 stdout
(使用 1>&2
),并且您应该返回一个非零退出代码。 - 最好将 fullname
重命名为 basename
(前者建议使用 dir 组件的路径)。 - name
无条件地附加一个 .
(句点),即使原来没有。您可以简单地使用 basename
实用程序,但请注意它会忽略终止 /
。
一个简单的答案:
要扩展 POSIX variables answer,请注意您可以做更多有趣的模式。因此,对于此处详述的案例,您可以简单地执行以下操作:
tar -zxvf $1
cd ${1%.tar.*}
这将切断 .tar.
更一般地说,如果你想删除最后一次出现的 .
${1.*.*}
应该可以正常工作。
上述答案的链接似乎已失效。 Here's a great explanation of a bunch of the string manipulation you can do directly in Bash, from TLDP。
如果您还想允许空扩展名,这是我能想到的最短的:
echo 'hello.txt' | sed -r 's/.+\.(.+)|.*/\1/' # EXTENSION
echo 'hello.txt' | sed -r 's/(.+)\..+|(.*)/\1\2/' # FILENAME
第一行解释:它匹配 PATH.EXT 或 ANYTHING 并将其替换为 EXT。如果 ANYTHING 匹配,则不会捕获 ext 组。
恕我直言,已经给出了最佳解决方案(使用 shell 参数扩展),并且是目前评价最高的解决方案。
然而,我添加了这个只使用哑命令的命令,它效率不高,而且任何人都不应该使用它:
FILENAME=$(echo $FILE | cut -d . -f 1-$(printf $FILE | tr . '\n' | wc -l))
EXTENSION=$(echo $FILE | tr . '\n' | tail -1)
添加只是为了好玩:-)
这是我在编写 Bash 脚本以在名称与大小写冲突时使名称唯一时用于查找文件的名称和扩展名的算法。
#! /bin/bash
#
# Finds
# -- name and extension pairs
# -- null extension when there isn't an extension.
# -- Finds name of a hidden file without an extension
#
declare -a fileNames=(
'.Montreal'
'.Rome.txt'
'Loundon.txt'
'Paris'
'San Diego.txt'
'San Francisco'
)
echo "Script ${0} finding name and extension pairs."
echo
for theFileName in "${fileNames[@]}"
do
echo "theFileName=${theFileName}"
# Get the proposed name by chopping off the extension
name="${theFileName%.*}"
# get extension. Set to null when there isn't an extension
# Thanks to mklement0 in a comment above.
extension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '')
# a hidden file without extenson?
if [ "${theFileName}" = "${extension}" ] ; then
# hidden file without extension. Fixup.
name=${theFileName}
extension=""
fi
echo " name=${name}"
echo " extension=${extension}"
done
试运行。
$ config/Name\&Extension.bash
Script config/Name&Extension.bash finding name and extension pairs.
theFileName=.Montreal
name=.Montreal
extension=
theFileName=.Rome.txt
name=.Rome
extension=.txt
theFileName=Loundon.txt
name=Loundon
extension=.txt
theFileName=Paris
name=Paris
extension=
theFileName=San Diego.txt
name=San Diego
extension=.txt
theFileName=San Francisco
name=San Francisco
extension=
$
仅供参考:可在此处找到完整的音译程序和更多测试用例:https://www.dropbox.com/s/4c6m0f2e28a1vxf/avoid-clashes-code.zip?dl=0
extension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '')
不定期副业成功案例分享
basename
extension=$([[ "$filename" = *.* ]] && echo ".${filename##*.}" || echo '')
。请注意,如果存在扩展,则将返回包括初始.
,例如.txt
。