在文件名中获取扩展名


33

我如何从bash获取文件扩展名?这是我尝试过的:

filename=`basename $filepath`
fileext=${filename##*.}

通过这样做,我可以bz2从路径中得到扩展/dir/subdir/file.bz2,但是路径有问题/dir/subdir/file-1.0.tar.bz2

如果可能,我希望仅使用bash而不使用外部程序的解决方案。

为了明确说明我的问题,我正在创建一个bash脚本,仅需通过一个命令即可提取任何给定的存档extract path_to_file。如何提取文件由脚本通过查看其压缩或归档类型来确定,该类型可以是.tar.gz,.gz,.bz2等。我认为这应该涉及字符串操作,例如,如果我得到扩展名,.gz则我应该检查它.tar之前是否有字符串.gz-如果有,扩展名应该是.tar.gz


2
file =“ / dir / subdir / file-1.0.tar.bz2”; echo $ {file ## *。}在这里打印'.bz2'。您期望的输出是什么?
axel_c 2010年

1
我需要.tar.bz2
uray 2010年

Answers:


19

如果文件名为file-1.0.tar.bz2,则扩展名为bz2。您用于提取扩展名(fileext=${filename##*.})的方法完全有效¹。

您如何确定要扩展名是tar.bz2不是bz2或不是0.tar.bz2?您需要先回答这个问题。然后,您可以找出与您的规范匹配的shell命令。

  • 一种可能的规范是扩展名必须以字母开头。对于一些常见的扩展(例如)7z,此启发式方法会失败,最好将其视为特例。这是一个bash / ksh / zsh实现:

    basename=$filename; fileext=
    while [[ $basename = ?*.* &&
             ( ${basename##*.} = [A-Za-z]* || ${basename##*.} = 7z ) ]]
    do
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    done
    fileext=${fileext%.}

    为了实现POSIX的可移植性,您需要使用一条case语句进行模式匹配。

    while case $basename in
            ?*.*) case ${basename##*.} in [A-Za-z]*|7z) true;; *) false;; esac;;
            *) false;;
          esac
    do 
  • 另一个可能的规范是某些扩展名表示编码,并指示需要进一步剥离。这是bash / ksh / zsh的实现(需要shopt -s extglob在bash和setopt ksh_globzsh下进行):

    basename=$filename
    fileext=
    while [[ $basename = ?*.@(bz2|gz|lzma) ]]; do
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    done
    if [[ $basename = ?*.* ]]; then
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    fi
    fileext=${fileext%.}

    请注意,这被认为0是的扩展file-1.0.gz

¹ 及其相关结构都在POSIX中,因此它们可以在任何非仿古的Bourne风格的外壳中使用,例如ash,bash,ksh或zsh。 ${VARIABLE##SUFFIX}


应该通过检查最后一个.标记之前的字符串是否为存档类型来解决该问题,例如tar,是否0应终止其非存档类型(如迭代)。
uray 2010年

2
@uray:在这种情况下可以使用,但这不是一般的解决方案。考虑Maciej的示例.patch.lzma。一个更好的启发是考虑字符串最后.:如果它是一个压缩后缀(.7z.bz2.gz,...),继续剥离。
吉尔斯(Gillles)“所以-别再作恶了” 2010年

@NoamM缩进有什么问题?编辑后肯定会损坏:双嵌套代码的缩进与单嵌套代码相同。
Gilles)'所以

22

您可以通过仅对文件名进行模式匹配而不是两次提取扩展名来简化事情:

case "$filename" in
    *.tar.bz2) bunzip_then_untar ;;
    *.bz2)     bunzip_only ;;
    *.tar.gz)  untar_with -z ;;
    *.tgz)     untar_with -z ;;
    *.gz)      gunzip_only ;;
    *.zip)     unzip ;;
    *.7z)      do something ;;
    *)         do nothing ;;
esac

这个解决方案非常简单。
AsymLabs


2

这是我的照片:将点转换为换行符,直通tail,获取最后一行:

$> TEXT=123.234.345.456.456.567.678
$> echo $TEXT | tr . \\n | tail -n1
678

0
echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}

例如:

% echo $filename
2.6.35-zen2.patch.lzma
% echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}
.patch.lzma

不适用于所有情况。尝试使用“ foo.7z”
axel_c 2010年

您需要加引号,并且最好printf在文件名包含反斜杠或以-"${filename#$(printf %s "$filename" | sed 's/\.[^[:digit:]].*$//g;')}"
Gilles'SO- stop not evil'10

@axel_c:是的,作为示例,我已经实现了与Maciej相同的规范。您认为哪种启发式方法比“以字母开头”更好?
吉尔斯(Gillles)“所以-别再作恶了”

1
@吉尔斯:我只是认为除非您使用已知扩展的预先计算列表,否则没有解决方案,因为扩展可以是任何东西。
axel_c 2010年

0

有一天,我创建了这些棘手的函数:

# args: string how_many
function get_last_letters(){ echo ${1:${#1}-$2:$2}; }
function cut_last_letters(){ echo ${1:0:${#1}-$2}; }

我发现这种简单的方法在许多情况下非常有用,不仅在扩展方面。

用于检查扩展名- 简单可靠

~$ get_last_letters file.bz2 4
.bz2
~$ get_last_letters file.0.tar.bz2 4
.bz2

对于截止扩展:

~$ cut_last_letters file.0.tar.bz2 4
file.0.tar

对于扩展名:

~$ echo $(cut_last_letters file.0.tar.bz2 4).gz
file.0.tar.gz

或者,如果您喜欢“方便的功能:

~$ function cut_last_letters_and_add(){ echo ${1:0:${#1}-$2}"$3"; }
~$ cut_last_letters_and_add file.0.tar.bz2 4 .gz
file.0.tar.gz

PS:如果您喜欢这些功能或发现它们已用完,请参阅此帖子:)(并希望发表评论)。


0

基于杰克曼案例的答案非常好并且可移植,但是如果您只想在变量中使用文件名和扩展名,我会找到以下解决方案:

INPUTFILE="$1"
INPUTFILEEXT=$( echo -n "$INPUTFILE" | rev | cut -d'.' -f1 | rev )
INPUTFILEEXT=$( echo -n $INPUTFILEEXT | tr '[A-Z]' '[a-z]' ) # force lowercase extension
INPUTFILENAME="`echo -n \"$INPUTFILE\" | rev | cut -d'.' -f2- | rev`"

# fix for files with multiple extensions like "gbamidi-v1.0.tar.gz"
INPUTFILEEXT2=$( echo -n "$INPUTFILENAME" | rev | cut -d'.' -f1 | rev )
if [ "$INPUTFILEEXT2" = "tar" ]; then
    # concatenate the extension
    INPUTFILEEXT="$INPUTFILEEXT2.$INPUTFILEEXT"
    # update the filename
    INPUTFILENAME="`echo -n \"$INPUTFILENAME\" | rev | cut -d'.' -f2- | rev`"
fi

它仅适用于双扩展名,第一个扩展名必须为“ tar”。

但是您可以使用字符串长度测试来更改“ tar”测试行,并多次重复此修复程序。


-1

我用这个解决了

filename=`basename $filepath`
fileext=${filename##*.}
fileext2=${filename%.*}
fileext3=${fileext2##*.}
if [ "$fileext3" == "tar" ]; then
    fileext="tar."$fileext
fi

但这仅适用于已知的归档类型,仅在这种情况下 tar

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.