ChatGPT解决这个技术问题 Extra ChatGPT

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

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

到目前为止我发现的最佳解决方案是:

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

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

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

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

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

有更好的想法吗?

This question 解释了这种 bash 技术和其他一些相关技术。
在应用下面的好答案时,不要像我在此处显示的那样简单地粘贴变量 Wrong: extension="{$filename##*.}",就像我刚才所做的那样!将 $ 移到花括号外:右: extension="${filename##*.}"
这显然是一个不平凡的问题,对我来说,很难判断下面的答案是否完全正确。令人惊讶的是,这不是 (ba)sh 中的内置操作(答案似乎是使用模式匹配来实现该功能)。我决定使用 Python 的 os.path.splitext 代替...
由于 extension 必须代表文件的 nature,因此有一个 magic 命令可以检查文件以判断其性质并提供 标准扩展名。见my answer
这个问题首先是有问题的,因为..从操作系统和unix文件系统的角度来看,一般来说,没有文件扩展名这样的东西。用一个 ”。”分开部分是人类的惯例,只有在人类同意遵守它的情况下才有效。例如,对于“tar”程序,可能已经决定用“tar”来命名输出文件。前缀而不是“.tar”后缀——给出“tar.somedir”而不是“somedir.tar”。因此,没有“通用、始终有效”的解决方案——您必须编写符合您的特定需求和预期文件名的代码。

L
Ludovic Kuty

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

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

或者,您可以关注路径的最后一个“/”而不是“。”即使您有不可预测的文件扩展名,它也应该工作:

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

您可能需要查看文档:

在网络上的“3.5.3 Shell 参数扩展”部分

在名为“参数扩展”部分的 bash 手册页中


查看 gnu.org/software/bash/manual/html_node/… 了解完整的功能集。
在 "$fullfile" 中添加一些引号,否则您将面临破坏文件名的风险。
哎呀,您甚至可以编写 filename="${fullfile##*/}" 并避免调用额外的 basename
如果文件没有扩展名,则此“解决方案”不起作用-相反,会输出整个文件名,考虑到无扩展名的文件无处不在,这非常糟糕。
修复处理不带扩展名的文件名:extension=$([[ "$filename" = *.* ]] && echo ".${filename##*.}" || echo '')。请注意,如果存在扩展,则将返回包括初始.,例如.txt
y
yelliver
~% FILE="example.tar.gz"

~% echo "${FILE%%.*}"
example

~% echo "${FILE%.*}"
example.tar

~% echo "${FILE#*.}"
tar.gz

~% echo "${FILE##*.}"
gz

有关详细信息,请参阅 Bash 手册中的 shell parameter expansion


您(也许是无意的)提出了一个很好的问题,即如果文件名的“扩展名”部分中有 2 个点,例如 .tar.gz,该怎么办......我从来没有考虑过这个问题,我怀疑它是如果不预先知道所有可能的有效文件扩展名,则无法解决。
为什么不能解决?在我的示例中,应该认为该文件包含两个扩展名,而不是带有两个点的扩展名。您分别处理这两个扩展。
在词汇基础上无法解决,您需要检查文件类型。考虑一下您是否有一个名为 dinosaurs.in.tar 的游戏并将其压缩到 dinosaurs.in.tar.gz :)
如果您在完整路径中传递,这会变得更加复杂。我的一个有一个'。在路径中间的目录中,但文件名中没有。示例“a/bc/d/e/filename”将结束“.c/d/e/filename”
显然没有 x.tar.gz 的扩展名是 gz,文件名是 x.tar 就是这样。没有双重扩展之类的东西。我很确定 boost::filesystem 就是这样处理的。 (分割路径,change_extension ...)如果我没记错的话,它的行为是基于python的。
T
Tomi Po

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

basename filename .extension

例如:

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

我们得到

filename

basename 的第二个论点令人大开眼界,亲切的先生/女士 :)
以及如何使用这种技术提取扩展名? ;) 等一下!我们实际上并不事先知道。
假设您有一个以 .zip.ZIP 结尾的压缩目录。有没有办法可以做类似 basename $file {.zip,.ZIP} 的事情?
虽然这只回答了部分 OPs 问题,但它确实回答了我输入谷歌的问题。 :-) 非常漂亮!
简单且符合 POSIX
t
tripleee

您可以使用 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%%.*}

)

site解释更多。

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

比 Joachim 的答案简单得多,但我总是要查找 POSIX 变量替换。此外,这在 Max OSX 上运行,其中 cut 没有 --complement 并且 sed 没有 -r
D
Doctor J

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

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

我经常看到 dir="${fullpath%$filename}" 而不是 dir="${fullpath:0:${#fullpath} - ${#filename}}"。写起来更简单。不确定是否有任何真正的速度差异或陷阱。
这使用 #!/bin/bash 这几乎总是错误的。如果可能,首选 #!/bin/sh 或 #!/usr/bin/env bash 如果不是。
@Good Person:我不知道这几乎总是错误的:which bash -> /bin/bash ;也许这是你的发行版?
@vol7ron - 在许多发行版中,bash 位于 /usr/local/bin/bash 中。在 OSX 上,许多人在 /opt/local/bin/bash 中安装更新的 bash。因此 /bin/bash 是错误的,应该使用 env 来找到它。更好的是使用 /bin/sh 和 POSIX 结构。除了在 solaris 上,这是一个 POSIX shell。
@GoodPerson,但如果您更喜欢 bash,为什么要使用 sh?这不是说,既然可以使用 sh,为什么还要使用 Perl?
p
paxdiablo
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 的命令在行首替换任意数量的字符,后跟一个 "." 字符,没有任何内容(即,它删除从行首到最后一个点的所有内容,包括在内)。这是一个贪婪的替换,它是默认操作。


对于没有扩展名的文件,此中断会打印相同的名称和扩展名。所以我使用 sed 's,\.[^\.]*$,,' 作为名称,使用 sed 's,.*\.,., ;t ;g' 作为扩展名(使用非典型的 testget 命令,以及典型的 substitute 命令)。
您可以在计算 NAME 之后测试它和 FILE 是否相等,如果相等,请将 EXTENSION 设置为空字符串。
从根本上说,将外部进程用于 shell 可以自己做的事情是一种反模式。
Tripleee:shell 可以在一百行内完成很多事情,而像 awk 这样的外部进程可以在五行内完成 :-)
B
Bjarke Freund-Hansen

您可以使用 basename

例子:

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

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

这应该做你想要的:

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

我想 cd $(basename $1 .tar.gz) 适用于 .gz 文件。但在问题中他提到了Archive files have several extensions: tar.gz, tat.xz, tar.bz2
Tomi Po 在 2 年前发布了同样的内容。
嗨 Blauhirn,哇,这是一个老问题。我认为日期发生了一些事情。我清楚地记得在被问到这个问题后不久就回答了这个问题,而且那里只有几个其他答案。难道是这个问题与另一个问题合并了,这样做吗?
是的,我没记错。我最初在被问到的同一天回答了这个问题 stackoverflow.com/questions/14703318/…,2 年后它被合并到这个问题中。当我的答案以这种方式移动时,我几乎不能因为重复的答案而受到指责。
P
Peter Mortensen

梅伦在一篇博文的评论中写道:

使用 Bash,还有 ${file%.*} 可以获取不带扩展名的文件名,而 ${file##*.} 可以单独获取扩展名。那是,

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

输出:

filename: thisfile
extension: txt

C
Cyker

对于这个简单的任务,无需费心使用 awksed 甚至 perl。有一个纯 Bash、os.path.splitext() 兼容的解决方案,它只使用参数扩展。

参考实现

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

将路径名路径拆分为一对 (root, ext),使得 root + ext == path,并且 ext 为空或以句点开头并且最多包含一个句点。基本名称上的前导句点被忽略; 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"}"

测试

以下是忽略前导句点实现的测试用例,它应该与每个输入上的 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' |
|---------------|-----------|-------|

试验结果

所有测试都通过了。


不,text.tar.gz 的基本文件名应该是 text,扩展名是 .tar.gz
@frederick99 正如我所说,这里的解决方案与 Python 中 os.path.splitext 的实现相匹配。对于可能有争议的输入,该实现是否合理是另一个话题。
模式 ("$root") 中的引号是如何工作的?如果它们被省略会发生什么? (我找不到有关此事的任何文档。)此外,这如何处理其中包含 *? 的文件名?
好的,测试表明引号使模式成为文字,即 *? 并不特殊。所以我的问题的两个部分互相回答。我是否正确,这没有记录?还是应该从引号通常禁用全局扩展这一事实来理解?
绝妙的答案!我将建议一个稍微简单的变体来计算根:root="${path#?}";root="${path::1}${root%.*}" - 然后继续进行相同的操作以提取扩展名。
S
Some programmer dude

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

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

正如克莱顿休斯在评论中指出的那样,这不适用于问题中的实际示例。因此,作为替代方案,我建议使用带有扩展正则表达式的 sed,如下所示:

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

它通过无条件地删除最后两个(字母数字)扩展来工作。

[在安德斯·林达尔发表评论后再次更新]


这仅适用于文件名/路径不包含任何其他点的情况: echo "mpc-1.0.1.tar.gz" |切-d'。 --complement -f2- 产生“mpc-1”(仅由 . 分隔后的前 2 个字段。)
@ClaytonHughes你是对的,我应该更好地测试它。添加了另一个解决方案。
sed 表达式应使用 $ 来检查匹配的扩展名是否位于文件名的末尾。否则,像 i.like.tar.gz.files.tar.bz2 这样的文件名可能会产生意想不到的结果。
@AndersLindahl 如果扩展的顺序与 sed 链顺序相反,它仍然会。即使以 $ 结尾,像 mpc-1.0.1.tar.bz2.tar.gz 这样的文件名也会删除 .tar.gz.tar.bz2
$ echo "foo.tar.gz" |切-d'。 -f2- WITHOUT --complement 将第二个拆分项放到字符串的末尾 $ echo "foo.tar.gz" |切-d'。 -f2- tar.gz
m
mklement0

accepted answer典型 情况 中运行良好,但在边缘 情况下失败,即:

对于没有扩展名的文件名(在此答案的其余部分中称为后缀), extension=${filename##*.} 返回输入文件名而不是空字符串。

extension=${filename##*.} 不包含开头的 .,这与约定相反。盲目的前置。不适用于没有后缀的文件名。

盲目的前置。不适用于没有后缀的文件名。

如果输入文件名以 .并且不包含进一步的内容。字符(例如,.bash_profile) - 违反约定。

---------

因此,涵盖所有边缘情况的强大解决方案的复杂性需要一个函数 - 请参见下面的定义;它可以返回路径的所有组件。

示例调用:

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

请注意,输入路径之后的参数是自由选择的,位置变量 names
要跳过那些之前不感兴趣的变量,请指定 _(使用一次性变量$_) 或 '';例如,要仅提取文件名根和扩展名,请使用 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

预期输出 - 注意边缘情况:

没有后缀的文件名

以 . 开头的文件名(不考虑后缀的开始)

以 / 结尾的输入路径(尾随 / 被忽略)

仅作为文件名的输入路径(. 作为父路径返回)

具有多个 .-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

h
henfiber

以下是一些替代建议(主要在 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 '^[/]\|^~/'
 

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


L
Let Me Tink About It

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

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

那是一个useless use of echo。一般来说,echo $(command) 最好写成 command,除非您特别要求 shell 在显示结果之前对 command 的输出执行空白标记化和通配符扩展。测验:echo $(echo '*') 的输出是什么(如果这是您真正想要的,那么您真的只想要 echo *)。
@triplee 我根本没有使用 echo 命令。我只是用它来演示作为第二行结果出现在第三行的结果 foo
但只有 basename "${file%.*}" 会做同样的事情;您正在使用命令替换来捕获其输出,只是立即 echo 相同的输出。 (不引用,结果名义上是不同的;但这几乎不相关,更不用说特征了。)
basename "$file" .txt 还避免了参数替换的复杂性。
@Ron 在指责他浪费我们的时间之前阅读他的第一条评论。
P
Peter Mortensen

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

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。


只是想要基础目录:) 谢谢!
S
Sarfraaz Ahmed

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

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

因此,如果 FILE 为 eth0.pcap.gz,则 EXTENSION 将为 pcap.gz

使用相同的逻辑,您还可以使用带有 cut 的 '-' 获取文件名,如下所示:

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

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


P
Peter Mortensen

魔术文件识别

除了关于这个 Stack Overflow 问题的很多好的答案,我想补充一下:

在 Linux 和其他 unixen 下,有一个名为 filemagic 命令,它通过分析文件的一些首字节来进行文件类型检测。这是一个非常古老的工具,最初用于打印服务器(如果不是为......我不确定)。

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 软件包):

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

您可以创建一个 函数来确定正确的扩展名。有一个小(不完美)示例:

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##*[/.-]}
}

这个函数可以设置一个以后可以使用的 Bash 变量:

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

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

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

尽管不是对原始帖子的直接回答,但这是迄今为止最明智的回应。感谢您提供它。
我非常感谢这个突出常见内置插件的详尽答案。虽然我最终只是在 python 中使用 -c 标志执行此操作,但如果我被限制为仅使用 shell 脚本,我将使用此处概述的概念。谢谢!
@JasonRStevensCFA 在 python 下,您将使用 python-magic 库!
@F.Hauri 酷库,感谢分享。我只是使用标准的东西,因为用于脚本的字符串内置函数非常简单。例如,$(python -c "'$1'.split('/')[-1]") 将使用子 shell 从路径字符串变量 $1 中获取带有扩展名的文件名(我在一些本地脚本中使用它)。我不会在 prod 中使用这种“魔法”,但是 Python 语言的这些特性对于简单的基于任务的事情来说非常棒。
@JasonRStevensCFA 像任何其他语言(perl、awk 等)一样,对 python 使用 forks 来满足如此小的需求会适得其反!尝试运行相同的 fork 1000 次并与参数扩展进行比较...
m
miriam
$ F = "text file.test.txt"  
$ echo ${F/*./}  
txt  

这适合文件名中的多个点和空格,但是如果没有扩展名,它会返回文件名本身。不过很容易检查;只需测试文件名和扩展名是否相同。

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


非常干净和直接的答案,非常感谢。
没有路径的文件名的好解决方案。不带扩展名的点文件的中断,在 for file in *.*; do ... ; done +1 等常见情况下不应该出现这种情况
C
Community

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

这对我有用:

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

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


正确的结果(根据 os.path.splitext,这是 OP 想要的)是 ('stuff.tar', '.gz')
S
SilverWolf

只需使用 ${parameter%word}

在你的情况下:

${FILE%.*}

如果你想测试它,下面的所有工作,只需删除扩展:

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

为什么投反对票?它仍然很有用,尽管 = 标志周围不应有空格。
这工作正常。谢谢! (现在等号周围没有空格,如果这就是它被否决的原因)
不适用于点文件并需要引号。
K
Ken Mueller

这是唯一对我有用的:

path='folder/other_folder/file.js'

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

>> file

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


J
Joydip Datta

我使用以下脚本

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

这根本没有效率。分叉太多次,这是完全没有必要的,因为这个操作可以在纯 Bash 中执行,而不需要任何外部命令和分叉。
D
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

这没有作为第一个示例的警告,但是您确实必须处理每种情况,因此根据您可以预期的扩展数量,它可能会更加乏味。


P
Peter Mortensen

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

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

您不应该需要最后一个示例中的第一个 awk 语句,对吧?
您可以通过执行另一个 split() 来避免将 Awk 传递给 Awk。 awk -F / '{ n=split($2, a, "."); print a[n] }' uses /` 作为顶级分隔符,然后拆分 . 上的第二个字段并打印新数组中的最后一个元素。
F
Fravadona

以前的答案没有使用 bash 正则表达式 这是一个纯 bash 解决方案,它将路径拆分为:

目录路径,当存在时,带有尾随 / 丢弃尾随 / 的正则表达式要长得多,以至于我没有发布它

文件名,不包括(最后一个)点扩展名

(最后一个)点扩展,其前导 .

该代码旨在处理所有可能的情况,欢迎您尝试。

#!/bin/bash

for path; do

####### the relevant part ######

[[ $path =~ ^(\.{1,2}|.*/\.{0,2})$|^(.*/)([^/]+)(\.[^/]*)$|^(.*/)(.+)$|^(.+)(\..*)$|^(.+)$ ]]

dirpath=${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[5]}
filename=${BASH_REMATCH[3]}${BASH_REMATCH[6]}${BASH_REMATCH[7]}${BASH_REMATCH[9]}
filext=${BASH_REMATCH[4]}${BASH_REMATCH[8]}

# dirpath should be non-null
[[ $dirpath ]] || dirpath='.'

################################

printf '%s=%q\n' \
    path     "$path" \
    dirpath  "$dirpath" \
    filename "$filename" \
    filext   "$filext"

done

它是如何工作的?

基本上,它确保只有一个子表达式(在正则表达式中用 | 分隔)能够捕获输入。多亏了这一点,您可以连接存储在 BASH_REMATCH 中的所有相同类型的捕获组(例如,与目录路径相关的捕获组),因为最多一个将是非空的。

以下是一组扩展但并不详尽的示例的结果:

+--------------------------------------------------------+
| input             dirpath        filename       filext |
+--------------------------------------------------------+
''                  .              ''             ''
.                   .              ''             ''
..                  ..             ''             ''
...                 .              ..             .
.file               .              .file          ''
.file.              .              .file          .
.file..             .              .file.         .
.file.Z             .              .file          .Z
.file.sh.Z          .              .file.sh       .Z
file                .              file           ''
file.               .              file           .
file..              .              file.          .
file.Z              .              file           .Z
file.sh.Z           .              file.sh        .Z
dir/                dir/           ''             ''
dir/.               dir/.          ''             ''
dir/...             dir/           ..             .
dir/.file           dir/           .file          ''
dir/.file.          dir/           .file          .
dir/.file..         dir/           .file.         .
dir/.file.Z         dir/           .file          .Z
dir/.file.x.Z       dir/           .file.x        .Z
dir/file            dir/           file           ''
dir/file.           dir/           file           .
dir/file..          dir/           file.          .
dir/file.Z          dir/           file           .Z
dir/file.x.Z        dir/           file.x         .Z
dir./.              dir./.         ''             ''
dir./...            dir./          ..             .
dir./.file          dir./          .file          ''
dir./.file.         dir./          .file          .
dir./.file..        dir./          .file.         .
dir./.file.Z        dir./          .file          .Z
dir./.file.sh.Z     dir./          .file.sh       .Z
dir./file           dir./          file           ''
dir./file.          dir./          file           .
dir./file..         dir./          file.          .
dir./file.Z         dir./          file           .Z
dir./file.x.Z       dir./          file.x         .Z
dir//               dir//          ''             ''
dir//.              dir//.         ''             ''
dir//...            dir//          ..             .
dir//.file          dir//          .file          ''
dir//.file.         dir//          .file          .
dir//.file..        dir//          .file.         .
dir//.file.Z        dir//          .file          .Z
dir//.file.x.Z      dir//          .file.x        .Z
dir//file           dir//          file           ''
dir//file.          dir//          file           .
dir//file..         dir//          file.          .
dir//file.Z         dir//          file           .Z
dir//file.x.Z       dir//          file.x         .Z
dir.//.             dir.//.        ''             ''
dir.//...           dir.//         ..             .
dir.//.file         dir.//         .file          ''
dir.//.file.        dir.//         .file          .
dir.//.file..       dir.//         .file.         .
dir.//.file.Z       dir.//         .file          .Z
dir.//.file.x.Z     dir.//         .file.x        .Z
dir.//file          dir.//         file           ''
dir.//file.         dir.//         file           .
dir.//file..        dir.//         file.          .
dir.//file.Z        dir.//         file           .Z
dir.//file.x.Z      dir.//         file.x         .Z
/                   /              ''             ''
/.                  /.             ''             ''
/..                 /..            ''             ''
/...                /              ..             .
/.file              /              .file          ''
/.file.             /              .file          .
/.file..            /              .file.         .
/.file.Z            /              .file          .Z
/.file.sh.Z         /              .file.sh       .Z
/file               /              file           ''
/file.              /              file           .
/file..             /              file.          .
/file.Z             /              file           .Z
/file.sh.Z          /              file.sh        .Z
/dir/               /dir/          ''             ''
/dir/.              /dir/.         ''             ''
/dir/...            /dir/          ..             .
/dir/.file          /dir/          .file          ''
/dir/.file.         /dir/          .file          .
/dir/.file..        /dir/          .file.         .
/dir/.file.Z        /dir/          .file          .Z
/dir/.file.x.Z      /dir/          .file.x        .Z
/dir/file           /dir/          file           ''
/dir/file.          /dir/          file           .
/dir/file..         /dir/          file.          .
/dir/file.Z         /dir/          file           .Z
/dir/file.x.Z       /dir/          file.x         .Z
/dir./.             /dir./.        ''             ''
/dir./...           /dir./         ..             .
/dir./.file         /dir./         .file          ''
/dir./.file.        /dir./         .file          .
/dir./.file..       /dir./         .file.         .
/dir./.file.Z       /dir./         .file          .Z
/dir./.file.sh.Z    /dir./         .file.sh       .Z
/dir./file          /dir./         file           ''
/dir./file.         /dir./         file           .
/dir./file..        /dir./         file.          .
/dir./file.Z        /dir./         file           .Z
/dir./file.x.Z      /dir./         file.x         .Z
/dir//              /dir//         ''             ''
/dir//.             /dir//.        ''             ''
/dir//...           /dir//         ..             .
/dir//.file         /dir//         .file          ''
/dir//.file.        /dir//         .file          .
/dir//.file..       /dir//         .file.         .
/dir//.file.Z       /dir//         .file          .Z
/dir//.file.x.Z     /dir//         .file.x        .Z
/dir//file          /dir//         file           ''
/dir//file.         /dir//         file           .
/dir//file..        /dir//         file.          .
/dir//file.Z        /dir//         file           .Z
/dir//file.x.Z      /dir//         file.x         .Z
/dir.//.            /dir.//.       ''             ''
/dir.//...          /dir.//        ..             .
/dir.//.file        /dir.//        .file          ''
/dir.//.file.       /dir.//        .file          .
/dir.//.file..      /dir.//        .file.         .
/dir.//.file.Z      /dir.//        .file          .Z
/dir.//.file.x.Z    /dir.//        .file.x        .Z
/dir.//file         /dir.//        file           ''
/dir.//file.        /dir.//        file           .
/dir.//file..       /dir.//        file.          .
/dir.//file.Z       /dir.//        file           .Z
/dir.//file.x.Z     /dir.//        file.x         .Z
//                  //             ''             ''
//.                 //.            ''             ''
//..                //..           ''             ''
//...               //             ..             .
//.file             //             .file          ''
//.file.            //             .file          .
//.file..           //             .file.         .
//.file.Z           //             .file          .Z
//.file.sh.Z        //             .file.sh       .Z
//file              //             file           ''
//file.             //             file           .
//file..            //             file.          .
//file.Z            //             file           .Z
//file.sh.Z         //             file.sh        .Z
//dir/              //dir/         ''             ''
//dir/.             //dir/.        ''             ''
//dir/...           //dir/         ..             .
//dir/.file         //dir/         .file          ''
//dir/.file.        //dir/         .file          .
//dir/.file..       //dir/         .file.         .
//dir/.file.Z       //dir/         .file          .Z
//dir/.file.x.Z     //dir/         .file.x        .Z
//dir/file          //dir/         file           ''
//dir/file.         //dir/         file           .
//dir/file..        //dir/         file.          .
//dir/file.Z        //dir/         file           .Z
//dir/file.x.Z      //dir/         file.x         .Z
//dir./.            //dir./.       ''             ''
//dir./...          //dir./        ..             .
//dir./.file        //dir./        .file          ''
//dir./.file.       //dir./        .file          .
//dir./.file..      //dir./        .file.         .
//dir./.file.Z      //dir./        .file          .Z
//dir./.file.sh.Z   //dir./        .file.sh       .Z
//dir./file         //dir./        file           ''
//dir./file.        //dir./        file           .
//dir./file..       //dir./        file.          .
//dir./file.Z       //dir./        file           .Z
//dir./file.x.Z     //dir./        file.x         .Z
//dir//             //dir//        ''             ''
//dir//.            //dir//.       ''             ''
//dir//...          //dir//        ..             .
//dir//.file        //dir//        .file          ''
//dir//.file.       //dir//        .file          .
//dir//.file..      //dir//        .file.         .
//dir//.file.Z      //dir//        .file          .Z
//dir//.file.x.Z    //dir//        .file.x        .Z
//dir//file         //dir//        file           ''
//dir//file.        //dir//        file           .
//dir//file..       //dir//        file.          .
//dir//file.Z       //dir//        file           .Z
//dir//file.x.Z     //dir//        file.x         .Z
//dir.//.           //dir.//.      ''             ''
//dir.//...         //dir.//       ..             .
//dir.//.file       //dir.//       .file          ''
//dir.//.file.      //dir.//       .file          .
//dir.//.file..     //dir.//       .file.         .
//dir.//.file.Z     //dir.//       .file          .Z
//dir.//.file.x.Z   //dir.//       .file.x        .Z
//dir.//file        //dir.//       file           ''
//dir.//file.       //dir.//       file           .
//dir.//file..      //dir.//       file.          .
//dir.//file.Z      //dir.//       file           .Z
//dir.//file.x.Z    //dir.//       file.x         .Z

如您所见,该行为不同于 basenamedirname。例如 basename dir/ 输出 dir 而正则表达式将为您提供一个空文件名。 ... 相同,它们被视为目录,而不是文件名。

我用 256 个字符的 10000 条路径对其进行计时,大约需要 1 秒,而等效的 POSIX shell 解决方案慢 2 倍,而基于野分叉的解决方案(for 循环内的外部调用)至少慢 60 倍。

备注: 没有必要测试包含 \n 或其他臭名昭著字符的路径,因为 bash 的正则表达式引擎以相同的方式处理所有字符。唯一能够打破当前逻辑的字符是 /.,它们以当前意想不到的方式混合或相乘。当我第一次发布我的答案时,我发现了一些我必须修复的边界案例;我不能说正则表达式是 100% 防弹的,但它现在应该非常健壮。

顺便说一句,这是产生相同输出的 POSIX shell 解决方案:

#!/bin/sh

for path; do

####### the relevant part ######

fullname=${path##*/}

case $fullname in
. | ..)
    dirpath="$path"
    filename=''
    filext=''
    ;;
*)
    dirpath=${path%"$fullname"}
    dirpath=${dirpath:-.}       # dirpath should be non-null
    filename=${fullname#.}
    filename="${fullname%"$filename"}${filename%.*}"
    filext=${fullname#"$filename"}
    ;;
esac

################################

printf '%s=%s\n' \
    path     "$path" \
    dirpath  "$dirpath" \
    filename "$filename" \
    filext   "$filext"

done

后记:有几点可能有人不同意上述代码给出的结果:

点文件的特殊情况:原因是点文件是一个 UNIX 概念。

的特殊情况。和..:恕我直言,将它们视为目录似乎很明显,但大多数库不这样做并强制用户对结果进行后处理。

不支持双扩展:这是因为您需要一个完整的数据库来存储所有有效的双扩展,最重要的是,因为文件扩展在 UNIX 中没有任何意义;例如,您可以调用 tar 存档 my_tarred_files 这完全没问题,您可以毫无问题地 tar xf my_tarred_files。


C
Community

Petesh 答案构建,如果只需要文件名,则路径和扩展名都可以在一行中剥离,

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

对我不起作用:“basename:缺少操作数尝试'basename --help'以获取更多信息。”
奇怪,你确定你在使用 Bash 吗?就我而言,无论是 3.2.25(旧 CentOS)版本还是 4.3.30(Debian Jessie)版本,它都能完美运行。
也许文件名中有空格?尝试使用 filename="$(basename "${fullname%.*}")"
basename 的第二个参数是可选的,但指定要剥离的扩展名。替换可能仍然有用,但 basename 实际上可能没有用,因为您实际上可以使用 shell 内置函数执行所有这些替换。
a
agc

很大程度上基于@mklement0 的优秀,充满随机,有用的bashisms - 以及这个/其他问题/“那个该死的互联网”的其他答案......我把它全部包起来为我的(或您的).bash_profile 提供一点、更易于理解、可重用的函数,它负责处理(我认为)应该是更强大的 dirname/basename / < em>你有什么..

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

做得很好;一些建议: - 您似乎根本不依赖 $IFS(如果是,您可以使用 local 来本地化设置它的效果)。 - 最好使用 local 变量。 - 您的错误消息应该输出到 stderr,而不是 stdout(使用 1>&2),并且您应该返回一个非零退出代码。 - 最好将 fullname 重命名为 basename(前者建议使用 dir 组件的路径)。 - name 无条件地附加一个 .(句点),即使原来没有。您可以简单地使用 basename 实用程序,但请注意它会忽略终止 /
P
Peter Mortensen

一个简单的答案:

要扩展 POSIX variables answer,请注意您可以做更多有趣的模式。因此,对于此处详述的案例,您可以简单地执行以下操作:

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

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

更一般地说,如果你想删除最后一次出现的 .. 那么

${1.*.*}

应该可以正常工作。

上述答案的链接似乎已失效。 Here's a great explanation of a bunch of the string manipulation you can do directly in Bash, from TLDP


有没有办法使匹配不区分大小写?
p
phil294

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

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

第一行解释:它匹配 PATH.EXT 或 ANYTHING 并将其替换为 EXT。如果 ANYTHING 匹配,则不会捕获 ext 组。


B
Bruno BEAUFILS

恕我直言,已经给出了最佳解决方案(使用 shell 参数扩展),并且是目前评价最高的解决方案。

然而,我添加了这个只使用哑命令的命令,它效率不高,而且任何人都不应该使用它:

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

添加只是为了好玩:-)


h
historystamp

这是我在编写 Bash 脚本以在名称与大小写冲突时使名称唯一时用于查找文件的名称和扩展名的算法。

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

试运行。

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

仅供参考:可在此处找到完整的音译程序和更多测试用例:https://www.dropbox.com/s/4c6m0f2e28a1vxf/avoid-clashes-code.zip?dl=0


在所有解决方案中,当文件没有扩展名时,这是唯一一个返回空字符串的解决方案:extension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '')