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

到目前为止,我找到的最佳解决方案是:

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解释器。

有更好的主意吗?


当前回答

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

## 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 "\n" "$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=“$sseparatorChar”),并将每个路径组件分配给各自的变量。

以下是sed构造的工作原理:

s/^[[:space:]]+/-和s/[[:space:]]+$//去掉任何前导和/或尾随空格字符t l1和:l1为下一个s函数刷新t函数s/^([^/]|$)//和t测试输入路径是否无效(不是以正斜杠开头的路径),在这种情况下,它将所有输出行留空并退出sed命令s/[/]+$//去掉任何尾随斜线t l2和:l2为下一个s函数刷新t函数s/^$/files\/\\[newline]filesystem/p和t测试输入路径由根目录/组成的特殊情况,在这种情况下,它为dirPathWithSlash和dirPath输出行打印文件系统/和文件系统,将所有其他输出行留空,并退出sed命令h将输入路径保存在等待空间中s/^(.*)([/])([^/]+)$/\1\\2\\[newline]\1\\[newline]\3/p打印dirPathWithSlash、dirPath和fileNameWithExt输出行g从保持空间检索输入路径t l3和:l3为下一个s函数刷新t函数s/^.*\[/]([^/]+)([.])([a-zA-Z0-9]+)$/\1\\[newline]\2\3\[newline]\3/p,并在存在文件扩展名的情况下打印fileName、fileExtWithDot和fileExt输出行(假设仅由字母数字字符组成),然后退出sed命令s/^.*\[/](.+)$/\1/p在文件扩展名不存在的情况下打印fileName,但不打印fileExtWithDot和fileExt输出行,然后退出sed命令。

其他回答

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

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

将导致${ME}为MyScript,${MY_EXT}为.sh:


脚本:

#!/bin/bash
set -e

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

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

一些测试:

$ ./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

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

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

这是唯一对我有用的:

path='folder/other_folder/file.js'

base=${path##*/}
echo ${base%.*}

>> file

这也可以用于字符串插值,但不幸的是,您必须事先设置基数。

如何在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

这并没有第一个例子中的警告,但是你必须处理每一个案例,所以它可能会更加乏味,这取决于你可以预期的扩展数量。

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

## 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 "\n" "$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=“$sseparatorChar”),并将每个路径组件分配给各自的变量。

以下是sed构造的工作原理:

s/^[[:space:]]+/-和s/[[:space:]]+$//去掉任何前导和/或尾随空格字符t l1和:l1为下一个s函数刷新t函数s/^([^/]|$)//和t测试输入路径是否无效(不是以正斜杠开头的路径),在这种情况下,它将所有输出行留空并退出sed命令s/[/]+$//去掉任何尾随斜线t l2和:l2为下一个s函数刷新t函数s/^$/files\/\\[newline]filesystem/p和t测试输入路径由根目录/组成的特殊情况,在这种情况下,它为dirPathWithSlash和dirPath输出行打印文件系统/和文件系统,将所有其他输出行留空,并退出sed命令h将输入路径保存在等待空间中s/^(.*)([/])([^/]+)$/\1\\2\\[newline]\1\\[newline]\3/p打印dirPathWithSlash、dirPath和fileNameWithExt输出行g从保持空间检索输入路径t l3和:l3为下一个s函数刷新t函数s/^.*\[/]([^/]+)([.])([a-zA-Z0-9]+)$/\1\\[newline]\2\3\[newline]\3/p,并在存在文件扩展名的情况下打印fileName、fileExtWithDot和fileExt输出行(假设仅由字母数字字符组成),然后退出sed命令s/^.*\[/](.+)$/\1/p在文件扩展名不存在的情况下打印fileName,但不打印fileExtWithDot和fileExt输出行,然后退出sed命令。