在Bash中提取文件名和扩展名

我想分别得到文件名(没有扩展名)和扩展名。

到目前为止,我发现的最佳解决scheme是:

NAME=`echo "$FILE" | cut -d'.' -f1` EXTENSION=`echo "$FILE" | cut -d'.' -f2` 

这是错误的,因为如果文件名包含多个“。”,则不起作用。 字符。 如果我们说,我有abjs,它会考虑ab.js ,而不是abjs

这可以很容易地在Python中完成

 file, ext = os.path.splitext(path) 

但如果可能的话,我宁愿不要为此解雇Python解释器。

任何更好的想法?

首先,获取没有path的文件名:

 filename=$(basename "$fullfile") extension="${filename##*.}" filename="${filename%.*}" 

或者,您可以专注于path的最后一个“/”而不是“。”。 即使你有不可预知的文件扩展名也应该工作:

 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 

你可以使用POSIXvariables的魔力:

 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%%.*}会贪婪地删除最长的匹配. 你会有空的string。

(你可以用临时variables来解决这个问题:

 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 

如果文件没有扩展名,或者没有文件名,这似乎不起作用。 这是我正在使用的; 它只使用内build并处理更多(但不是全部)的病态文件名。

 #!/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 

这里有一些testing用例:

 $ 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 =“”
 /家/我/:
     dir =“/ home / me /”
     base =“”
     ext =“”
 /家/我/文件:
     dir =“/ home / me /”
     base =“文件”
     ext =“”
 /home/me/file.tar:
     dir =“/ home / me /”
     base =“文件”
     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”
 /家/我/ ..:
     dir =“/ home / me /”
     base =“..”
     ext =“”
 。:
     dir =“”
     base =“。”
     ext =“”

你可以使用basename

例:

 $ basename foo-bar.tar.gz .tar.gz foo-bar 

你需要提供基本名称的扩展名,但是如果你总是用-z执行tar ,那么你知道扩展名是.tar.gz

这应该做你想要的:

 tar -zxvf $1 cd $(basename $1 .tar.gz) 

您可以使用cut命令删除最后两个扩展名( ".tar.gz"部分):

 $ echo "foo.tar.gz" | cut -d'.' --complement -f2- foo 

正如克莱顿·休斯(Clayton Hughes)在评论中指出的那样,这个问题不适用于实际的例子。 所以作为替代scheme,我build议使用sed和扩展正则expression式,如下所示:

 $ echo "mpc-1.0.1.tar.gz" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//' mpc-1.0.1 

它的工作原理是无条件删除最后两个(字母数字)扩展名。

[在Anders Lindahl发表评论后再次更新]

梅伦在一篇博文中写道:

使用Bash,还有${file%.*}来获取没有扩展名的文件名和${file##*.}来获得扩展名。 那是,

 file="thisfile.txt" echo "filename: ${file%.*}" echo "extension: ${file##*.}" 

输出:

 filename: thisfile extension: txt 

这里有一些替代的build议(主要是在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 '^[/]\|^~/' 

所有用例都使用原始完整path作为input,而不依赖于中间结果。

 pax> echo abjs | sed 's/\.[^.]*$//' ab pax> echo abjs | sed 's/^.*\.//' js 

工作正常,所以你可以使用:

 pax> FILE=abjs pax> NAME=$(echo "$FILE" | sed 's/\.[^.]*$//') pax> EXTENSION=$(echo "$FILE" | sed 's/^.*\.//') pax> echo $NAME ab pax> echo $EXTENSION js 

顺便说一句,这些命令的工作如下。

NAME的命令会replace"." 字符后跟任意数量的非"." 字符直到行的末尾,没有任何内容(即,它从最后的"."到行的末尾,包括所有内容)。 这基本上是一个非贪婪的替代使用正则expression式欺骗。

EXTENSION命令replace任意数量的字符后跟一个"." 字符在行的开头,没有任何东西(例如,它删除了从行首到最后一个点的所有内容)。 这是一个贪婪的替代,这是默认的行为。

我认为如果你只是需要文件的名字,你可以试试这个:

 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。

[从单线程修改为通用bash 函数 ,行为现在与dirnamebasename实用程序一致; 理由补充。]

接受的答案在典型情况下performance良好 ,但边缘情况下失败 ,即:

  • 对于没有扩展名的文件名(在这个答案的其余部分称为后缀 ), extension=${filename##*.}返回input文件名,而不是空string。
  • extension=${filename##*.}不包括最初的. ,违背惯例。
    • 盲目前瞻. 不适用于没有后缀的文件名。
  • filename="${filename%.*}"将是空string,如果input文件名称以. 并不包含其他内容. 字符(例如.bash_profile ) – 与惯例相反。

———

因此, 涵盖所有边缘情况强大解决scheme的复杂性需要一个函数 – 请参阅下面的定义; 它可以返回path的所有组件

示例调用:

 splitPath '/etc/bash.bashrc' dir fname fnameroot suffix # -> $dir == '/etc' # -> $fname == 'bash.bashrc' # -> $fnameroot == 'bash' # -> $suffix == '.bashrc' 

请注意,inputpath之后的参数是自由select的位置variables名称
要跳过那些没有兴趣的variables,指定_ (使用抛出variables$_ )或者'' ; 例如,只提取文件名根目录和扩展名,使用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 

testing代码,执行function:

 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 

预期的输出 – 注意边缘情况:

  • 一个没有后缀的文件名
  • .开头的文件名.考虑后缀的开始)
  • / (尾随/被忽略)结尾的inputpath
  • inputpath是一个文件名(仅作为父path返回)
  • 有一个以上的文件名. -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 

您可以强制剪切以显示所有字段和后续字段添加到字段编号。

 NAME=`basename "$FILE"` EXTENSION=`echo "$NAME" | cut -d'.' -f2-` 

所以如果FILE是eth0.pcap.gz ,那么EXTENSION就是pcap.gz

使用相同的逻辑,您也可以使用' – '来获取文件名,如下所示:

 NAME=`basename "$FILE" | cut -d'.' -f-1` 

这甚至可以用于没有任何扩展名的文件名。

这个简单的任务不需要麻烦awksed甚至perl 。 有一个纯粹的Bash, os.path.splitext()兼容的解决scheme,只使用参数扩展。

参考实现

os.path.splitext(path)文档os.path.splitext(path)

将path名path拆分成一对(root, ext) ,使得root + ext == pathext是空的,或者以句点开始并且至多包含一个句点。 在基本名称的领导阶段被忽略; splitext('.cshrc') ('.cshrc', '') 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"}" 

testing

下面是忽略领导阶段实现的testing用例,它应该匹配每个input的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' | |---------------|-----------|-------| 

检测结果

所有testing通过。

好吧,如果我理解正确,这里的问题是如何获得具有多个扩展名的文件的名称和完整扩展名,例如stuff.tar.gz

这适用于我:

 fullfile="stuff.tar.gz" fileExt=${fullfile#*.} fileName=${fullfile%*.$fileExt} 

这会给你stuff作为扩展名的文件名和.tar.gz 。 它适用于任何数量的扩展,包括0.希望这有助于任何人有相同的问题=)

魔术文件识别

除了这个堆栈溢出问题的很多好的答案,我想补充:

在Linux和其他unixen下,有一个名为file魔术命令,它通过分析文件的第一个字节来做文件types检测。 这是一个非常古老的工具,用于打印服务器的初始化(如果没有创build…我不确定)。

 file myfile.txt myfile.txt: UTF-8 Unicode text file -b --mime-type myfile.txt text/plain 

标准扩展可以在/etc/mime.types中find(在我的Debian GNU / Linux桌面上,请参阅man fileman mime.types 。也许你必须安装file工具和mime-support软件包):

 grep $( file -b --mime-type myfile.txt ) </etc/mime.types text/plain asc txt text pot brf srt 

你可以创build一个确定右扩展的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##*[/.-]} } 

这个函数可以设置一个可以在以后使用的Bashvariables:

(这是来自@Petesh的正确答案):

 filename=$(basename "$fullfile") filename="${filename%.*}" file2ext "$fullfile" extension echo "$fullfile -> $filename . $extension" 

我使用下面的脚本

 $ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev foo 
 $ F = "text file.test.txt" $ echo ${F/*./} txt 

这将迎合文件名中的多个点和空格,但是如果没有扩展名,它将返回文件名。 虽然容易检查; 只是testing文件名和扩展名是相同的。

当然,这种方法不适用于.tar.gz文件。 但是,这可以通过两个步骤来处理。 如果扩展名为gz,则再次检查是否还有焦油扩展。

如何提取的文件名和扩展名:

 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 

这没有作为第一个例子的警告,但你必须处理每一个案件,所以这可能是更乏味的,取决于你可以期待多less扩展。

这里是AWK的代码。 它可以做得更简单。 但是我不擅长AWK。

 filename$ ls abc.a.txt abctxt pp-kk.txt filename$ find . -type f | awk -F/ '{print $2}' | rev | awk -F"." '{$1="";print}' | rev | awk 'gsub(" ",".") ,sub(".$", "")' abc.a abc pp-kk filename$ find . -type f | awk -F/ '{print $2}' | awk -F"." '{print $NF}' txt txt txt 

最小和最简单的解决scheme(单行)是:

$ file=/blaabla/bla/blah/foo.txt

echo $(basename ${file%.*})

foo

一个简单的答案:

要扩展POSIXvariables的答案 ,请注意,你可以做更多有趣的模式。 所以对于这里详细的情况,你可以简单地做到这一点:

 tar -zxvf $1 cd ${1%.tar.*} 

这将切断.tar的最后一次出现。 <something>

更一般地说,如果你想删除最后一次出现。 <something> 。 然后, <something-else>

 ${1.*.*} 

应该工作正常。

上面的答案链接似乎已经死了。 这里有一个很好的解释,你可以从TLDP直接在Bash中进行一些string操作 。

如果只需要文件名,那么path和扩展名都可以在一行中去掉,

 filename=$(basename ${fullname%.*}) 

在很大程度上,基于@ mklement0的优秀,充满了随机,有用的bashisms – 以及其他答案这个/其他问题/“互联网打networking”…我把它包起来一点点,更容易理解,我的(或你的) .bash_profile可重用的function ,照顾什么(我认为)应该是一个更强大的版本的dirname / basename / 你有什么 ..

 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 extesionless 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> 

From the answers above, the shortest oneliner to mimic Python's

 file, ext = os.path.splitext(path) 

presuming your file really does have an extension, is

 EXT="${PATH##*.}"; FILE=$(basename "$PATH" .$EXT) 

If you also want to allow empty extensions, this is the shortest I could come up with:

 sed -r 's/.+\.(.+)|.*/\1/' # EXTENSION sed -r 's/(.+)\..+|(.*)/\1\2/' # FILENAME 

1st line explained: It matches PATH.EXT or ANYTHING and replaces it with EXT. If ANYTHING was matched, the ext group is not captured.

In order to make dir more useful (in the case a local file with no path is specified as input) I did the following:

 # Substring from 0 thru pos of filename dir="${fullpath:0:${#fullpath} - ${#filename}}" if [[ -z "$dir" ]]; then dir="./" fi 

This allows you to do something useful like add a suffix to the input file basename as:

 outfile=${dir}${base}_suffix.${ext} testcase: foo.bar dir: "./" base: "foo" ext: "bar" outfile: "./foo_suffix.bar" testcase: /home/me/foo.bar dir: "/home/me/" base: "foo" ext: "bar" outfile: "/home/me/foo_suffix.bar" 

您可以使用

 sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2- 

to get file name and

 sed 's/^/./' | rev | cut -d. -f1 | rev 

to get extension.

testing用例:

 echo "filename.gz" | sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2- echo "filename.gz" | sed 's/^/./' | rev | cut -d. -f1 | rev echo "filename" | sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2- echo "filename" | sed 's/^/./' | rev | cut -d. -f1 | rev echo "filename.tar.gz" | sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2- echo "filename.tar.gz" | sed 's/^/./' | rev | cut -d. -f1 | rev 

Here is the algorithm I used for finding the name and extension of a file when I wrote a Bash script to make names unique when names conflicted with respect to casing.

 #! /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 

The test run.

 $ 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= $ 

FYI: The complete transliteration program and more test cases can be found here: https://www.dropbox.com/s/4c6m0f2e28a1vxf/avoid-clashes-code.zip?dl=0

Using example file /Users/Jonathan/Scripts/bash/MyScript.sh , this code:

 MY_EXT=".${0##*.}" ME=$(/usr/bin/basename "${0}" "${MY_EXT}") 

will result in ${ME} being MyScript and ${MY_EXT} being .sh :


脚本:

 #!/bin/bash set -e MY_EXT=".${0##*.}" ME=$(/usr/bin/basename "${0}" "${MY_EXT}") echo "${ME} - ${MY_EXT}" 

一些testing:

 $ ./MyScript.sh MyScript - .sh $ bash MyScript.sh MyScript - .sh $ /Users/Jonathan/Scripts/bash/MyScript.sh MyScript - .sh $ bash /Users/Jonathan/Scripts/bash/MyScript.sh MyScript - .sh