我想分别获取文件名(没有扩展名)和扩展名.
我到目前为止找到的最佳解决方案是:
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解释器.
有更好的想法吗?
首先,获取没有路径的文件名:
filename=$(basename -- "$fullfile") extension="${filename##*.}" filename="${filename%.*}"
或者,您可以专注于路径的最后一个'/'而不是'.' 即使你有不可预测的文件扩展名,它应该工作:
filename="${fullfile##*/}"
您可能需要查看文档:
在网上" 3.5.3外壳参数扩展 "部分
在bash手册页的"参数扩展"一节中
~% 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,但是如果您总是执行tar
,-z
那么您将知道扩展名.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
替换任意数量的字符的命令,后跟"."
行开头的字符,没有任何内容(即,它删除从行的开头到最后一个点的所有内容,包括在内).这是一个贪婪的替换,这是默认操作.
梅伦在博客文章评论中写道:
使用Bash,还${file%.*}
可以获取没有扩展名的文件名并${file##*.}
单独获取扩展名.那是,
file="thisfile.txt" echo "filename: ${file%.*}" echo "extension: ${file##*.}"
输出:
filename: thisfile extension: txt
无需费心awk
或者sed
甚至perl
为这个简单的任务.有一个纯Bash,os.path.splitext()
兼容的解决方案,只使用参数扩展.
文件os.path.splitext(path)
:
分裂路径名路径成一对
(root, ext)
,使得root + ext == path
,和分机是空的或用一个周期开始,并且包含至多一个周期.基本名称的前导句点被忽略;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"}"测试
以下是Ignoring leading period实现的测试用例,它应该与每个输入上的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发表评论后再次更新]
以下是一些替代建议(主要是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 '^[/]\|^~/'
所有用例都使用原始完整路径作为输入,而不依赖于中间结果.
该接受的答案行之有效的典型案例,但在失败边缘的情况下,即:
对于没有扩展名的文件名(在本答案的其余部分中称为后缀),extension=${filename##*.}
返回输入文件名而不是空字符串.
extension=${filename##*.}
不包括最初的.
,违反惯例.
盲目前置.
不适用于没有后缀的文件名.
filename="${filename%.*}"
将是空字符串,如果输入文件名以其开头.
并且不包含其他.
字符(例如.bash_profile
) - 与惯例相反.
因此,涵盖所有边缘情况的鲁棒解决方案的复杂性需要一个函数 - 参见下面的定义; 它可以返回路径的所有组件.
示例电话:
splitPath '/etc/bash.bashrc' dir fname fnameroot suffix # -> $dir == '/etc' # -> $fname == 'bash.bashrc' # -> $fnameroot == 'bash' # -> $suffix == '.bashrc'
请注意,输入路径之后的参数是自由选择的,位置变量名称.
要跳过那些之前不感兴趣的变量,指定_
(使用丢弃变量$_
)或''
; 例如,要仅提取文件名根和扩展名,请使用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
预期输出 - 注意边缘情况:
没有后缀的文件名
以.
(不被视为后缀的开头)开头的文件名
以/
(尾随/
被忽略)结尾的输入路径
仅.
作为文件名的输入路径(作为父路径返回)
一个具有多个.
前缀标记的文件名(只有最后一个被认为是后缀):
----- /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
最小和最简单的解决方案(单线)是:
$ file=/blaabla/bla/blah/foo.txt echo $(basename ${file%.*}) # foo
我想如果你只需要文件的名称,你可以试试这个:
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`
这适用于没有任何扩展名的文件名.
好的,如果我理解正确,这里的问题是如何获得具有多个扩展名的文件的名称和完整扩展名,例如stuff.tar.gz
.
这对我有用:
fullfile="stuff.tar.gz" fileExt=${fullfile#*.} fileName=${fullfile%*.$fileExt}
这将为您stuff
提供文件名和.tar.gz
扩展名.它适用于任何数量的扩展,包括0.希望这有助于任何有相同问题的人=)
除了这个Stack Overflow问题的很多好答案之外,我想补充一下:
在Linux和其他unixen下,有一个名为magic的魔术命令file
,它通过分析文件的第一个字节来进行文件类型检测.这是一个非常古老的工具,最初用于打印服务器(如果没有创建...我不确定).
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 )您可以创建一个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此函数可以设置一个可以在以后使用的Bash变量:
(这是受@Petesh正确答案的启发):
filename=$(basename "$fullfile") filename="${filename%.*}" file2ext "$fullfile" extension echo "$fullfile -> $filename . $extension"
18> Joydip Datta..:我使用以下脚本
$ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev foo
19> 小智..:$ F = "text file.test.txt" $ echo ${F/*./} txt这适用于文件名中的多个点和空格,但是如果没有扩展名,则返回文件名本身.虽然容易检查; 只测试文件名和扩展名是否相同.
当然,这种方法不适用于.tar.gz文件.但是,这可以通过两个步骤处理.如果扩展名是gz,则再次检查以查看是否还有tar扩展名.
20> Dennis..:如何在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这并不会有警告作为第一个例子,但你必须处理每一个案件,因此它可以根据你多少可以扩展期待更加繁琐。