我想分别获取文件名(不带扩展名)和扩展名。
到目前为止,我发现的最佳解决方案是:
NAME=`echo "$FILE" | cut -d'.' -f1`
EXTENSION=`echo "$FILE" | cut -d'.' -f2`
这是错误的,因为如果文件名包含多个,则无法使用.
字符。如果说我有abjs
,它将考虑a
和b.js
,而不是ab
和js
。
可以使用 Python 轻松完成
file, ext = os.path.splitext(path)
但如果可能的话,我不希望为此而启动 Python 解释器。
还有更好的主意吗?
首先,获取不带路径的文件名:
filename=$(basename -- "$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"
或者,您可以将焦点放在路径的最后一个 “/” 而不是 “。”。即使您具有无法预测的文件扩展名,它也应能正常工作:
filename="${fullfile##*/}"
您可能需要检查文档:
~% FILE="example.tar.gz"
~% echo "${FILE%%.*}"
example
~% echo "${FILE%.*}"
example.tar
~% echo "${FILE#*.}"
tar.gz
~% echo "${FILE##*.}"
gz
有关更多详细信息,请参见 Bash 手册中的shell 参数扩展 。
通常,您已经知道扩展名,因此您可能希望使用:
basename filename .extension
例如:
basename /path/to/dir/filename.txt .txt
我们得到
filename
您可以使用 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%%.*}
)
该网站解释了更多。
${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
如果文件没有扩展名或文件名,那似乎不起作用。这是我正在使用的;它仅使用内置函数并处理更多(但不是全部)病理文件名。
#!/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 = ""
您可以使用basename
。
例:
$ basename foo-bar.tar.gz .tar.gz
foo-bar
您确实需要为 basename 提供要删除的扩展名,但是,如果始终使用-z
执行tar
,则知道扩展名将是.tar.gz
。
这应该做您想要的:
tar -zxvf $1
cd $(basename $1 .tar.gz)
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
的命令将替换任意数量的字符,后跟"."
行开头的字符,没有任何内容(即,它将删除从行开头到最后一个点的所有内容)。这是一个贪婪的替换,这是默认操作。
梅伦(Mellen)在博客文章中发表评论:
使用 Bash,还有${file%.*}
来获取不带扩展名的文件名,还有${file##*.}
来获取扩展名。那是,
file="thisfile.txt"
echo "filename: ${file%.*}"
echo "extension: ${file##*.}"
输出:
filename: thisfile
extension: txt
无需为这个简单的任务而烦恼awk
或sed
甚至perl
。有一个纯 Bash 的os.path.splitext()
兼容解决方案,仅使用参数扩展。
将路径名 path 分成一对
(root, ext)
,使root + ext == path
,并且ext为空或以一个句点开头,并且最多包含一个句点。基本名称上的前导句号将被忽略;splitext('.cshrc')
('.cshrc', '')
splitext('.cshrc')
返回('.cshrc', '')
。
Python 代码:
root, ext = os.path.splitext(path)
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' |
|---------------|-----------|-------|
所有测试均通过。
您可以使用cut
命令删除最后两个扩展名( ".tar.gz"
部分):
$ echo "foo.tar.gz" | cut -d'.' --complement -f2-
foo
正如克莱顿 · 休斯(Clayton Hughes)在评论中指出的那样,这不适用于问题中的实际示例。因此,我建议将sed
与扩展的正则表达式结合使用,如下所示:
$ echo "mpc-1.0.1.tar.gz" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'
mpc-1.0.1
它通过无条件删除最后两个(字母数字)扩展名来工作。
[在收到安德斯 · 林达尔(Anders Lindahl)的评论后再次更新]