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

ibz*_*ibz 1969 string bash filenames

我想分别获取文件名(没有扩展名)和扩展名.

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

NAME=`echo "$FILE" | cut -d'.' -f1`
EXTENSION=`echo "$FILE" | cut -d'.' -f2`
Run Code Online (Sandbox Code Playgroud)

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

它可以在Python中轻松完成

file, ext = os.path.splitext(path)
Run Code Online (Sandbox Code Playgroud)

但是如果可能的话,我宁愿不为此启动Python解释器.

有更好的想法吗?

Pet*_*esh 3313

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

filename=$(basename -- "$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"
Run Code Online (Sandbox Code Playgroud)

或者,您可以专注于路径的最后一个'/'而不是'.' 即使你有不可预测的文件扩展名,它应该工作:

filename="${fullfile##*/}"
Run Code Online (Sandbox Code Playgroud)

您可能需要查看文档:

  • 查看完整功能集的http://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html#Shell-Parameter-Expansion. (83认同)
  • 哎呀,你甚至可以写filename ="$ {fullfile ##*/}"并避免调用额外的`basename` (46认同)
  • 如果文件没有扩展名,则此"解决方案"不起作用 - 而是输出整个文件名,考虑到没有扩展名的文件无处不在,这是非常糟糕的. (40认同)
  • 修复处理没有扩展名的文件名:`extension = $([["$ filename"=*.*]] && echo".$ {filename ##*.}"|| echo'')`.请注意,如果存在扩展_is_,将返回包含初始`.`的内容,例如`.txt`. (40认同)
  • 添加一些引号到"$ fullfile",否则你将冒险破坏文件名. (23认同)
  • @tomsgd 命令被赋予诸如“command --long-something -short”之类的选项。在这种情况下,该命令还接受文件名,但该文件名可以以一两个破折号开头,这将使该命令尝试将其解释为选项而不是文件名,并会给出意外的结果。`--` 告诉命令它不应该有更多的选项,接下来的只是空格分隔的参数,在本例中是一个文件名 (6认同)
  • 在文件名= $(basename-“ $ fullfile”)`中使用-的原因是什么?从我的本地测试来看,没有它似乎同样有效,但是我丢失了什么吗? (4认同)
  • @ v.oddou:结果:Python,Ruby,node.js和Perl构成了异常(在开头特别处理`.`); 相比之下,Boost C++库没有(并且.NET也没有,但我不认为这很重要,因为它是专为Windows设计的). (3认同)
  • 不适用于`thing.1.1.0.tar.gz`之类的东西 (3认同)
  • 请注意以下有关此解决方案的内容:-对于不带扩展名的文件名,`$ extension`返回_file name_而不是`“”`。-`$ extension`不包含初始的`“。”`。-如果输入文件名以“ _。”开头,并且不包含其他“。”字符(例如,.bash_profile),则$ filename将为“””。 (2认同)
  • 我有时希望`basename`有一个删除任意扩展名的选项.`basename foo.txt .txt`打印`foo`,但它不能删除`.txt`,除非你告诉它后缀是什么.(是的,它很容易与其他设施一起使用,但是``$ {filename%.*}"`并不容易记住.) (2认同)
  • @ v.oddou:很公道,它肯定会大大简化解决方案(参见我的回答的疯狂).然而,它可能不是人们所期望的(我没有),所以我认为值得指出 - 初始期间标记隐藏文件和期间扩展分隔符是竞争概念. (2认同)

Jul*_*ano 614

~% FILE="example.tar.gz"
~% echo "${FILE%%.*}"
example
~% echo "${FILE%.*}"
example.tar
~% echo "${FILE#*.}"
tar.gz
~% echo "${FILE##*.}"
gz
Run Code Online (Sandbox Code Playgroud)

有关更多详细信息,请参阅Bash手册中的shell参数扩展.

  • 如果文件名的"扩展"部分有两个点,你(也许是无意中)提出了一个很好的问题,如.tar.gz ......我从来没有考虑过这个问题,我怀疑它是如果不事先知道所有可能的有效文件扩展名,就无法解决. (20认同)
  • 它在词汇基础上无法解决,您需要检查文件类型.考虑一下你是否有一个名为`dinosaurs.in.tar`的游戏,然后你把它压缩到`dinosaurs.in.tar.gz` :) (20认同)
  • 为什么不能解决?在我的示例中,应该认为该文件包含*two*扩展名,而不是包含两个点的扩展名.您可以单独处理两个扩展. (8认同)
  • 如果您传递完整路径,这会变得更复杂.我的一个人有'.' 在路径中间的目录中,但在文件名中没有.示例"a/bc/d/e/filename"将结束".c/d/e/filename" (7认同)
  • 显然没有`x.tar.gz`的扩展名是`gz`,文件名是`x.tar`就是它.没有双重扩展这样的东西.我很确定boost :: filesystem会这样处理它.(split path,change_extension ...)如果我没有弄错的话,它的行为是基于python的. (5认同)
  • 如果你的文件中有 `.` 和“扩展名”`.tar.gz`,你可以使用 `${FILE%.*.*}` (2认同)

小智 378

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

basename filename .extension
Run Code Online (Sandbox Code Playgroud)

例如:

basename /path/to/dir/filename.txt .txt
Run Code Online (Sandbox Code Playgroud)

我们得到了

filename
Run Code Online (Sandbox Code Playgroud)

  • "basename"的第二个参数非常令人大开眼界,ty先生/女士:) (53认同)
  • 以及如何使用这种技术提取扩展?;) 等一下!我们实际上并不了解它. (10认同)
  • 虽然这只回答了OP问题的一部分,但它确实回答了我输入谷歌的问题.:-)很光滑! (4认同)
  • 假设你有一个以`.zip`或`.ZIP`结尾的压缩目录.有没有办法可以做`basename $ file {.zip,.ZIP}`? (2认同)

sot*_*pme 137

你可以使用POSIX变量的神奇之处:

bash-3.2$ FILENAME=somefile.tar.gz
bash-3.2$ echo ${FILENAME%%.*}
somefile
bash-3.2$ echo ${FILENAME%.*}
somefile.tar
Run Code Online (Sandbox Code Playgroud)

有一个警告,如果你的文件名是形式,./somefile.tar.gz那么echo ${FILENAME%%.*}贪婪地删除最长的匹配.,你就有了空字符串.

(您可以使用临时变量解决此问题:

FULL_FILENAME=$FILENAME
FILENAME=${FULL_FILENAME##*/}
echo ${FILENAME%%.*}
Run Code Online (Sandbox Code Playgroud)

)


这个网站解释更多.

${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
Run Code Online (Sandbox Code Playgroud)

  • 比Joachim的答案简单得多,但我总是要查找POSIX变量替换.此外,这在Max OSX上运行,其中`cut`没有`--complement`而`sed`没有`-r`. (5认同)

Doc*_*r J 68

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

#!/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
Run Code Online (Sandbox Code Playgroud)

以下是一些测试用例:

$ 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:0:$ {#fullpath} - $ {#filename}}"`我经常看到`dir ="$ {fullpath%$ filename}"`.写起来比较简单.不确定是否有任何真正的速度差异或陷阱. (2认同)
  • 这使用了#!/ bin/bash,这几乎总是错误的.如果可能,首选#!/ bin/sh或者如果没有,请选择#!/ usr/bin/env bash. (2认同)
  • @ vol7ron - 关于许多发行版bash在/ usr/local/bin/bash中.在OSX上,许多人在/ opt/local/bin/bash中安装了更新的bash.因此/ bin/bash是错误的,应该使用env来查找它.更好的是使用/ bin/sh和POSIX构造.除了solaris,这是一个POSIX shell. (2认同)
  • @GoodPerson但是如果你对bash感觉更舒服,为什么要用sh?这不就是说,为什么在使用sh时可以使用Perl? (2认同)

Bja*_*sen 43

你可以用basename.

例:

$ basename foo-bar.tar.gz .tar.gz
foo-bar
Run Code Online (Sandbox Code Playgroud)

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

这应该做你想要的:

tar -zxvf $1
cd $(basename $1 .tar.gz)
Run Code Online (Sandbox Code Playgroud)

  • 我想`cd $(basename $ 1 .tar.gz)`适用于.gz文件.但问题是他提到`存档文件有几个扩展名:tar.gz,tat.xz,tar.bz2` (2认同)

pax*_*blo 32

pax> echo a.b.js | sed 's/\.[^.]*$//'
a.b
pax> echo a.b.js | sed 's/^.*\.//'
js
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

顺便说一下,这些命令的工作原理如下.

用于NAME替换"."字符后跟任意数量的非"."字符直到行尾的命令,没有任何内容(即,它删除从最后"."到行尾的所有内容,包括在内).这基本上是使用正则表达式欺骗的非贪婪替换.

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


小智 28

梅伦在博客文章评论中写道:

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

file="thisfile.txt"
echo "filename: ${file%.*}"
echo "extension: ${file##*.}"
Run Code Online (Sandbox Code Playgroud)

输出:

filename: thisfile
extension: txt
Run Code Online (Sandbox Code Playgroud)

  • @REACHUS:请参阅http://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html (2认同)

Cyk*_*ker 26

无需费心awk或者sed甚至perl为这个简单的任务.有一个纯Bash,os.path.splitext()兼容的解决方案,只使用参数扩展.

参考实施

文件os.path.splitext(path):

分裂路径名路径成一对(root, ext),使得root + ext == path,和分机是空的或用一个周期开始,并且包含至多一个周期.基本名称的前导句点被忽略; splitext('.cshrc')回报('.cshrc', '').

Python代码:

root, ext = os.path.splitext(path)
Run Code Online (Sandbox Code Playgroud)

Bash实现

尊重领先时期

root="${path%.*}"
ext="${path#"$root"}"
Run Code Online (Sandbox Code Playgroud)

忽略领先时期

root="${path#.}";root="${path%"$root"}${root%.*}"
ext="${path#"$root"}"
Run Code Online (Sandbox Code Playgroud)

测试

以下是Ignoring leading period实现的测试用例,它应该与每个输入上的Python参考实现相匹配.

|---------------|-----------|-------|
|path           |root       |ext    |
|---------------|-----------|-------|
|' .txt'        |' '        |'.txt' |
|' .txt.txt'    |' .txt'    |'.txt' |
|' txt'         |' txt'     |''     |
|'*.txt.txt'    |'*.txt'    |'.txt' |
|'.cshrc'       |'.cshrc'   |''     |
|'.txt'         |'.txt'     |''     |
|'?.txt.txt'    |'?.txt'    |'.txt' |
|'\n.txt.txt'   |'\n.txt'   |'.txt' |
|'\t.txt.txt'   |'\t.txt'   |'.txt' |
|'a b.txt.txt'  |'a b.txt'  |'.txt' |
|'a*b.txt.txt'  |'a*b.txt'  |'.txt' |
|'a?b.txt.txt'  |'a?b.txt'  |'.txt' |
|'a\nb.txt.txt' |'a\nb.txt' |'.txt' |
|'a\tb.txt.txt' |'a\tb.txt' |'.txt' |
|'txt'          |'txt'      |''     |
|'txt.pdf'      |'txt'      |'.pdf' |
|'txt.tar.gz'   |'txt.tar'  |'.gz'  |
|'txt.txt'      |'txt'      |'.txt' |
|---------------|-----------|-------|
Run Code Online (Sandbox Code Playgroud)

检测结果

所有测试都通过了

  • @frederick99 正如我所说,这里的解决方案与 Python 中 `os.path.splitext` 的实现相匹配。对于可能有争议的输入,该实现是否合理是另一个话题。 (3认同)
  • 不,`text.tar.gz` 的基本文件名应该是`text`,扩展名应该是`.tar.gz` (2认同)

Som*_*ude 25

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

$ echo "foo.tar.gz" | cut -d'.' --complement -f2-
foo
Run Code Online (Sandbox Code Playgroud)

正如Clayton Hughes在评论中指出的那样,这对问题中的实际例子不起作用.所以作为替代方案,我建议使用sed扩展正则表达式,如下所示:

$ echo "mpc-1.0.1.tar.gz" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'
mpc-1.0.1
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这仅适用于文件名/路径不包含任何其他点的情况:echo"mpc-1.0.1.tar.gz"| 切-d'.' --complement -f2-产生"mpc-1"(只是在分隔后的前两个字段.) (4认同)

hen*_*ber 21

以下是一些替代建议(主要是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 '^[/]\|^~/'
Run Code Online (Sandbox Code Playgroud)

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


mkl*_*nt0 18

接受的答案行之有效的典型案例,但在失败边缘的情况下,即:

  • 对于没有扩展名的文件名(在本答案的其余部分中称为后缀),extension=${filename##*.}返回输入文件名而不是空字符串.
  • extension=${filename##*.}不包括最初的.,违反惯例.
    • 盲目前置.不适用于没有后缀的文件名.
  • filename="${filename%.*}"将是空字符串,如果输入文件名以其开头.并且不包含其他.字符(例如.bash_profile) - 与惯例相反.

---------

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

示例电话:

splitPath '/etc/bash.bashrc' dir fname fnameroot suffix
# -> $dir == '/etc'
# -> $fname == 'bash.bashrc'
# -> $fnameroot == 'bash'
# -> $suffix == '.bashrc'
Run Code Online (Sandbox Code Playgroud)

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

执行该功能的测试代码:

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
Run Code Online (Sandbox Code Playgroud)

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

  • 没有后缀的文件名
  • .(被视为后缀的开头)开头的文件名
  • /(尾随/被忽略)结尾的输入路径
  • .作为文件名的输入路径(作为父路径返回)
  • 一个具有多个.前缀标记的文件名(只有最后一个被认为是后缀):
----- /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
Run Code Online (Sandbox Code Playgroud)


小智 17

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

$ file=/blaabla/bla/blah/foo.txt
echo $(basename ${file%.*}) # foo
Run Code Online (Sandbox Code Playgroud)

  • @Ron 在指责他浪费我们的时间之前阅读他的第一条评论。 (2认同)

小智 12

我想如果你只需要文件的名称,你可以试试这个:

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"
Run Code Online (Sandbox Code Playgroud)

这就是全部= D.


Ken*_*ler 12

这是唯一对我有用的:

path='folder/other_folder/file.js'

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

>> file
Run Code Online (Sandbox Code Playgroud)

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


小智 11

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

NAME=`basename "$FILE"`
EXTENSION=`echo "$NAME" | cut -d'.' -f2-`
Run Code Online (Sandbox Code Playgroud)

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

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

NAME=`basename "$FILE" | cut -d'.' -f-1`
Run Code Online (Sandbox Code Playgroud)

这适用于没有任何扩展名的文件名.


小智 8

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

这对我有用:

fullfile="stuff.tar.gz"
fileExt=${fullfile#*.}
fileName=${fullfile%*.$fileExt}
Run Code Online (Sandbox Code Playgroud)

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


Fra*_*ona 8

之前的答案没有使用 bash 正则表达式
\n这里是一个纯 bash 解决方案,它将路径分成:

\n
    \n
  • 目录路径/,当存在时其尾随
    \n丢弃尾随的正则表达式/太长,所以我没有发布它
  • \n
  • 文件,不包括(最后一个)点扩展名
  • \n
  • (最后一个)点扩展,及其前导.
  • \n
\n

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

\n
#!/bin/bash\n\nfor path; do\n\n####### the relevant part ######\n\n[[ $path =~ ^(\\.{1,2}|.*/\\.{0,2})$|^(.*/)([^/]+)(\\.[^/]*)$|^(.*/)(.+)$|^(.+)(\\..*)$|^(.+)$ ]]\n\ndirpath=${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[5]}\nfilename=${BASH_REMATCH[3]}${BASH_REMATCH[6]}${BASH_REMATCH[7]}${BASH_REMATCH[9]}\nfilext=${BASH_REMATCH[4]}${BASH_REMATCH[8]}\n\n# dirpath should be non-null\n[[ $dirpath ]] || dirpath=\'.\'\n\n################################\n\nprintf \'%s=%q\\n\' \\\n    path     "$path" \\\n    dirpath  "$dirpath" \\\n    filename "$filename" \\\n    filext   "$filext"\n\ndone\n
Run Code Online (Sandbox Code Playgroud)\n

它是如何工作的?

\n

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

\n
以下是一组扩展但并非详尽的示例的结果:
\n
+--------------------------------------------------------+\n| input             dirpath        filename       filext |\n+--------------------------------------------------------+\n\'\'                  .              \'\'             \'\'\n.                   .              \'\'             \'\'\n..                  ..             \'\'             \'\'\n...                 .              ..             .\n.file               .              .file          \'\'\n.file.              .              .file          .\n.file..             .              .file.         .\n.file.Z             .              .file          .Z\n.file.sh.Z          .              .file.sh       .Z\nfile                .              file           \'\'\nfile.               .              file           .\nfile..              .              file.          .\nfile.Z              .              file           .Z\nfile.sh.Z           .              file.sh        .Z\ndir/                dir/           \'\'             \'\'\ndir/.               dir/.          \'\'             \'\'\ndir/...             dir/           ..             .\ndir/.file           dir/           .file          \'\'\ndir/.file.          dir/           .file          .\ndir/.file..         dir/           .file.         .\ndir/.file.Z         dir/           .file          .Z\ndir/.file.x.Z       dir/           .file.x        .Z\ndir/file            dir/           file           \'\'\ndir/file.           dir/           file           .\ndir/file..          dir/           file.          .\ndir/file.Z          dir/           file           .Z\ndir/file.x.Z        dir/           file.x         .Z\ndir./.              dir./.         \'\'             \'\'\ndir./...            dir./          ..             .\ndir./.file          dir./          .file          \'\'\ndir./.file.         dir./          .file          .\ndir./.file..        dir./          .file.         .\ndir./.file.Z        dir./          .file          .Z\ndir./.file.sh.Z     dir./          .file.sh       .Z\ndir./file           dir./          file           \'\'\ndir./file.          dir./          file           .\ndir./file..         dir./          file.          .\ndir./file.Z         dir./          file           .Z\ndir./file.x.Z       dir./          file.x         .Z\ndir//               dir//          \'\'             \'\'\ndir//.              dir//.         \'\'             \'\'\ndir//...            dir//          ..             .\ndir//.file          dir//          .file          \'\'\ndir//.file.         dir//          .file          .\ndir//.file..        dir//          .file.         .\ndir//.file.Z        dir//          .file          .Z\ndir//.file.x.Z      dir//          .file.x        .Z\ndir//file           dir//          file           \'\'\ndir//file.          dir//          file           .\ndir//file..         dir//          file.          .\ndir//file.Z         dir//          file           .Z\ndir//file.x.Z       dir//          file.x         .Z\ndir.//.             dir.//.        \'\'             \'\'\ndir.//...           dir.//         ..             .\ndir.//.file         dir.//         .file          \'\'\ndir.//.file.        dir.//         .file          .\ndir.//.file..       dir.//         .file.         .\ndir.//.file.Z       dir.//         .file          .Z\ndir.//.file.x.Z     dir.//         .file.x        .Z\ndir.//file          dir.//         file           \'\'\ndir.//file.         dir.//         file           .\ndir.//file..        dir.//         file.          .\ndir.//file.Z        dir.//         file           .Z\ndir.//file.x.Z      dir.//         file.x         .Z\n/                   /              \'\'             \'\'\n/.                  /.             \'\'             \'\'\n/..                 /..            \'\'             \'\'\n/...                /              ..             .\n/.file              /              .file          \'\'\n/.file.             /              .file          .\n/.file..            /              .file.         .\n/.file.Z            /              .file          .Z\n/.file.sh.Z         /              .file.sh       .Z\n/file               /              file           \'\'\n/file.              /              file           .\n/file..             /              file.          .\n/file.Z             /              file           .Z\n/file.sh.Z          /              file.sh        .Z\n/dir/               /dir/          \'\'             \'\'\n/dir/.              /dir/.         \'\'             \'\'\n/dir/...            /dir/          ..             .\n/dir/.file          /dir/          .file          \'\'\n/dir/.file.         /dir/          .file          .\n/dir/.file..        /dir/          .file.         .\n/dir/.file.Z        /dir/          .file          .Z\n/dir/.file.x.Z      /dir/          .file.x        .Z\n/dir/file           /dir/          file           \'\'\n/dir/file.          /dir/          file           .\n/dir/file..         /dir/          file.          .\n/dir/file.Z         /dir/          file           .Z\n/dir/file.x.Z       /dir/          file.x         .Z\n/dir./.             /dir./.        \'\'             \'\'\n/dir./...           /dir./         ..             .\n/dir./.file         /dir./         .file          \'\'\n/dir./.file.        /dir./         .file          .\n/dir./.file..       /dir./         .file.         .\n/dir./.file.Z       /dir./         .file          .Z\n/dir./.file.sh.Z    /dir./         .file.sh       .Z\n/dir./file          /dir./         file           \'\'\n/dir./file.         /dir./         file           .\n/dir./file..        /dir./         file.          .\n/dir./file.Z        /dir./         file           .Z\n/dir./file.x.Z      /dir./         file.x         .Z\n/dir//              /dir//         \'\'             \'\'\n/dir//.             /dir//.        \'\'             \'\'\n/dir//...           /dir//         ..             .\n/dir//.file         /dir//         .file          \'\'\n/dir//.file.        /dir//         .file          .\n/dir//.file..       /dir//         .file.         .\n/dir//.file.Z       /dir//         .file          .Z\n/dir//.file.x.Z     /dir//         .file.x        .Z\n/dir//file          /dir//         file           \'\'\n/dir//file.         /dir//         file           .\n/dir//file..        /dir//         file.          .\n/dir//file.Z        /dir//         file           .Z\n/dir//file.x.Z      /dir//         file.x         .Z\n/dir.//.            /dir.//.       \'\'             \'\'\n/dir.//...          /dir.//        ..             .\n/dir.//.file        /dir.//        .file          \'\'\n/dir.//.file.       /dir.//        .file          .\n/dir.//.file..      /dir.//        .file.         .\n/dir.//.file.Z      /dir.//        .file          .Z\n/dir.//.file.x.Z    /dir.//        .file.x        .Z\n/dir.//file         /dir.//        file           \'\'\n/dir.//file.        /dir.//        file           .\n/dir.//file..       /dir.//        file.          .\n/dir.//file.Z       /dir.//        file           .Z\n/dir.//file.x.Z     /dir.//        file.x         .Z\n//                  //             \'\'             \'\'\n//.                 //.            \'\'             \'\'\n//..                //..           \'\'             \'\'\n//...               //             ..             .\n//.file             //             .file          \'\'\n//.file.            //             .file          .\n//.file..           //             .file.         .\n//.file.Z           //             .file          .Z\n//.file.sh.Z        //             .file.sh       .Z\n//file              //             file           \'\'\n//file.             //             file           .\n//file..            //             file.          .\n//file.Z            //             file           .Z\n//file.sh.Z         //             file.sh        .Z\n//dir/              //dir/         \'\'             \'\'\n//dir/.             //dir/.        \'\'             \'\'\n//dir/...           //dir/         ..             .\n//dir/.file         //dir/         .file          \'\'\n//dir/.file.        //dir/         .file          .\n//dir/.file..       //dir/         .file.         .\n//dir/.file.Z       //dir/         .file          .Z\n//dir/.file.x.Z     //dir/         .file.x        .Z\n//dir/file          //dir/         file           \'\'\n//dir/file.         //dir/         file           .\n//dir/file..        //dir/         file.          .\n//dir/file.Z        //dir/         file           .Z\n//dir/file.x.Z      //dir/         file.x         .Z\n//dir./.            //dir./.       \'\'             \'\'\n//dir./...          //dir./        ..             .\n//dir./.file        //dir./        .file          \'\'\n//dir./.file.       //dir./        .file          .\n//dir./.file..      //dir./        .file.         .\n//dir./.file.Z      //dir./        .file          .Z\n//dir./.file.sh.Z   //dir./        .file.sh       .Z\n//dir./file         //dir./        file           \'\'\n//dir./file.        //dir./        file           .\n//dir./file..       //dir./        file.          .\n//dir./file.Z       //dir./        file           .Z\n//dir./file.x.Z     //dir./        file.x         .Z\n//dir//             //dir//        \'\'             \'\'\n//dir//.            //dir//.       \'\'             \'\'\n//dir//...          //dir//        ..             .\n//dir//.file        //dir//        .file          \'\'\n//dir//.file.       //dir//        .file          .\n//dir//.file..      //dir//        .file.         .\n//dir//.file.Z      //dir//        .file          .Z\n//dir//.file.x.Z    //dir//        .file.x        .Z\n//dir//file         //dir//        file           \'\'\n//dir//file.        //dir//        file           .\n//dir//file..       //dir//        file.          .\n//dir//file.Z       //dir//        file           .Z\n//dir//file.x.Z     //dir//        file.x         .Z\n//dir.//.           //dir.//.      \'\'             \'\'\n//dir.//...         //dir.//       ..             .\n//dir.//.file       //dir.//       .file          \'\'\n//dir.//.file.      //dir.//       .file          .\n//dir.//.file..     //dir.//       .file.         .\n//dir.//.file.Z     //dir.//       .file          .Z\n//dir.//.file.x.Z   //dir.//       .file.x        .Z\n//dir.//file        //dir.//       file           \'\'\n//dir.//file.       //dir.//       file           .\n//dir.//file..      //dir.//       file.          .\n//dir.//file.Z      //dir.//       file           .Z\n//dir.//file.x.Z    //dir.//       file.x         .Z\n
Run Code Online (Sandbox Code Playgroud)\n

正如您所看到的,行为与basename和不同dirname。\n例如basename\xc2\xa0dir/输出dir,而正则表达式将为您提供一个空文件名。.与和相同..,它们被视为目录,而不是文件名。

\n

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

\n

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

\n
\n

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

\n
#!/bin/sh\n\nfor path; do\n\n####### the relevant part ######\n\nfullname=${path##*/}\n\ncase $fullname in\n. | ..)\n    dirpath="$path"\n    filename=\'\'\n    filext=\'\'\n    ;;\n*)\n    dirpath=${path%"$fullname"}\n    dirpath=${dirpath:-.}       # dirpath should be non-null\n    filename=${fullname#.}\n    filename="${fullname%"$filename"}${filename%.*}"\n    filext=${fullname#"$filename"}\n    ;;\nesac\n\n################################\n\nprintf \'%s=%s\\n\' \\\n    path     "$path" \\\n    dirpath  "$dirpath" \\\n    filename "$filename" \\\n    filext   "$filext"\n\ndone\n
Run Code Online (Sandbox Code Playgroud)\n
\n

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

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

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

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

    \n
  • \n
\n


F. *_*uri 7

魔术文件识别

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

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

file myfile.txt
myfile.txt: UTF-8 Unicode text

file -b --mime-type myfile.txt
text/plain
Run Code Online (Sandbox Code Playgroud)

标准扩展可以在/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
Run Code Online (Sandbox Code Playgroud)

您可以创建一个函数来确定正确的扩展名.有一点(不完美)的样本:

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##*[/.-]}
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

echo "$fullfile -> $filename . $extension"
Run Code Online (Sandbox Code Playgroud)

  • 虽然这不是对原始帖子的直接回答,但这是迄今为止最明智的回应。感谢您提供它。 (2认同)

eny*_*nyo 6

只需使用 ${parameter%word}

在你的情况下:

${FILE%.*}
Run Code Online (Sandbox Code Playgroud)

如果您想对其进行测试,请执行以下所有操作,只需删除扩展名:

FILE=abc.xyz; echo ${FILE%.*};
FILE=123.abc.xyz; echo ${FILE%.*};
FILE=abc; echo ${FILE%.*};
Run Code Online (Sandbox Code Playgroud)

  • 为什么投反对票?它仍然很有用,尽管 `=` 符号周围不应该有空格。 (2认同)

Joy*_*tta 6

我使用以下脚本

$ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev
foo
Run Code Online (Sandbox Code Playgroud)


小智 6

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

filename=$(basename ${fullname%.*})
Run Code Online (Sandbox Code Playgroud)


小智 5

$ F = "text file.test.txt"  
$ echo ${F/*./}  
txt  
Run Code Online (Sandbox Code Playgroud)

这适用于文件名中的多个点和空格,但是如果没有扩展名,则返回文件名本身.虽然容易检查; 只测试文件名和扩展名是否相同.

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


小智 5

这是带有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
Run Code Online (Sandbox Code Playgroud)


Ale*_*ray 5

很大程度上基于@mklement0的优秀,并且充满了随机的、有用的bashisms - 以及这个/其他问题/“该死的互联网”的其他答案......我把它全部包裹在一个稍微更容易理解的地方,我的(或你的)的可重用函数.bash_profile,它负责(我认为)应该是更健壮的版本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 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
}     
Run Code Online (Sandbox Code Playgroud)

用法示例...

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>
Run Code Online (Sandbox Code Playgroud)


Den*_*nis 5

如何在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
Run Code Online (Sandbox Code Playgroud)

注意事项:在最后一个点上分割,这对于其中带有点的文件名效果很好,但对于其中带有点的扩展名效果不好。请参见下面的示例。

用法:

$ 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.
Run Code Online (Sandbox Code Playgroud)

可能有更好的方法可以做到这一点。随时编辑我的答案以改善它。


如果您要处理的扩展名有限,并且您知道所有扩展名,请尝试以下操作:

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
Run Code Online (Sandbox Code Playgroud)

这并不会有警告作为第一个例子,但你必须处理每一个案件,因此它可以根据你多少可以扩展期待更加繁琐。