在 linux 中使用 awk 或 sed 解析简单字符串
Parsing simple string with awk or sed in linux
原始字符串:
A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/
目录的深度会有所不同,但 /trunk 部分将始终保持不变。
/trunk 前面的单个字符是该行的指示符。
期望的输出:
A /trunk/apple
B /trunk/apple
Z /trunk/orange
Q /trunk/melon/juice/venti/straw
*** 编辑
很抱歉,我犯了一个错误,在原始字符串的每个路径末尾添加了一个斜杠,这使得输出变得混乱。原始字符串没有大写字母前的斜杠,但我保留它。
我的尝试:
echo $str1 | sed 's/\(.\/trunk\)/\n/g'
我觉得它应该有用,但没有用。
要处理复杂的样本输入,比如在一行中可能有 N 个 /
和值,请尝试以下操作。
awk '
{
gsub(/[^/]*\/trunk/,OFS"&")
sub(/^ /,"")
sub(/\//,OFS"&")
gsub(/ +[^/]*\/trunk\/[^[:space:]]+/,"\n&")
sub(/\n/,OFS)
gsub(/\n /,ORS)
gsub(/\/trunk/,OFS"&")
sub(/[[:space:]]+/,OFS)
}
1
' Input_file
说明:为以上添加详细说明。
awk ' ##Starting awk program from here.
{
gsub(/[^/]*\/trunk/,OFS"&") ##Globally substituting everything from / to till next / followed by trunk/ with space and matched value.
sub(/^ /,"") ##Substituting starting space with NULL here.
sub(/\//,OFS"&") ##Substituting first / with space / here.
gsub(/ +[^/]*\/trunk\/[^[:space:]]+/,"\n&") ##Globally substituting spaces followed by everything till / trunk till space comes with new line and matched values.
sub(/\n/,OFS) ##Substituting new line with space.
gsub(/\n /,ORS) ##Globally substituting new line space with ORS.
gsub(/\/trunk/,OFS"&") ##Globally substituting /trunk with OFS and matched value.
sub(/[[:space:]]+/,OFS) ##Substituting spaces with OFS here.
}
1 ##Printing edited/non-edited line here.
' Input_file ##Mentioning Input_file name here.
使用您展示的示例,请尝试以下 awk
代码。
awk '{gsub(/\/trunk/,OFS "&");gsub(/trunk\/[^/]*\//,"&\n")} 1' Input_file
在 awk
您可以尝试这个解决方案。它处理当下一个字符为大写时删除正斜杠的特殊要求。不会赢得设计奖但作品。
$ echo "A/trunk/apple/B/trunk/apple/Z/trunk/orange" |
awk -F '' '{ x=""; for(i=1;i<=NF;i++){
if($(i+1)~/[A-Z]/&&$i=="/"){$i=""};
if($i~/[A-Z]/){ printf x""$i" "}
else{ x="\n"; printf $i } }; print "" }'
A /trunk/apple
B /trunk/apple
Z /trunk/orange
也适用于 n 个单词。实际上适用于任何遵循给定模式的东西。
$ echo "A/fruits/apple/mango/B/anything/apple/pear/banana/Z/ball/orange/anything" |
awk -F '' '{ x=""; for(i=1;i<=NF;i++){
if($(i+1)~/[A-Z]/&&$i=="/"){$i=""};
if($i~/[A-Z]/){ printf x""$i" "}
else{ x="\n"; printf $i } }; print "" }'
A /fruits/apple/mango
B /anything/apple/pear/banana
Z /ball/orange/anything
这可能适合您 (GNU sed):
sed 's/[^/]*/& /;s/\//\n/3;P;D' file
用 space.
将第一个单词与第一个 /
分开
用换行符替换第三个 /
。
Print/delete 第一行并重复。
如果第一个单词有属性表示它只有一个字符长:
sed 's/./& /;s#/\(./\)#\n#;P;D' file
或者,如果第一个单词的 属性 以大写字符开头:
sed 's/[[:upper:]][^/]*/& /;s#/\([[:upper:][^/]*/\)#\n#;P;D' file
或者如果第一个单词有 属性,后面跟着 /trunk/
:
sed -E 's#([^/]*)(/trunk/)#\n #g;s/.//' file
使用 gnu awk
您可以使用 FPAT 使用模式设置每个字段的内容。
循环字段时,将第一个 /
替换为 /
str1="A/trunk/apple/B/trunk/apple/Z/trunk/orange"
echo $str1 | awk -v FPAT='[^/]+/trunk/[^/]+' '{
for(i=1;i<=NF;i++) {
sub("/", " /", $i)
print $i
}
}'
模式匹配
[^/]+
匹配除 /
之外的任何字符
/trunk/[^/]+
匹配 /trunk/
和除 /
之外的任何字符
输出
A /trunk/apple
B /trunk/apple
Z /trunk/orange
更新问题后FPAT可以使用的其他模式:
匹配单词边界 \<
和大写字符 A-Z 并在 /trunk
之后重复 /
和小写字符
FPAT='\<[A-Z]/trunk(/[a-z]+)*'
如果/trunk
之后的目录字符串长度至少为2个字符:
FPAT='\<[A-Z]/trunk(/[^/]{2,})*'
如果没有由单个大写字符 A-Z 组成的单独文件夹
FPAT='\<[A-Z]/trunk(/([^/A-Z][^/]*|[^/]{2,}))*'
输出
A /trunk/apple
B /trunk/apple
Z /trunk/orange/citrus
Q /trunk/melon/juice/venti/straw
使用 GNU awk 进行多字符 RS 和 RT:
$ awk -v RS='([^/]+/){2}[^/\n]+' 'RT{sub("/",OFS,RT); print RT}' file
A trunk/apple
B trunk/apple
Z trunk/orange
我将 RS
设置为正则表达式,描述您要匹配的每个字符串,即重复 2 次非 /
后跟 /
,然后是最后一个字符串非 /
s(以及输入行最后一个字符串的非换行符)。 RT
自动设置为每个匹配的字符串,所以我只需将第一个 /
更改为空白并打印结果。
如果每条路径并不总是 3 层深但总是以 something/trunk/
开头,例如:
$ cat file
A/trunk/apple/banana/B/trunk/apple/Z/trunk/orange
然后:
$ awk -v RS='[^/]+/trunk/' 'RT{if (NR>1) print pfx [=12=]; pfx=gensub("/"," ",1,RT)} END{printf "%s%s", pfx, [=12=]}' file
A trunk/apple/banana/
B trunk/apple/
Z trunk/orange
更新
使用您的新数据文件:
$ cat file
A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/
这个 GNU awk
解决方案:
awk '
{
sub(/[/]$/,"")
gsub(/[[:upper:]]{1}/,"& ")
print gensub(/([/])([[:upper:]])/,"\n\2","g")
}' file
A /trunk/apple
B /trunk/apple
Z /trunk/orange/citrus
Q /trunk/melon/juice/venti/straw
假设您的数据始终采用作为单个字符串提供的格式,您可以试试这个 sed
。
$ sed 's/$/\//;s|\([A-Z]\)\([a-z/]*\)/\([a-z]*\?\)| \n|g' input_file
$ echo "A/trunk/apple/pine/skunk/B/trunk/runk/bunk/apple/Z/trunk/orange/T/fruits/apple/mango/P/anything/apple/pear/banana/L/ball/orange/anything/S/fruits/apple/mango/B/rupert/cream/travel/scout/H/tall/mountains/pottery/barnes" | sed 's/$/\//;s|\([A-Z]\)\([a-z/]*\)/\([a-z]*\?\)| \n|g'
A /trunk/apple/pine/skunk
B /trunk/runk/bunk/apple
Z /trunk/orange
T /fruits/apple/mango
P /anything/apple/pear/banana
L /ball/orange/anything
S /fruits/apple/mango
B /rupert/cream/travel/scout
H /tall/mountains/pottery/barnes
Perl 的一些乐趣,您可以在其中使用非消耗性正则表达式自动拆分为 @F
数组,然后根据需要打印。
perl -lanF'/(?=.{1,2}trunk)/' -e 'print "$F[2*$_] $F[2*$_+1]" for 0..$#F/2'
第 1 步:拆分
perl -lanF/(?=.{1,2}trunk)/'
- 这将获取输入流,并在遇到模式
.{1,2}trunk
时分割每一行
- 因为我们想保留
trunk
和前面的 1 或 2 个字符,所以我们将拆分模式包装在 (?=)
中以实现非消耗性前瞻性
- 这样分解:
$ echo A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/ | perl -lanF'/(?=.{1,2}trunk)/' -e 'print join " ", @F'
A /trunk/apple/ B /trunk/apple/ Z /trunk/orange/citrus/ Q /trunk/melon/juice/venti/straw/
第 2 步:格式化输出:
@F
数组包含我们要按顺序打印的对,因此我们将迭代数组索引的一半,并一次打印 2 个:
print "$F[2*$_] $F[2*$_+1]" for 0..$#F/2
--> 双倍迭代器,并打印对
- 使用
perl -l
意味着每个print
在末尾都有一个隐含的\n
- 结果:
$ echo A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/ | perl -lanF'/(?=.{1,2}trunk)/' -e 'print "$F[2*$_] $F[2*$_+1]" for 0..$#F/2'
A /trunk/apple/
B /trunk/apple/
Z /trunk/orange/citrus/
Q /trunk/melon/juice/venti/straw/
尾注:Perl 混淆无效。
- perl 中的任何数组都可以转换为散列,格式为 (key,val,key,val....)
- 所以
%F=@F; print "$_ $F{$_}" for keys %F
看起来会很圆滑
- 但是你失去了秩序:
$ echo A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/ | perl -lanF'/(?=.{1,2}trunk)/' -e '%F=@F; print "$_ $F{$_}" for keys %F'
Z /trunk/orange/citrus/
A /trunk/apple/
Q /trunk/melon/juice/venti/straw/
B /trunk/apple/
使用 GNU sed:
$ str="A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/"
$ sed -E 's|/?(.)(/trunk/)|\n |g;s|/$||' <<< "$str"
A /trunk/apple
B /trunk/apple
Z /trunk/orange/citrus
Q /trunk/melon/juice/venti/straw
注意第一个空输出行。如果不希望我们可以单独处理第一行输出:
$ sed -E 's|(.)| |;s|/(.)(/trunk/)|\n |g;s|/$||' <<< "$str"
A /trunk/apple
B /trunk/apple
Z /trunk/orange/citrus
Q /trunk/melon/juice/venti/straw
原始字符串:
A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/
目录的深度会有所不同,但 /trunk 部分将始终保持不变。 /trunk 前面的单个字符是该行的指示符。
期望的输出:
A /trunk/apple
B /trunk/apple
Z /trunk/orange
Q /trunk/melon/juice/venti/straw
*** 编辑
很抱歉,我犯了一个错误,在原始字符串的每个路径末尾添加了一个斜杠,这使得输出变得混乱。原始字符串没有大写字母前的斜杠,但我保留它。
我的尝试:
echo $str1 | sed 's/\(.\/trunk\)/\n/g'
我觉得它应该有用,但没有用。
要处理复杂的样本输入,比如在一行中可能有 N 个 /
和值,请尝试以下操作。
awk '
{
gsub(/[^/]*\/trunk/,OFS"&")
sub(/^ /,"")
sub(/\//,OFS"&")
gsub(/ +[^/]*\/trunk\/[^[:space:]]+/,"\n&")
sub(/\n/,OFS)
gsub(/\n /,ORS)
gsub(/\/trunk/,OFS"&")
sub(/[[:space:]]+/,OFS)
}
1
' Input_file
说明:为以上添加详细说明。
awk ' ##Starting awk program from here.
{
gsub(/[^/]*\/trunk/,OFS"&") ##Globally substituting everything from / to till next / followed by trunk/ with space and matched value.
sub(/^ /,"") ##Substituting starting space with NULL here.
sub(/\//,OFS"&") ##Substituting first / with space / here.
gsub(/ +[^/]*\/trunk\/[^[:space:]]+/,"\n&") ##Globally substituting spaces followed by everything till / trunk till space comes with new line and matched values.
sub(/\n/,OFS) ##Substituting new line with space.
gsub(/\n /,ORS) ##Globally substituting new line space with ORS.
gsub(/\/trunk/,OFS"&") ##Globally substituting /trunk with OFS and matched value.
sub(/[[:space:]]+/,OFS) ##Substituting spaces with OFS here.
}
1 ##Printing edited/non-edited line here.
' Input_file ##Mentioning Input_file name here.
使用您展示的示例,请尝试以下 awk
代码。
awk '{gsub(/\/trunk/,OFS "&");gsub(/trunk\/[^/]*\//,"&\n")} 1' Input_file
在 awk
您可以尝试这个解决方案。它处理当下一个字符为大写时删除正斜杠的特殊要求。不会赢得设计奖但作品。
$ echo "A/trunk/apple/B/trunk/apple/Z/trunk/orange" |
awk -F '' '{ x=""; for(i=1;i<=NF;i++){
if($(i+1)~/[A-Z]/&&$i=="/"){$i=""};
if($i~/[A-Z]/){ printf x""$i" "}
else{ x="\n"; printf $i } }; print "" }'
A /trunk/apple
B /trunk/apple
Z /trunk/orange
也适用于 n 个单词。实际上适用于任何遵循给定模式的东西。
$ echo "A/fruits/apple/mango/B/anything/apple/pear/banana/Z/ball/orange/anything" |
awk -F '' '{ x=""; for(i=1;i<=NF;i++){
if($(i+1)~/[A-Z]/&&$i=="/"){$i=""};
if($i~/[A-Z]/){ printf x""$i" "}
else{ x="\n"; printf $i } }; print "" }'
A /fruits/apple/mango
B /anything/apple/pear/banana
Z /ball/orange/anything
这可能适合您 (GNU sed):
sed 's/[^/]*/& /;s/\//\n/3;P;D' file
用 space.
将第一个单词与第一个/
分开
用换行符替换第三个 /
。
Print/delete 第一行并重复。
如果第一个单词有属性表示它只有一个字符长:
sed 's/./& /;s#/\(./\)#\n#;P;D' file
或者,如果第一个单词的 属性 以大写字符开头:
sed 's/[[:upper:]][^/]*/& /;s#/\([[:upper:][^/]*/\)#\n#;P;D' file
或者如果第一个单词有 属性,后面跟着 /trunk/
:
sed -E 's#([^/]*)(/trunk/)#\n #g;s/.//' file
使用 gnu awk
您可以使用 FPAT 使用模式设置每个字段的内容。
循环字段时,将第一个 /
替换为 /
str1="A/trunk/apple/B/trunk/apple/Z/trunk/orange"
echo $str1 | awk -v FPAT='[^/]+/trunk/[^/]+' '{
for(i=1;i<=NF;i++) {
sub("/", " /", $i)
print $i
}
}'
模式匹配
[^/]+
匹配除/
之外的任何字符
/trunk/[^/]+
匹配/trunk/
和除/
之外的任何字符
输出
A /trunk/apple
B /trunk/apple
Z /trunk/orange
更新问题后FPAT可以使用的其他模式:
匹配单词边界 \<
和大写字符 A-Z 并在 /trunk
之后重复 /
和小写字符
FPAT='\<[A-Z]/trunk(/[a-z]+)*'
如果/trunk
之后的目录字符串长度至少为2个字符:
FPAT='\<[A-Z]/trunk(/[^/]{2,})*'
如果没有由单个大写字符 A-Z 组成的单独文件夹
FPAT='\<[A-Z]/trunk(/([^/A-Z][^/]*|[^/]{2,}))*'
输出
A /trunk/apple
B /trunk/apple
Z /trunk/orange/citrus
Q /trunk/melon/juice/venti/straw
使用 GNU awk 进行多字符 RS 和 RT:
$ awk -v RS='([^/]+/){2}[^/\n]+' 'RT{sub("/",OFS,RT); print RT}' file
A trunk/apple
B trunk/apple
Z trunk/orange
我将 RS
设置为正则表达式,描述您要匹配的每个字符串,即重复 2 次非 /
后跟 /
,然后是最后一个字符串非 /
s(以及输入行最后一个字符串的非换行符)。 RT
自动设置为每个匹配的字符串,所以我只需将第一个 /
更改为空白并打印结果。
如果每条路径并不总是 3 层深但总是以 something/trunk/
开头,例如:
$ cat file
A/trunk/apple/banana/B/trunk/apple/Z/trunk/orange
然后:
$ awk -v RS='[^/]+/trunk/' 'RT{if (NR>1) print pfx [=12=]; pfx=gensub("/"," ",1,RT)} END{printf "%s%s", pfx, [=12=]}' file
A trunk/apple/banana/
B trunk/apple/
Z trunk/orange
更新
使用您的新数据文件:
$ cat file
A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/
这个 GNU awk
解决方案:
awk '
{
sub(/[/]$/,"")
gsub(/[[:upper:]]{1}/,"& ")
print gensub(/([/])([[:upper:]])/,"\n\2","g")
}' file
A /trunk/apple
B /trunk/apple
Z /trunk/orange/citrus
Q /trunk/melon/juice/venti/straw
假设您的数据始终采用作为单个字符串提供的格式,您可以试试这个 sed
。
$ sed 's/$/\//;s|\([A-Z]\)\([a-z/]*\)/\([a-z]*\?\)| \n|g' input_file
$ echo "A/trunk/apple/pine/skunk/B/trunk/runk/bunk/apple/Z/trunk/orange/T/fruits/apple/mango/P/anything/apple/pear/banana/L/ball/orange/anything/S/fruits/apple/mango/B/rupert/cream/travel/scout/H/tall/mountains/pottery/barnes" | sed 's/$/\//;s|\([A-Z]\)\([a-z/]*\)/\([a-z]*\?\)| \n|g'
A /trunk/apple/pine/skunk
B /trunk/runk/bunk/apple
Z /trunk/orange
T /fruits/apple/mango
P /anything/apple/pear/banana
L /ball/orange/anything
S /fruits/apple/mango
B /rupert/cream/travel/scout
H /tall/mountains/pottery/barnes
Perl 的一些乐趣,您可以在其中使用非消耗性正则表达式自动拆分为 @F
数组,然后根据需要打印。
perl -lanF'/(?=.{1,2}trunk)/' -e 'print "$F[2*$_] $F[2*$_+1]" for 0..$#F/2'
第 1 步:拆分
perl -lanF/(?=.{1,2}trunk)/'
- 这将获取输入流,并在遇到模式
.{1,2}trunk
时分割每一行 - 因为我们想保留
trunk
和前面的 1 或 2 个字符,所以我们将拆分模式包装在(?=)
中以实现非消耗性前瞻性 - 这样分解:
$ echo A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/ | perl -lanF'/(?=.{1,2}trunk)/' -e 'print join " ", @F' A /trunk/apple/ B /trunk/apple/ Z /trunk/orange/citrus/ Q /trunk/melon/juice/venti/straw/
第 2 步:格式化输出:
@F
数组包含我们要按顺序打印的对,因此我们将迭代数组索引的一半,并一次打印 2 个:print "$F[2*$_] $F[2*$_+1]" for 0..$#F/2
--> 双倍迭代器,并打印对- 使用
perl -l
意味着每个print
在末尾都有一个隐含的\n
- 结果:
$ echo A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/ | perl -lanF'/(?=.{1,2}trunk)/' -e 'print "$F[2*$_] $F[2*$_+1]" for 0..$#F/2' A /trunk/apple/ B /trunk/apple/ Z /trunk/orange/citrus/ Q /trunk/melon/juice/venti/straw/
尾注:Perl 混淆无效。
- perl 中的任何数组都可以转换为散列,格式为 (key,val,key,val....)
- 所以
%F=@F; print "$_ $F{$_}" for keys %F
看起来会很圆滑 - 但是你失去了秩序:
$ echo A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/ | perl -lanF'/(?=.{1,2}trunk)/' -e '%F=@F; print "$_ $F{$_}" for keys %F' Z /trunk/orange/citrus/ A /trunk/apple/ Q /trunk/melon/juice/venti/straw/ B /trunk/apple/
使用 GNU sed:
$ str="A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/"
$ sed -E 's|/?(.)(/trunk/)|\n |g;s|/$||' <<< "$str"
A /trunk/apple
B /trunk/apple
Z /trunk/orange/citrus
Q /trunk/melon/juice/venti/straw
注意第一个空输出行。如果不希望我们可以单独处理第一行输出:
$ sed -E 's|(.)| |;s|/(.)(/trunk/)|\n |g;s|/$||' <<< "$str"
A /trunk/apple
B /trunk/apple
Z /trunk/orange/citrus
Q /trunk/melon/juice/venti/straw