提取基本文件名最后 3 个字符(减去后缀)的最短方法

提取基本文件名最后 3 个字符(减去后缀)的最短方法

我正在尝试将 sh 脚本中的变量设置为文件基本名称的最后 3 个字符(基本名称的意思是没有路径没有后缀)。我已经成功地做到了这一点,但是,纯粹出于好奇,我想知道是否有一个更短的单一命令我可以使用。本来我有一个单行awk,但它相当长。目前我有这个两行脚本(假设完整的文件名位于$1):

filebase=`basename "$1"`
lastpart=`echo -n ${filebase%.*} | tail -c3`

例如,“/path/to/somefile.txt”最终以“伊莱”$lastpart

我可以以某种方式组合basename和位以将后缀剥离为单个命令,并且有没有办法在tail不使用管道的情况下将其发送到(或我可以使用的其他东西)?后缀未知,因此我无法将其作为 的参数basename

主要目标实际上并不是尽可能短,而是尽可能一目了然。所有这一切的实际背景是这个关于超级用户的问题,我试图给出一个相当简单的答案。

答案1

var=123456
echo "${var#"${var%???}"}"

###OUTPUT###

456

首先删除 的最后三个字符,然后从该删除的结果$var中删除- 它返回 的最后三个字符。以下是一些更具体的示例,旨在演示如何执行此类操作:$var$var

touch file.txt
path=${PWD}/file.txt
echo "$path"

/tmp/file.txt

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

file
txt
file.txt
/tmp/file.txt

您不必通过这么多命令来分散这一切。你可以压缩这个:

{
    base=${path##*/} exten= 
    printf %s\\n "${base%.*}" "${exten:=${base#"${base%???}"}}" "$base" "$path"
    echo "$exten"
}

file 
txt 
file.txt 
/tmp/file.txt
txt

$IFS与ting shell 参数结合set也可以是解析和钻取 shell 变量的非常有效的手段:

(IFS=. ; set -f; set -- ${path##*/}; printf %s "${1#"${1%???}"}")

/这将只为您提供紧接在 中最后一个句点之后的第一个句点之前的三个字符$path。如果您只想检索最后一个.字符之前的前三个字符$path .(例如,如果文件名中可能有多个):

(IFS=.; set -f; set -- ${path##*/}; ${3+shift $(($#-2))}; printf %s "${1#"${1%???}"}")

在这两种情况下你都可以这样做:

newvar=$(IFS...)

和...

(IFS...;printf %s "$2")

...将打印以下内容.

如果您不介意使用外部程序,您可以执行以下操作:

printf %s "${path##*/}" | sed 's/.*\(...\)\..*/\1/'

\n如果文件名中可能存在ewline 字符(不适用于本机 shell 解决方案 - 无论如何它们都会处理这个问题):

printf %s "${path##*/}" | sed 'H;$!d;g;s/.*\(...\)\..*/\1/'

答案2

这是一个典型的工作expr

$ file=/path/to/abcdef.txt
$ expr "/$file" : '.*\([^/.]\{3\}\)\.[^/.]*$'
def

如果您知道您的文件名具有预期的格式(包含一个且仅一个点,并且点之前至少有 3 个字符),则可以简化为:

expr "/$file" : '.*\(.\{3\}\)\.'

请注意,如果没有匹配,则退出状态将为非零,而且如果匹配部分是解析为 0 的数字。(如 fora000.txta-00.txt

zsh

file=/path/to/abcdef.txt
lastpart=${${file:t:r}[-3,-1]}

:t为了尾巴(基本名称),:r对于休息(删除扩展名))。

答案3

如果你可以使用perl

lastpart=$(
    perl -e 'print substr((split(/\.[^.]*$/,shift))[0], -3, 3)
            ' -- "$(basename -- "$1")"
)

答案4

如果 perl 可用,我发现它比其他解决方案更具可读性,特别是因为它的正则表达式语言更具表现力,并且它具有修饰符/x,可以编写更清晰的正则表达式:

perl -e 'print $1 if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"

如果没有这样的匹配(如果基本名称没有扩展名或者扩展名之前的根太短),则不会打印任何内容。根据您的要求,您可以调整正则表达式。此正则表达式强制执行约束:

  1. 它匹配最终扩展名之前的 3 个字符(最后一个点之后并包括最后一个点的部分)。这 3 个字符可以包含一个点。
  2. 扩展名可以为空(点除外)。
  3. 匹配的部分和扩展名必须是基本名称的一部分(最后一个斜杠后面的部分)。

在命令替换中使用它会出现删除太多尾随换行符的常见问题,这个问题也会影响 Stéphane 的答案。这两种情况都可以处理,但这里更容易一些:

lastpart=$(
  perl -e 'print "$1x" if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"
)
lastpart=${lastpart%x}  # allow for possible trailing newline

相关内容