问题描述
我正在尝试从 bash 中的文件名列表中动态提取独特的模式。
输入文件名列表如下
FILE=open(path,'wb')
DATA.to_csv(FILE)
我想动态提取字符串
Exp1_ML_Rep1.txt,Exp1_ML_Rep2.txt,Exp1_ML_Rep3.txt
如图所示:
注意:输入模式每次都可以改变 例如另一个用例可能是
Rep1,Rep2,Rep3
Exp2_DT_10ng_55C_1_User1.png,Exp2_DT_10ng_55C_2_User1.png,Exp2_DT_10ng_55C_3_User1.png
如图所示:
在 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> func
与 awk
和 sort
结合使用
uniq
或
echo 'Exp1_ML_Rep1.txt,Exp1_ML_Rep2.txt,Exp1_ML_Rep3.txt' | awk -v RS='[_.,]' '1' | sort | uniq -u
结合 tr
和 sort
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 提出的解决方案:
- 找到公共前缀
- 找到常见的后缀
- 去掉常见的前缀/后缀,剩下的就是区别
假设:
- 文件名列表以逗号分隔的字符串形式提供
- 可变数量的文件名
- 逐个字符进行比较
- 无分隔符
- 假设一个由连续字符组成的“差异”,例如,在比较
aBcDe
和aXcYe
时,我们不认为c
是常见的,因此差异将报告为BcD
和XcY
一个使用 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}"
注意:
- 在这一点上有点冗长;或许可以精简一点...
- 可以修改代码以直接使用“普通”文件列表(例如,从
find
到awk
的管道输出);一个想法是只处理第一条记录 (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$$