我正在尝试将 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.txt
或a-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"
如果没有这样的匹配(如果基本名称没有扩展名或者扩展名之前的根太短),则不会打印任何内容。根据您的要求,您可以调整正则表达式。此正则表达式强制执行约束:
- 它匹配最终扩展名之前的 3 个字符(最后一个点之后并包括最后一个点的部分)。这 3 个字符可以包含一个点。
- 扩展名可以为空(点除外)。
- 匹配的部分和扩展名必须是基本名称的一部分(最后一个斜杠后面的部分)。
在命令替换中使用它会出现删除太多尾随换行符的常见问题,这个问题也会影响 Stéphane 的答案。这两种情况都可以处理,但这里更容易一些:
lastpart=$(
perl -e 'print "$1x" if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"
)
lastpart=${lastpart%x} # allow for possible trailing newline