关于字符串:提取bash中的文件名和扩展名

Extract filename and extension in Bash

我想分别获取文件名(不带扩展名)和扩展名。

迄今为止,我发现的最佳解决方案是:

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

这是错误的,因为如果文件名包含多个.字符,它将不起作用。假设我有a.b.js,它会考虑ab.js,而不是a.bjs

它可以很容易地用python完成

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

但如果可能的话,我不想为此而启动Python解释器。

有更好的主意吗?


首先,获取不带路径的文件名:

1
2
3
filename=$(basename --"$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"

或者,您可以将焦点放在路径的最后一个"/",而不是".",即使您有不可预知的文件扩展名,它也可以工作:

1
filename="${fullfile##*/}"

您可能需要检查文档:

  • 在Web上的"3.5.3 Shell参数扩展"部分
  • 在bash手册页的"参数扩展"部分中


1
2
3
4
5
6
7
8
9
~% FILE="example.tar.gz"
~% echo"${FILE%%.*}"
example
~% echo"${FILE%.*}"
example.tar
~% echo"${FILE#*.}"
tar.gz
~% echo"${FILE##*.}"
gz

有关更多详细信息,请参见bash手册中的shell参数扩展。


通常您已经知道扩展名,因此您可能希望使用:

1
basename filename .extension

例如:

1
basename /path/to/dir/filename.txt .txt

我们得到

1
filename


你可以使用魔法posix变量:

1
2
3
4
5
bash-3.2$ FILENAME=somefile.tar.gz
bash-3.2$ echo ${FILENAME%%.*}
somefile
bash-3.2$ echo ${FILENAME%.*}
somefile.tar

有一个警告,如果你的文件在你的形式,然后删除从./somefile.tar.gzecho ${FILENAME%%.*}greedily最长匹配的.和你会有空的字符串。

(你可以在一个工作是临时变量:

1
2
3
FULL_FILENAME=$FILENAME
FILENAME=${FULL_FILENAME##*/}
echo ${FILENAME%%.*}

)

本网站介绍了更多。

1
2
3
4
5
6
7
8
${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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/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:
\tdir  = "
$dir"
\tbase = "
$base"
\text  = "
$ext""
done

下面是一些测试案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$ 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

例子:

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

您需要为basename提供要删除的扩展名,但是如果您总是使用-z执行tar,那么您知道扩展名是.tar.gz

这应该是您想要的:

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


1
2
3
4
pax> echo a.b.js | sed 's/\.[^.]*$//'
a.b
pax> echo a.b.js | sed 's/^.*\.//'
js

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

1
2
3
4
5
6
7
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的命令将一个"."字符替换为后面跟有任意数量的非"."字符,直到行的末尾,而不加任何内容(即,它删除了从最后的"."到行的末尾(包括行尾)的所有内容)。这基本上是一个使用regex欺骗的非贪婪替换。

EXTENSION的命令在行的开头替换任意数量的字符,后面跟一个"."字符,不带任何内容(即,它删除从行的开头到最后一个点的所有内容,包括在内)。这是一个贪婪的替换,这是默认操作。


梅伦在博客上发表评论:

使用bash,还可以使用${file%.*}获取不带扩展名的文件名,使用${file##*.}只获取扩展名。也就是说,

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

输出:

1
2
filename: thisfile
extension: txt


你可以使用cut命令解除负载扩展(".tar.gz"两部分):

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

他指出,由休斯(克莱顿评论,这将需要服务的例子中的实际问题。这是一个sed提供替代使用扩展正则表达式,这样:

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

它由除最后两unconditionally(α值)扩展。

[更新]后又如何从不同的林达尔


对于这个简单的任务,不需要为awksed甚至perl而烦恼。有一个纯粹的bash,os.path.splitext()兼容的解决方案,它只使用参数扩展。

参考实施

os.path.splitext(path)文件:

Split the pathname path into a pair (root, ext) such that root + ext == path, and ext is empty or begins with a period and contains at most one period. Leading periods on the basename are ignored; splitext('.cshrc') returns ('.cshrc', '').

Python代码:

1
root, ext = os.path.splitext(path)

bash实现表彰领导时期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
root="${path%.*}"
ext="${path#"$root
<div class="suo-content">[collapse title=""]<ul><li>不,<wyn>text.tar.gz</wyn>的基本文件名应该是<wyn>text</wyn>,扩展名应该是<wyn>.tar.gz</wyn></li><li>@正如我所说,这里的解决方案与Python中的<wyn>os.path.splitext</wyn>实现相匹配。对于可能有争议的输入,该实现是否健全是另一个主题。</li></ul>[/collapse]</div><hr><P>这里有一些可选的建议(主要在<wyn>awk</wyn>),包括一些高级用例,比如提取软件包的版本号。</P>[cc lang="bash"]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 '^[/]\|^~/'

所有用例都使用原始完整路径作为输入,而不依赖于中间结果。


Smallest and simplest solution (in single line) is:

1
2
$ file=/blaabla/bla/blah/foo.txt
echo $(basename ${file%.*}) # foo


在回答接受作品很典型的案例,但在失败的边缘的情况下,即:

  • 扩展(称为文件名后缀为不在本extension=${filename##*.}其余的答案),而当输入文件名返回空字符串。
  • 不包含extension=${filename##*.}首次.,违反公约。
    • blindly prepending .将不会工作,没有后缀的文件名。
  • filename="${filename%.*}"想是空字符串,如果输入文件的名称和包含没有进一步起飞和..人物(例如,20 .bash_profile)会议。

---------

因此,复杂性的鲁棒性解决方案A边缘覆盖所有电话的功能。其定义下面的湖;它可以返回所有组件的路径。

呼叫的例子:

1
2
3
4
5
splitPath '/etc/bash.bashrc' dir fname fnameroot suffix
# -> $dir == '/etc'
# -> $fname == 'bash.bashrc'
# -> $fnameroot == 'bash'
# -> $suffix == '.bashrc'

注意的是参数自由选择后输入路径,定位变量的名字。一个变量的兴趣是跳跃在那些需要来指定是,_(使用变量或$_掷远'');例如,一个文件名和扩展根提取物splitPath '/etc/bash.bashrc' _ _ fnameroot extension只使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 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.">&amp;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 = *.* ]] &amp;&amp; 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 ]] &amp;&amp; printf -v"$2""$_sp_dirname"
  [[ -n $3 ]] &amp;&amp; printf -v"$3""$_sp_basename"
  [[ -n $4 ]] &amp;&amp; printf -v"$4""$_sp_basename_root"
  [[ -n $5 ]] &amp;&amp; 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

测试代码的功能,包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

价格说明:边缘。

  • 一个没有后缀的文件名
  • 一个文件名(不被视.启动开始的后缀)
  • 在输入路径/(尾是以/忽略)
  • 这是一个在输入路径(文件名是为只读.返回父路径)
  • a filename是安切洛蒂。.字头令牌(只被认为是最后的后缀):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
----- /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

我认为,如果您只需要文件名,可以尝试以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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。


您可以强制剪切以显示所有字段以及向字段编号添加-的后续字段。

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

因此,如果文件是eth0.pcap.gz,扩展名将是pcap.gz

使用相同的逻辑,还可以使用"-"和CUT提取文件名,如下所示:

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

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


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

这对我很有用:

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

这将为您提供文件名为stuff,扩展名为.tar.gz。它适用于任何数量的扩展,包括0。希望这对任何有同样问题的人都有帮助=)


魔术文件识别

在添加到焊料良好的答案在本栈 ;溢出的问题,我想补充一点:

在Linux和其他unixen命名,有一个神奇的filefiletype命令,做一些分析,检测由第一字节的文件。这是一个非常老的工具,可用于初步打印服务器(如果需要创建…我需要知道吗)。

1
2
3
4
5
file myfile.txt
myfile.txt: UTF-8 Unicode text

file -b --mime-type myfile.txt
text/plain

标准的扩展,可以发现在我/etc/mime.types(Debian GNU / Linux的桌面上。man fileman mime.types湖。也许你有一个安装包file效用和mime-support):

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

你可以创建一个bash函数定权的延伸。有一个小的(不完美)的样品。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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" ] &amp;&amp;
                _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]" ] &amp;&amp;
                _basemimetype=${_line[1]} ||
                _basemimetype=${_basemimetype##*[/.-]}
            break
        fi
        done </etc/mime.types
    case ${_basemimetype##*[/.-]} in
        executable ) _basemimetype='' ;;
        shellscript ) _basemimetype='sh' ;;
        dosexec ) _basemimetype='exe' ;;
        * ) ;;
    esac
    ["$_mimetype" ] &amp;&amp; ["$_basemimetype" !="$_mimetype" ] &amp;&amp;
      printf ${2+-v} $2"%s.%s" ${_basemimetype##*[/.-]} ${_mimetype##*[/.-]} ||
      printf ${2+-v} $2"%s" ${_basemimetype##*[/.-]}
}

这个函数可以设置一个bash变量可以用后:

(这是受从正确的回答:"petesh)

1
2
3
4
5
filename=$(basename"$fullfile")
filename="${filename%.*}"
file2ext"$fullfile" extension

echo"$fullfile -> $filename . $extension"

我用下面的脚本

1
2
$ echo"foo.tar.gz"|rev|cut -d"." -f3-|rev
foo


1
2
3
$ F ="text file.test.txt"  
$ echo ${F/*./}  
txt

这可以处理文件名中的多个点和空格,但是如果没有扩展名,则返回文件名本身。但检查起来很容易;只需测试文件名和扩展名是否相同。

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


如何在fish中提取文件名和扩展名:

1
2
3
4
5
6
7
8
9
10
11
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

注意:在最后一个点上进行拆分,这对包含点的文件名很好,但对于包含点的扩展名不太好。见下面的例子。

用途:

1
2
3
$ 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.

有更好的方法可以做到这一点。请随意编辑我的答案以改进它。

如果您要处理的扩展集有限,并且您了解所有扩展集,请尝试以下操作:

1
2
3
4
5
6
7
8
9
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

这并不是第一个例子中的警告,但是您必须处理每一个案例,这样根据您期望的扩展数量,它可能会更加繁琐。


这是带锥子的代码。它可以做得更简单。但我的锥子不好。

1
2
3
4
5
6
7
8
9
10
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


只需使用${parameter%word}

在你的情况下:

1
${FILE%.*}

如果要测试它,请执行以下所有操作,只需删除扩展名:

1
2
3
FILE=abc.xyz; echo ${FILE%.*};
FILE=123.abc.xyz; echo ${FILE%.*};
FILE=abc; echo ${FILE%.*};


一个简单的答案:

要扩展POSIX变量的答案,请注意,您可以执行更有趣的模式。因此,对于这里详细介绍的案例,您可以简单地执行以下操作:

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

这将切断最后一次出现的.tar.

更一般地说,如果您想删除最后一次出现的。

1
${1.*.*}

应该工作得很好。

上述答案的链接似乎已失效。这里有一个很好的解释,您可以直接在bash中从tldp执行一系列字符串操作。


基于上的开关mklement0的优秀学院",与chock全随机,bashisms -有用的其他工作人员在其他问题的答案"那该死的/ /网络"……我在一个小化妆包,这一切,似乎更多的comprehensible,可重复使用的功能,我(或你的任务.bash_profile护理)什么(我认为)应该是更强大的版本的dirnamebasename/什么/你。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function path { SAVEIFS=$IFS; IFS=""   # stash IFS for safe-keeping, etc.
    [[ $# != 2 ]] &amp;&amp; echo"usage: path <path> <dir|name|fullname|ext>" &amp;&amp; return    # demand 2 arguments
    [[ $1 =~ ^(.*/)?(.+)?$ ]] &amp;&amp; {     # regex parse the path
        dir=${BASH_REMATCH[1]}
        file=${BASH_REMATCH[2]}
        ext=$([[ $file = *.* ]] &amp;&amp; printf %s ${file##*.} || printf '')
        # edge cases for extesionless files and files like".nesh_profile.coffee"
        [[ $file == $ext ]] &amp;&amp; fnr=$file &amp;&amp; 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
}

使用的例子。

1
2
3
4
5
6
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>


如果您还想允许空扩展,这是我能想到的最短的:

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

第一行解释说:它与path.ext或任何内容匹配,并将其替换为ext。如果匹配了任何内容,则不会捕获ext组。


根据petesh answer构建,如果只需要文件名,路径和扩展都可以在一条线上剥离,

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


从上面的答案来看,最短的一行程序可以模仿python的

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

假设您的文件确实有扩展名,是

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


imho已经给出了最好的解决方案(使用shell参数扩展),目前是最佳的解决方案。

但是,我添加了一个只使用哑铃命令的命令,这是不高效的,任何严肃的人都不应该使用它:

1
2
3
4
FILENAME=$(echo $FILE | cut -d . -f 1-$(printf $FILE | tr . '
'
| wc -l))
EXTENSION=$(echo $FILE | tr . '
'
| tail -1)

添加只是为了好玩:—)


也许有一个选项,在tar这样做;你检查你吗?否则,你可以使用bash字符串扩展:

1
2
3
test="mpc-1.0.1.tar.gz"
noExt="${test/.tar.gz/}" # Remove the string '.tar.gz'
echo $noExt


这是我在写bash脚本时用来查找文件名和扩展名的算法,当名称与大小写冲突时,可以使名称唯一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#! /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" == *.* ]] &amp;&amp; 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

测试运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ config/Name\&amp;Extension.bash
Script config/Name&amp;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/avole-conflicts-code.zip?DL=0


使用示例文件/Users/Jonathan/Scripts/bash/MyScript.sh,此代码:

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

将导致${ME}MyScript${MY_EXT}.sh

脚本:

1
2
3
4
5
6
7
#!/bin/bash
set -e

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

echo"${ME} - ${MY_EXT}"

一些测试:

1
2
3
4
5
6
7
8
9
10
11
$ ./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


下面是一个SED解决方案,它以各种形式提取路径组件,并可以处理大多数边缘情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
## Enter the input path and field separator character, for example:
## (separatorChar must not be present in inputPath)

inputPath="/path/to/Foo.bar"
separatorChar=":"

## sed extracts the path components and assigns them to output variables

oldIFS="$IFS"
IFS="$separatorChar"
read dirPathWithSlash dirPath fileNameWithExt fileName fileExtWithDot fileExt <<<"$(sed -En '
s/^[[:space:]]+//
s/[[:space:]]+$//
t l1
:l1
s/^([^/]|$)//
t
s/[/]+$//
t l2
:l2
s/^$/filesystem\/\
filesystem/p
t
h
s/^(.*)([/])([^/]+)$/\1\2\
\1\
\3/p
g
t l3
:l3
s/^.*[/]([^/]+)([.])([a-zA-Z0-9]+)$/\1\
\2\3\
\3/p
t
s/^.*[/](.+)$/\1/p
' <<<"
$inputPath" | tr"
""$separatorChar")"
IFS="$oldIFS"

## Results (all use separatorChar=":")

## inputPath        = /path/to/Foo.bar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to
## fileNameWithExt  = Foo.bar
## fileName         = Foo
## fileExtWithDot   = .bar
## fileExt          = bar

## inputPath        = /path/to/Foobar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to
## fileNameWithExt  = Foobar
## fileName         = Foobar
## fileExtWithDot   =
## fileExt          =

## inputPath        = /path/to/...bar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to
## fileNameWithExt  = ...bar
## fileName         = ..
## fileExtWithDot   = .bar
## fileExt          = bar

## inputPath        = /path/to/..bar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to
## fileNameWithExt  = ..bar
## fileName         = .
## fileExtWithDot   = .bar
## fileExt          = bar

## inputPath        = /path/to/.bar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to
## fileNameWithExt  = .bar
## fileName         = .bar
## fileExtWithDot   =
## fileExt          =

## inputPath        = /path/to/...
## dirPathWithSlash = /path/to/
## dirPath          = /path/to
## fileNameWithExt  = ...
## fileName         = ...
## fileExtWithDot   =
## fileExt          =

## inputPath        = /path/to/Foo.
## dirPathWithSlash = /path/to/
## dirPath          = /path/to
## fileNameWithExt  = Foo.
## fileName         = Foo.
## fileExtWithDot   =
## fileExt          =

## inputPath        = / (the root directory)
## dirPathWithSlash = filesystem/
## dirPath          = filesystem
## fileNameWithExt  =
## fileName         =
## fileExtWithDot   =
## fileExt          =

## inputPath        =  (invalid because empty)
## dirPathWithSlash =
## dirPath          =
## fileNameWithExt  =
## fileName         =
## fileExtWithDot   =
## fileExt          =

## inputPath        = Foo/bar (invalid because doesn't start with a forward slash)
## dirPathWithSlash =
## dirPath          =
## fileNameWithExt  =
## fileName         =
## fileExtWithDot   =
## fileExt          =

它的工作原理如下:

SED解析输入路径并按顺序在单独的行上打印以下路径组件:

  • 带有尾随斜杠字符的目录路径
  • 没有尾随斜杠字符的目录路径
  • 带扩展名的文件名
  • 不带扩展名的文件名
  • 带前导点字符的文件扩展名
  • 不带前导点字符的文件扩展名

tr将SED输出转换为以上路径组件的分隔符字符分隔字符串。

read使用分隔符作为字段分隔符(ifs="$separatorchar"),并将每个路径组件分配给各自的变量。

以下是SED结构的工作原理:

  • S/^[[:space:]+//和S/[:space:]+$//去掉任何前导和/或尾随空格字符
  • t l1和:l1为下一个s函数刷新t函数
  • S/^([^/]$)//和T测试无效的输入路径(不以正斜杠开头的路径),在这种情况下,它将所有输出行留空并退出SED命令
  • s/[/]+$//删除任何尾随斜杠
  • t l2和:l2为下一个s函数刷新t函数
  • s/^$/filesystem/[newline]filesystem/p和t测试输入路径由根目录/组成的特殊情况,在这种情况下,它用斜杠和dirpath输出行打印dirpath的filesystem/和filesystem,将所有其他输出行留空,并退出sed命令。
  • H将输入路径保存在保留空间中
  • S/^(.*)([/])([^/]+)$/12[换行符].1[换行符].3/P用斜杠、dirpath和文件名打印dirpathwithext输出行
  • G从保留空间检索输入路径
  • t l3和:l3为下一个s函数刷新t函数
  • S/^.*[/]([^/]+)([.])([A-ZA-Z0-9]+)$/1[newline]23[newline]3/P和T为存在文件扩展名的情况打印文件名、fileextwithdot和fileext输出行(假定仅由字母数字字符组成),然后退出SED命令
  • s/^.*[/](.+)$/1/p打印文件名,但不打印文件扩展名不存在的fileextwithdot和fileext输出行,然后退出sed命令

为了让你更有用(案例A本地文件路径指定为输入一个没有)我做下面的:

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

这允许你做一些有用的类添加一个输入文件的基本文件名后缀为:

1
2
3
4
5
6
7
8
9
10
11
12
13
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"

你可以使用

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

获取文件名和

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

以获得扩展。

测试用例:

1
2
3
4
5
6
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

还可以使用for循环和tr从路径中提取文件名…

1
for x in `echo $path | tr"/"""`; do filename=$x; done

tr将路径中的所有/分隔符替换为空格,从而生成字符串列表,for循环将扫描这些字符串,并将最后一个字符串保留在文件名变量中。


一个简单的bash一行程序。我用这个从pwd中的所有文件中删除rst扩展名

1
2
3
4
5
for each in `ls -1 *.rst`
do
     a=$(echo $each | wc -c)
     echo $each | cut -c -$(( $a-5 )) >> blognames
done

它做什么?

1)ls -1 *.rst将在新行中列出stdout上的所有文件(try)。

2)echo $each | wc -c统计每个文件名中的字符数。

3)echo $each | cut -c -$(( $a-5 ))选择最多4个字符,即.rst字符。