Bash 获取数组中基于子字符串的唯一文件名列表

Bash 获取数组中基于子字符串的唯一文件名列表,bash,shell,Bash,Shell,我有一个目录my_dir,其中的文件名如下: a_v5.json a_v5.mapping.json a_v5.settings.json f_v39.json f_v39.mapping.json f_v39.settings.json f_v40.json f_v40.mapping.json f_v40.settings.json c_v1.json c_v1.mapping.json c_v1.settings.json 我正在寻找一种在bash中获取数组[a_v5,f_v40,c_v

我有一个目录
my_dir
,其中的文件名如下:

a_v5.json
a_v5.mapping.json
a_v5.settings.json
f_v39.json
f_v39.mapping.json
f_v39.settings.json
f_v40.json
f_v40.mapping.json
f_v40.settings.json
c_v1.json
c_v1.mapping.json
c_v1.settings.json

我正在寻找一种在bash中获取数组
[a_v5,f_v40,c_v1]
的方法。在这里,我需要的是具有最新版本号的文件名数组


尝试了以下操作:
ls*.json | find-类型f-exec basename“{}”\;|切割-d-f1
,但它返回的结果中包含扩展名不为.json的文件。

这是一种可能的实现方法:

arr=( $(  { for name in $( ls {f,n,m}*.txt );  do echo ${name:0:1} ; done; } | sort | uniq ) )
输出:
问候

数组和bash字符串解析

declare -A tmp=()
for f in $SOURCE_DIR/*.json
do f=${f##*/}        # strip path
   tmp[${f%%.*}]=1   # strip extraneous data after . in filename
done
declare -a c=( $( printf "%s\n" "${!tmp[@]}" | cut -c 1 | sort -u ) ) # get just the first chars
declare -a lst=( $( for f in "${c[@]}"
                    do printf "%s\n" "${!tmp[@]}" |
                         grep "^${f}_"            |
                         sort -n                  |
                         tail -1; done ) )
echo "[ ${lst[@]} ]"
[ a_v5 c_v1 f_v40 ]
或者,如果你愿意

declare -a arr=( $(
  for f in $SOURCE_DIR/*.json
  do d=${f%/*}         # get dir path
     f=${f##*/}        # strip path
     g=${f:0:2}        # get leading str
     ( cd $d && printf "%s\n" ${g}*.json | 
       sort -n | sed -n '$ { s/[.].*//; p; }' )
  done | sort -u ) )
echo "[ ${arr[@]} ]"
[ a_v5 c_v1 f_v40 ]
AWK解决方案 这不是一个优雅的解决方案。。。我对awk的了解有限。 你会发现这很实用

我已经更新了它,删除了@socowi建议的过时uniq。 我还包括了@socowi建议的printf版本

ls *.json | cut -d. -f1 | sort -rn |  awk -v last="xx" '$1 !~ last{ print $1; last=substr($1,1,3)  }'


旧的理解

查找具有名称匹配模式的文件

现在选择第二个字段,因为您的结果可能类似于/

find . -type f -iname "*.json" | cut -d. -f2
要获得唯一的标题

find . -type f -iname "*.json" | cut -d. -f2 | sort | uniq

如果文件名不包含空格和特殊符号,如
*
,则可以使用以下命令:

array=($(
    find . -type f -iname \*.json |
    sed -E 's|(.*/)*(.*_v)([0-9]+)\..*|\2 \3|' |
    sort -Vr | sort -uk1,1 | tr -d ' '
))
它既丑陋又不安全。下面的解决方案较长,但可以处理所有文件名,甚至是包含换行符的文件名

maxversions() {
    find -type f -iname \*.json -print0 |
    gawk 'BEGIN { RS = "\0"; ORS = "\0" }
        match($0, /(.*\/)*(.*_v)([0-9]+)\..*/, group) {
            prefix = group[2];
            version = group[3];
            if (version > maxversion[prefix])
                maxversion[prefix] = version
        }
        END {
            for (prefix in maxversion)
                print prefix maxversion[prefix]
        }'
}
mapfile -d '' array < <(maxversions)
maxversions(){
find-type f-iname\*.json-print0|
gawk'BEGIN{RS=“\0”;ORS=“\0”}
匹配($0,/(.\/)*(.*U v)([0-9]+)\.*/,组){
前缀=组[2];
版本=组[3];
如果(版本>最大版本[前缀])
maxversion[前缀]=版本
}
结束{
for(maxversion中的前缀)
打印前缀maxversion[前缀]
}'
}


mapfile-d''数组<@anubhava更新了OP。关于您的命令:
ls | find
与刚才的
find
相同<代码>查找
不读取标准数据。要仅列出
json
文件,请使用
find-键入f-iname\*.json
@Socowi根据编号获取唯一的文件名如何?对此有什么建议吗?
[a_v5,f_v40,c_v1]
是预期的输出。“具有最新版本号的文件名数组”正是我的意思。为我的错误解释道歉。你可以只写
{f,n,m}而不是
$(ls{f,n,m}*.txt
*.txt
。后者不仅更短,而且更安全、更快。还有,为什么
txt
?OP示例中的所有文件都以
json
结尾。我没有投票,因为这并不能回答问题。即使这个解决方案是OP要求的,它也只适用于单字母基名称,如
a_v1.json
,但不适用于
abc_v1.json
。如果您能修复或删除此答案,我将不胜感激。我不知道这将如何解决我的问题。我的糟糕。忘记指定json文件。这将创建您所需的数组,尽管它将同时具有
f_v39
f_v40
。好的。我只需要具有最大/la的文件名测试其中的版本号。你有什么办法过滤掉它吗?误解。给我一点。好的。用你的代码,我得到
语法错误:预期的操作数
。你也得到了吗?这并没有给我预期的输出。相反,它给了我
设置映射json…
@Akshaymaldure你的环境可能与我的不同。如果您将获得设置、映射和json,您可能确实需要-f1。即使如此(
find.-type f-iname”*.json“| cut-d.-f1 | sort | uniq
),我没有得到预期的输出,因为所有的文件名在考虑版本时总是唯一的。@AkshayMaldhure另一种方法是根本不使用find.ls*.json | cut-d.-f1 | sort | uniqs一些改进:用
printf%s\\n*.json
替换
ls*.json
,我认为
uniq
是不必要的nymore.
substr($1,1,3)
只适用于格式为
a#v#
的文件名,其中
a
正好是一个字母。这样,我就得到了
sed:1:“s/|(.*/)*(.*u v)([0-9]+)…”:RE错误:空(sub)expression
。顺便说一句,我的文件名中没有空格。我的不好。这就是我在不先检查的情况下进行一些更改所得到的结果:(.您能再试一次吗?这很有效!(不过需要做一些小的修改,即删除数组赋值右侧的前括号和后括号).最后一件事:当文件名中有多个下划线时,您的解决方案应该有效吗?我想是的。但请不要在生产中使用它。
awk
命令应该很快完成,并且在任何情况下都应该有效。@akshaymaldure多个下划线没有问题。两种解决方案都应该正确处理它们。如果您有多个
\u v12。
然后将使用文件名中第一个点之前的版本。例如:在
some/path\u v1.dir/file\u v2.\u v3.json
中,将使用版本
v2
v1
v3
被忽略。
array=($(
    find . -type f -iname \*.json |
    sed -E 's|(.*/)*(.*_v)([0-9]+)\..*|\2 \3|' |
    sort -Vr | sort -uk1,1 | tr -d ' '
))
maxversions() {
    find -type f -iname \*.json -print0 |
    gawk 'BEGIN { RS = "\0"; ORS = "\0" }
        match($0, /(.*\/)*(.*_v)([0-9]+)\..*/, group) {
            prefix = group[2];
            version = group[3];
            if (version > maxversion[prefix])
                maxversion[prefix] = version
        }
        END {
            for (prefix in maxversion)
                print prefix maxversion[prefix]
        }'
}
mapfile -d '' array < <(maxversions)