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


2104

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

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

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

还有更好的主意吗?


这个问题解释了这种bash技术以及其他一些相关技术。
jjclarkson 2009年

28
当应用下面的最佳答案时,不要像我在此处显示的那样简单地将变量粘贴到错误的位置: extension="{$filename##*.}"就像我花了一段时间!将$外面的卷发移到外面:右: extension="${filename##*.}"
克里斯K

4
这显然是一个不平凡的问题,对我而言,很难确定以下答案是否完全正确。令人惊讶的是,它不是(ba)sh中的内置操作(答案似乎使用模式匹配来实现该功能)。我决定改用os.path.splitext上面的Python ...
Peter Gibson

1
由于扩展名必须代表文件的性质,因此有一个魔术命令可以检查文件以了解其性质并提供标准扩展名。看到我的答案
F. Hauri

2
首先,这个问题是有问题的,因为从操作系统和unix文件系统的角度来看,通常没有文件扩展名之类的东西。用一个 ”。” 分开各个部分是人类的惯例,只有在人类同意遵守该惯例的情况下,该惯例才有效。例如,使用“ tar”程序,可能已经决定使用“ tar”来命名输出文件。前缀而不是“ .tar”后缀-给出“ tar.somedir”而不是“ somedir.tar”。因此,没有“通用的,始终有效的”解决方案-您必须编写符合您的特定需求和预期文件名的代码。
CM

Answers:


3497

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

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

或者,您可以将焦点放在路径的最后一个“ /”而不是“。”。即使您具有无法预测的文件扩展名,它也应能正常工作:

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

您可能需要检查文档:


85
查看gnu.org/software/bash/manual/html_node/…了解完整的功能集。
D.Shawley

24
在“ $ fullfile”中添加一些引号,否则将有可能损坏文件名。
lhunath

47
哎呀,您甚至可以编写filename =“ $ {fullfile ## * /}”并避免调用多余的内容basename
短暂

45
如果文件没有扩展名,则此“解决方案”不起作用-而是输出整个文件名,考虑到无扩展名的文件无处不在,这非常糟糕。
nccc 2012年

43
解决了不带扩展名的文件名的问题extension=$([[ "$filename" = *.* ]] && echo ".${filename##*.}" || echo '')。注意,如果一个扩展存在,它将被返回包括初始.,例如.txt
mklement0 2012年

682
~% FILE="example.tar.gz"

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

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

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

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

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


22
您(可能是无意中)提出了一个很好的问题,即如果文件名的“扩展名”部分中包含2个点,该怎么办,例如在.tar.gz中...我从未考虑过该问题,并且我怀疑这是在不知道所有可能的有效文件扩展名的情况下无法解决。
rmeador

8
为什么不能解决?在我的示例中,应考虑该文件包含两个扩展名,而不是包含两个点的扩展名。您可以分别处理这两个扩展。
朱利安诺

22
从词法上讲是无法解决的,您需要检查文件类型。考虑一下您是否有一个名为的游戏,dinosaurs.in.tar然后将其压缩为dinosaurs.in.tar.gz:)
porges

11
如果您要通过完整路径,则情况将变得更加复杂。我的一个有“。” 在路径中间的目录中,但文件名中没有。示例“ a / bc / d / e /文件名”将显示为“ .c / d / e /文件名”
Walt Sellers

6
显然no x.tar.gz的扩展gz名是x.tar,文件名就是它。没有双重扩展。我很确定boost :: filesystem会这样处理。(分割路径,change_extension ...),如果我没有记错的话,它的行为基于python。
v.oddou

430

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

basename filename .extension

例如:

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

我们得到

filename

60
第二个论点basename令人大开眼界,ty先生/女士:)
akaIDIOT

10
以及如何使用这种技术提取扩展名?;) 等一下!我们实际上并不预先知道。
Tomasz Gandor 2014年

3
假设您有一个以.zip或结尾的压缩目录.ZIP。有没有办法做类似的事情basename $file {.zip,.ZIP}
丹尼斯

8
虽然这仅回答了部分OP问题,但确实回答了我在google中输入的问题。:-)非常光滑!
sudo make install

1
轻松且符合POSIX
gpanda

146

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


站点解释了更多。

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

5
比Joachim的答案简单得多,但我始终必须查找POSIX变量替换。另外,它可以在cut没有--complementsed没有的Max OSX上运行-r
jwadsa​​ck 2014年

72

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

#!/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 =“ /”
    基本=“”
    ext =“”
/ home / me /:
    dir =“ / home / me /”
    基本=“”
    ext =“”
/ home / me / file:
    dir =“ / home / me /”
    base =“文件”
    ext =“”
/home/me/file.tar:
    dir =“ / home / me /”
    base =“文件”
    ext =“ tar”
/home/me/file.tar.gz:
    dir =“ / home / me /”
    base =“ file.tar”
    ext =“ gz”
/home/me/.hidden:
    dir =“ / home / me /”
    base =“ .hidden”
    ext =“”
/home/me/.hidden.tar:
    dir =“ / home / me /”
    base =“ .hidden”
    ext =“ tar”
/ home / me / ..:
    dir =“ / home / me /”
    base =“ ..”
    ext =“”
::
    dir =“”
    base =“。
    ext =“”

2
而不是dir="${fullpath:0:${#fullpath} - ${#filename}}"我经常见到的dir="${fullpath%$filename}"。编写起来更简单。不确定是否存在实际速度差异或陷阱。
dubiousjim 2012年

2
这使用#!/ bin / bash几乎总是错误的。如果可能的话,最好使用#!/ bin / sh;如果不能的话,最好使用#!/ usr / bin / env bash。
好人

@好人:我不知道这几乎总是错误的:which bash-> /bin/bash; 也许是你的发行版?
vol7ron

2
@ vol7ron-在许多发行版中,bash在/ usr / local / bin / bash中。在OSX上,许多人在/ opt / local / bin / bash中安装了更新的bash。因此,/ bin / bash是错误的,应该使用env来找到它。更好的是使用/ bin / sh和POSIX构造。除了solaris之外,这是POSIX外壳。
好人2013年

2
@GoodPerson,但是如果您对bash更满意,为什么要使用sh?这不是说,为什么可以在使用sh时使用Perl?
vol7ron

46

您可以使用basename

例:

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

您确实需要提供基本名称以及将被删除的扩展名,但是,如果您始终执行该扩展名tar-z则知道该扩展名将是.tar.gz

这应该做您想要的:

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

2
我想cd $(basename $1 .tar.gz)适用于.gz文件。但有问题的他提到Archive files have several extensions: tar.gz, tat.xz, tar.bz2
SS Hegde

Tomi Po在两年前发布了相同的内容。
phil294

嗨,布劳恩,哇,这是一个古老的问题。我认为日期发生了一些变化。我特别记得在问完这个问题后不久就回答了这个问题,那里只有几个其他答案。可能是这个问题与另一个问题合并了吗?
巴杰克·弗洛恩德·汉森

是的,我没记错。我最初在问这个问题的那天回答了stackoverflow.com/questions/14703318/…这个问题,两年后它被合并到了这个问题中。当我的答案以这种方式移动时,我很难怪我重复了答案。
Bjarke Freund-Hansen

37
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命令)。
嬉皮

32

梅伦(Mellen)在博客文章中发表评论:

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

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

输出:

filename: thisfile
extension: txt


29

无需费心awk或者sed甚至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' |
|---------------|-----------|-------|

试验结果

所有测试均通过。


2
不,其基本文件名text.tar.gz应为text扩展名为.tar.gz
frederick99 '18

2
@ frederick99正如我所说的,这里的解决方案与os.path.splitextPython 中的实现相匹配。该实现对于可能引起争议的输入是否理智是另一个主题。
Cyker

模式("$root")中的引号如何工作?如果省略它们会发生什么?(关于此事,我找不到任何文档。)另外,此文件如何处理带有*或带有文件名的文件名?
ymett

好了,测试表明我的报价使模式中的文字,即 *?没有特别的。因此,我的问题的两个部分相互回答。我是否正确,这没有记录?还是应该从引号通常禁用glob扩展这一事实来理解这一点?
ymett

辉煌的答案!我只是建议一个稍微简单一些的变种来计算根:root="${path#?}";root="${path::1}${root%.*}"—然后继续进行同样的操作以提取扩展名。
马兰

26

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

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

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

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

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

[在收到安德斯·林达尔(Anders Lindahl)的评论后再次更新]


4
仅在文件名/路径不包含其他点的情况下才有效:echo“ mpc-1.0.1.tar.gz” | 切-d'。--complement -f2-生产“MPC-1”(由限定后只是第2个字段。)
克莱顿休斯

@ClaytonHughes你是正确的,我应该对它进行更好的测试。添加了另一个解决方案。
程序员花了

sed表达式$应用于检查匹配的扩展名是否在文件名的末尾。否则,类似的文件名i.like.tar.gz.files.tar.bz2可能会产生意外的结果。
Anders Lindahl 2013年

@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
基因黑

23

这里有一些替代建议(大部分在中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 '^[/]\|^~/'

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


20

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

  • 对于不带扩展名的文件名(称为后缀)在此答案的其余部分),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'

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

预期输出-请注意以下几种情况:

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

19

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

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

这对没用echo。通常,echo $(command)最好编写简单的代码,command除非您明确要求外壳程序command在显示结果之前对输出执行空白标记化和通配符扩展。测验:的输出是什么echo $(echo '*')(如果那是您真正想要的,那么您真的想要echo *)。
三人房

@triplee我根本不使用echo命令。我只是用它来演示foo第二行的结果出现在第三行的结果。
罗恩

但是只是basename "${file%.*}"会做同样的事情。您正在使用命令替换来捕获其输出,仅echo立即捕获到相同的输出。(不加引号,其结果名义上有所不同;但这几乎不相关,这里的功能要少得多。)
Tripleee

basename "$file" .txt避免了参数替换的复杂性。
Tripleee '18

1
@Ron在指责他浪费我们的时间之前先阅读他的第一条评论。
frederick99

14

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

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。


只是想要BASEDIRECTORY :)谢谢!
卡洛斯·里卡多

12

您可以强制剪切以显示所有字段,并随后添加-到字段编号中。

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

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

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

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

即使没有扩展名的文件名也可以使用。


8

魔术文件识别

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

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

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"

8

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

这对我有用:

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

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


正确的结果(根据os.path.splitextOP的期望)是('stuff.tar', '.gz')
Cyker '16

6

我使用以下脚本

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

这根本没有效率。分叉的次数过多,这是不必要的,因为此操作可以在纯Bash中执行,而无需任何外部命令和分叉。
codeforester

5
$ F = "text file.test.txt"  
$ echo ${F/*./}  
txt  

这可以满足文件名中的多个点和空格,但是如果没有扩展名,它将返回文件名本身。易于检查;只需测试文件名和扩展名是否相同即可。

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


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

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

用法:

$ 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

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


4

这是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语句,对吗?
BHSPitMonkey

您可以通过执行另一个操作避免将Awk传递到Awk split()awk -F / '{ n=split($2, a, "."); print a[n] }' uses /`作为顶级定界符,但随后分割第二个字段.并打印新数组中的最后一个元素。
三人房

4

只需使用 ${parameter%word}

在您的情况下:

${FILE%.*}

如果要对其进行测试,请执行以下所有操作,然后删除扩展名:

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

2
为什么要下票?它仍然有用,尽管=标牌周围不应有空格。
SilverWolf-恢复莫妮卡

1
这很好。谢谢!(现在,等号周围没有空格,如果那就是为什么它被否决的原因)
亚历克斯。S.18年

3

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

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

对我不起作用:“基本名称:缺少操作数请尝试'基本名称-帮助'以获取更多信息。”
helmy

奇怪,确定要使用Bash吗?就我而言,无论是版本3.2.25(旧的CentOS)还是版本4.3.30(Debian Jessie),它都可以正常工作。
cvr

文件名中是否有空格?尝试使用filename="$(basename "${fullname%.*}")"
阿德里安

的第二个参数basename是可选的,但指定要剥离的扩展名。替换可能仍然有用,但basename实际上可能没有用,因为您实际上可以使用shell内置函数执行所有这些替换。
三胞胎

3

很大程度上是基于@ 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
}     

用法示例...

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

2

一个简单的答案:

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

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

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

更一般而言,如果您要删除的最后一次出现。<something><something-else>然后

${1.*.*}

应该工作正常。

上述答案的链接似乎已失效。这是对您可以从TLDP直接在Bash中直接进行的一堆字符串操作的很好解释


有没有办法使比赛不区分大小写?
tonix

2

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

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

第一行说明:它匹配PATH.EXT或ANYTHING并将其替换为EXT。如果任何都匹配,则不捕获分机组。


2

这是唯一为我工作的人:

path='folder/other_folder/file.js'

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

>> file

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


1

这是我编写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 '')
f0nzie

1

使用示例文件/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

2
不知道为什么会有这么多否决票-实际上比公认的答案更有效。(对于后者,它也与输入文件名一起中断,没有扩展名)。使用一条明确的路径basename可能是矫kill过正。
mklement0 2014年

1

从上面的答案来看,最短的模仿Python的oneliner

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

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

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

我对此表示不满。我正在考虑删除答案,人们不知何故不喜欢它。
commonpike 2014年

basename不会删除扩展名,只会删除路径。
David Cullen

自从看了我忘记了SUFFIX选项的手册页以来已经有很长时间了。
David Cullen

您必须先确定要剥离的扩展名,然后才能知道要放入的扩展名,EXT因此这就是乌龟。(此外,您应避免将所有大写字母用作私人变量名称;它们保留用于系统变量。)
Tripleee,2017年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.