动态提取bash中字符串列表中每个字符串唯一的模式

问题描述

我正在尝试从 bash 中的文件名列表中动态提取独特的模式。

输入文件名列表如下

FILE=open(path,'wb') DATA.to_csv(FILE)

我想动态提取字符串

Exp1_ML_Rep1.txt,Exp1_ML_Rep2.txt,Exp1_ML_Rep3.txt

如图所示:

Example1

注意:输入模式每次都可以改变 例如另一个用例可能是

Rep1,Rep2,Rep3

在这种情况下,我想提取

Exp2_DT_10ng_55C_1_User1.png,Exp2_DT_10ng_55C_2_User1.png,Exp2_DT_10ng_55C_3_User1.png

如图所示:

Example2

在 bash 中实现这一目标的最佳方法是什么?


按照评论中的建议,我尝试了以下操作:

1,2,3

declare -p string1 string2

declare -- string1="ER_Rep1" declare -- string2="ER_Rep2" 返回

diff  <(echo "$string1" ) <(echo "$string2")

我试图提取的是 Rep1、Rep2。

解决方法

您可以将 GNU Function<String,String> funcawksort 结合使用

uniq

echo 'Exp1_ML_Rep1.txt,Exp1_ML_Rep2.txt,Exp1_ML_Rep3.txt' | awk -v RS='[_.,]' '1' | sort | uniq -u 结合 trsort

uniq

产生输出

echo 'Exp1_ML_Rep1.txt,Exp1_ML_Rep3.txt' | tr '_.,' '\n' | sort | uniq -u
,

您可以考虑这个 awk 解决方案:

declare -- string1="ER_Rep1"
declare -- string2="ER_Rep2"

awk -F '[_.]+' '{for (i=1; i<=NF; ++i) ++fq[$i]}
END {for (w in fq) if (fq[w] == 1) print w}' <(echo "$string1" ) <(echo "$string2")

Rep1
Rep2

awk 解决方案使用 _. 作为字段分隔符,并将每个字段存储在关联数组 fq 中,其值为表示出现频率的数字那个词。

END 块中,我们迭代 fq 数组中的每个单词,并在频率等于 1 时打印该单词,指示该单词的唯一出现。

,

我确信有更好的编码方式,但我在这里所做的是针对任意数量的输入字符串的通用解决方案。

  • 找到下划线分隔的子串的最长公共前缀

    longestCommonPrefix() {
        local i prefix file found
        local -a pieces
        IFS=_ read -ra pieces <<<"$1"
        for ((i = ${#pieces[@]} - 1; i > 0; i--)); do
            prefix=$(IFS=_; echo "${pieces[*]:0:i}_")
            found=true
            for file in "${@:2}"; do
                if [[ $file != "$prefix"* ]]; then
                    found=false
                    break
                fi
            done
            if $found; then
                echo "$prefix"
                return
            fi
        done
    }
    
  • 找出最长的公共后缀(纯字符)

    longestCommonSuffix() {
        local i suffix file found
        for ((i = ${#1}; i > 0; i--)); do
            suffix=${1: -i}
            found=true
            for file in "${@:2}"; do
                if [[ $file != *"$suffix" ]]; then
                    found=false
                    break
                fi
            done
            if $found; then
                echo "$suffix"
                return
            fi
        done
    }
    
  • 把它们放在一起

    uniqueStrings() {
        local prefix=$(longestCommonPrefix "$@")
        set -- "${@/#"$prefix"/}"
        local suffix=$(longestCommonSuffix "$@")
        printf '%s\n' "${@/%"$suffix"/}"
    }
    

然后

$ uniqueStrings Exp1_ML_Rep1.txt Exp1_ML_Rep2.txt Exp1_ML_Rep3.txt
Rep1
Rep2
Rep3

$ uniqueStrings Exp2_DT_10ng_55C_1_User1.png Exp2_DT_10ng_55C_2_User1.png Exp2_DT_10ng_55C_3_User1.png
1
2
3

其他几个例子:

# nothing in common,should return the input strings
$ uniqueStrings foo bar baz
foo
bar
baz

$ uniqueStrings x_foo13 x_bar13 x_baz13 x_qux13
foo
bar
baz
qux

适用于 bash v3.2+

,

看一个类似于@glennjackman 提出的解决方案:

  • 找到公共前缀
  • 找到常见的后缀
  • 去掉常见的前缀/后缀,剩下的就是区别

假设:

  • 文件名列表以逗号分隔的字符串形式提供
  • 可变数量的文件名
  • 逐个字符进行比较
  • 无分隔符
  • 假设一个由连续字符组成的“差异”,例如,在比较 aBcDeaXcYe 时,我们不认为 c 是常见的,因此差异将报告为BcDXcY

一个使用 awk 的想法,它应该比 bash 级循环有一些性能改进:

awk '

# function to return an absolute value of a number

function abs(v) { return v < 0 ? -v : v }

# function to determine if each string has the same character at a given offset;
# return 0 if "no",return 1 if "yes"

function equal() {

    for ( i=1; i<=n; i++ ) {
        pos = offset <= 0 ? length(fname[i]) + offset : offset
        x   = substr(fname[i],pos,1)
        if ( i == 1 )    curr = x
        if ( x != curr ) return 0
    }
    return 1
}

# for now assume strings input using a here-string,and strings are delimited by a comma

FNR==1 { n=split($0,fname,",")
         exit                              # skip to END processing
       }

END {
    # twice through the outer "for" loop:
    #    op =  1 => prefix processing
    #    op = -1 => suffix processing
    # "op" will be used to increment/decrement our offset pointer to
    # perform the character-by-character comparison

    for ( op=1; op>=-1; op=op-2 ) {
        offset = op == 1 ? 1 : 0           # determine initial offset based on op (prefix vs suffix)

        # if all strings have the same character @ a given offset then update our pfx/sfx pointers

        while ( equal() && abs(offset) <= length(fname[1]) ) {
            if ( op == 1 ) pfx = offset
            else           sfx = offset

            offset = offset + op           # go to next offset
        }
    }

if ( pfx == "" ) pfx=0                     # if no common prefix,default to 0
if ( sfx == "" ) sfx=1                     # if no common suffix,default to 1

# use substr() and our pfx/sfx offsets to display the difference

for ( i=1; i<=n; i++ )
    print substr(fname[i],pfx+1,length(fname[i]) - pfx - 1 + sfx )

}' <<< "${in}"

注意:

  • 在这一点上有点冗长;或许可以精简一点...
  • 可以修改代码以直接使用“普通”文件列表(例如,从 findawk 的管道输出);一个想法是只处理第一条记录 (FNR==1) 并将 FILENAME 填充到数组中

测试结果:

# in='Exp1_ML_Rep1.txt,Exp1_ML_Rep3.txt'
1
2
3

# in='Exp2_DT_10ng_55C_1_User1.png,Exp2_DT_10ng_55C_2_User1.png,Exp2_DT_10ng_55C_3_User1.png'
1
2
3

# in='x_foo13,x_bar13,x_baz13,x_qux13'
foo
bar
baz
qux

# in='x_foo13,x_abcde23'
foo1
bar1
baz1
abcde2

# in='abcde.123,abcde.123,abcde.123'    # identical
                  # three
                  # blank
                  # lines

# in='abc,def,123456,xyz$$'             # nothing in common
abc
def
123456
xyz$$

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...