提问人:cybersnow1989 提问时间:11/11/2023 最后编辑:TylerHcybersnow1989 更新时间:11/23/2023 访问量:175
尝试使用 bash 从文件名中提取子字符串和版本号
Trying to extract a substring and version number from a filename using bash
问:
我目前正在尝试使用 bash 从文件名中提取子字符串和版本号。
文件名有两种格式:
example-substring-1.1.0.tgz
example-substring-1.1.0-branch-name.tgz
对于第一个场景,我能够使用 sed 提取版本号,如下所示:
echo example-substring-1.1.0.tgz | sed "s/.*-\(.*\)\.[a-zA-Z0-9]\{3\}$/\1/"
但是,这不适用于第二种情况。
最终,我想创建一个脚本,该脚本将第一个子字符串和版本存储在关联数组中,如下所示。
example_array["example-substring"]="1.1.0"
example_array["example-substring"]="1.1.0-branch-name"
然而,事实证明这很棘手,因为我似乎找不到一种适用于这两种情况的好方法。对于版本包含分支名称的场景,我无法事先知道分支名称将包含多少个单词。
我认为可变扩展可能是要走的路,但无法让它输出我想要的东西。
答:
如果您愿意使用 而不是 ,那么 lookaheads 和 lookbehinds 将允许您定义模式来提取您关心的内容。grep
sed
考虑以下模式:这将匹配后面跟着 的任何内容。 标记条件展望,该表达式必须与下一个字符匹配,但被排除在模式的最终匹配之外。
与您的示例一起使用时:.+(?=-\d+\.\d+\.\d+)
-<numbers>.<numbers>.<numbers>
?=
$ echo example-substring-1.1.0.tgz | grep -Po '.+(?=-\d+\.\d+\.\d+)'
example-substring
$ echo example-substring-1.1.0-branch-name.tgz | grep -Po '.+(?=-\d+\.\d+\.\d+)'
example-substring
(该标志启用 PCRE2,并且该标志仅打印匹配项)P
o
还要考虑模式:它使用后视来断言,在模式之前,有一个 ,并使用前瞻来断言模式以 结尾。
与您的示例一起使用时:(?<=-)\d+\.\d+\.\d+.*(?=\.tgz$)
-
.tgz
echo 'example-substring-1.1.0.tgz' | grep -Po '(?<=-)\d+\.\d+\.\d+.*(?=\.tgz$)'
1.1.0
$ echo 'example-substring-1.1.0-branch-name.tgz' | grep -Po '(?<=-)\d+\.\d+\.\d+.*(?=\.tgz$)'
1.1.0-branch-name
评论
grep -Po
'(?<=-)[\d.]+-.*(?=\.tgz$)'
grep
grep -P
perl
grep -P
perl -lne 's/regex/print $&/e'
grep -Po 'regex'
perl -lne 'print $1 if /regex/'
grep -Po regex
perl -lne 's/(regex1).*(regex2)/print $1\n$2/'
grep -Po
为了能够真正测试这一点,我们需要包含更多有问题的情况的示例输入,例如,一个看起来像分支名称中出现的版本号的字符串:-1.2.3
$ cat file
example-substring-foo-1.1.0.tgz
example-substring-bar-1.1.0-branch-name.tgz
example-substring-rainy-1.1.0-branch-1.2.3.tgz
通常我会在 sed 或 awk 中执行模式匹配部分,例如使用任何 awk:
$ awk 'match($0,/-([0-9].*)\.[^.]+$/) {
printf "\"%s\" \"%s\"\n", substr($0,1,RSTART-1), substr($0,RSTART+1)
}' file
"example-substring-foo" "1.1.0.tgz"
"example-substring-bar" "1.1.0-branch-name.tgz"
"example-substring-rainy" "1.1.0-branch-1.2.3.tgz"
而不是 shell 循环,但因为您无论如何都想用结果填充 shell 数组:
$ cat tst.sh
#!/usr/bin/env bash
declare -A example_array
while IFS= read -r ver; do
if [[ $ver =~ -([0-9].*)\.[^.]+$ ]]; then
example_array["${ver::-${#BASH_REMATCH[0]}}"]="${BASH_REMATCH[1]}"
fi
done < "$@"
for idx in "${!example_array[@]}"; do
printf 'example_array["%s"]="%s"\n' "$idx" "${example_array[$idx]}"
done
$ ./tst.sh file
example_array["example-substring-rainy"]="1.1.0-branch-1.2.3"
example_array["example-substring-bar"]="1.1.0-branch-name"
example_array["example-substring-foo"]="1.1.0"
这可能对你有用 (GNU sed):
sed -E 's/^([^-]+-)+([0-9.]+).*\..*/\2/' file
匹配一个或多个单词以 's 分隔的文件名,后跟以 's 分隔的数字,然后以 's 分隔的扩展名结尾,并返回以 's 分隔的数字。-
.
.
.
也许可以只使用 Bash 的内置模式匹配来做你需要的事情。以下 Shellcheck-clean 代码演示了这个想法:
#! /bin/bash -p
shopt -s extglob
files=( example-substring-1.1.0.tgz example-substring2-1.1.0-branch-name.tgz )
declare -A example_array
for f in "${files[@]}"; do
base=${f%.*} # remove suffix
substring=${base%%-+([0-9]).*}
example_array["$substring"]=${base#"$substring-"}
done
declare -p example_array
这将输出:
declare -A example_array=([example-substring2]="1.1.0-branch-name" [example-substring]="1.1.0" )
shopt -s extglob
启用“扩展通配”(包括类似 的模式)。请参阅 glob - Greg's Wiki 中的 extglob 部分。+([0-9])
- 有关、和 的解释,请参阅删除字符串的一部分 (BashFAQ/100 (How do I do string manipulation in bash?)) 。
${f%.*}
${base%%-+([0-9]).*}
${base#"$substring-"}
- 通常,以明确的方式打印变量的值。它避免了在打印这两种数组的值时出现的循环和陷阱。
declare -p var
使用 Perl
echo "example-substring-1.1.0-branch-name.tgz" |
perl -wne'print join " ", /(.+)\-([0-9]+\.[0-9]+\.[0-9]+.*)\.tgz/'
打印两个单词
example-substring 1.1.0-branch-name
因此,这是它对 shell 脚本的返回,我推测这将从中调用,然后可以在 shell 脚本中形成所需的结构。†测试时没有分支名称,并且有输入字符串的其他一些变体。
由于 can 也可以包含数字(为什么不呢?),分支名称也可以包含 (why not?),因此正则表达式模式没有限制,并且前导部分和(可能的)尾随部分都简单地用 和 匹配。example-substring
.+
.*
但是,我们需要更具体的版本号,我使用了一个假设,即它始终由三个用点分隔的数字组成。我还假设字符串的固定其余部分,文件扩展名 .如果需要,可以稍微放松一下。.tgz
†可以直接将列表(键值、键值等)读入关联数组
#!/bin/bash
eval declare -A ver=( $(
echo "example-substring-1.1.0-branch-name.tgz" |
perl -wnE'say join " ", /(.+)\-([0-9]+\.[0-9]+\.[0-9]+.*)\.tgz/' ))
echo ${ver["example-substring"]}
或者,先分配给变量可能更合适
str="example-substring-1.1.0-branch-name.tgz"
read -r str val <<< $(
perl -wE'say join " ", $ARGV[0] =~ /(.+)\-([0-9]+\.[0-9]+\.[0-9]+.+)\.tgz/'
-- "$str" )
ver[$str]=$val
甚至只是使用位置参数
set -- $(
perl -wE'say join " ", $ARGV[0] =~ /(.+)\-([0-9]+\.[0-9]+\.[0-9]+.+)\.tgz/'
-- "$str" )
ver[$1]=$2
当然,还有其他方法可以将参数传递给 Perl 脚本或命令行程序(“单行”),以及其他方法可以将其输出到 bash 中。
让我知道这个Perl代码是否需要注释。
评论
(.*)
([0-9.]*)
sed -r
*.tgz
*.tar.gz
-<digits>
example-substring-1.1.0-branch-1.2.3.tgz