从管道分隔文件

时间:2017-08-31 11:17:48

标签: linux shell unix awk sed

我之前遇到类似的问题我已经为这个问题添加了更多的范围但不知道如何编辑它并让它再次生效。这就是我发布新问题的原因。

我的文件是管道分隔文件。

 NAME | NUM | WEB | LOCATION | CURRENCY | PLACE
 ABCD | 04  | GO|OGLE | EUROPE | EURO   | PARIS
 XYZE | 12  | Y|A|HOO | USA    | DOLLAR | SEATTLE
 LMNO | 17  | |FACE|B|O|O|K | ASIA | ASIAN DOLLAR | HONGKONG
 EDDE | 98  | A||M|AZ|ON| | AFRICA | AF DOLLAR | CAPETOWN

我的档案就像这样复杂。我们需要删除" |"来自WEB字段的符号,并将其替换为#,$,&等垃圾值。或任何东西。

输出必须是:

NAME | NUM | WEB | LOCATION | CURRENCY | PLACE
ABCD | 04  | GO#OGLE | EUROPE | EURO   | PARIS
XYZE | 12  | Y#A#HOO | USA    | DOLLAR | SEATTLE
LMNO | 17  | #FACE#B#O#O#K | ASIA | ASIAN DOLLAR | HONGKONG
EDDE | 98  | A##M#AZ#ON# | AFRICA | AF DOLLAR | CAPETOWN

我尝试过几个过滤器以清除这个混乱局面。似乎没有什么能找到一个幸福的结局。谢谢! 我要感谢几个回答我的问题的名字:RomanPerekhrest,Ed Morton,shellter,val rog。

6 个答案:

答案 0 :(得分:2)

您可以使用此awk命令:

awk 'BEGIN{FS=OFS="|"} NR==1{n=NF} NF > n {
s=$3; for (i=4; i<=NF-3; i++) {s = s "#" $i; $i=""} $3=s; gsub(/\|{2,}/, "|")} 1' file

NAME | NUM | WEB | LOCATION | CURRENCY | PLACE
ABCD | 04  | GO#OGLE | EUROPE | EURO   | PARIS
XYZE | 12  | Y#A#HOO | USA    | DOLLAR | SEATTLE
LMNO | 17  | #FACE#B#O#O#K | ASIA | ASIAN DOLLAR | HONGKONG
EDDE | 98  | A##M#AZ#ON# | AFRICA | AF DOLLAR | CAPETOWN

答案 1 :(得分:1)

另一个awk解决方案可以是: -

awk  -F'[[:space:]][|][[:space:]]' '{gsub(/\|/,"#",$3);print $1,"|",$2,"|",$3,"|",$4,"|",$5,"|",$6}' file.txt

说明: -

-F - for field separator here it is space|space
gsub - global substitution in field 3. i.e. every occurance of | will be replaced by #. 
print - just print all the columns separated by "|"

输出将是: -

NAME | NUM | WEB | LOCATION | CURRENCY | PLACE
ABCD | 04  | GO#OGLE | EUROPE | EURO   | PARIS
XYZE | 12  | Y#A#HOO | USA    | DOLLAR | SEATTLE
LMNO | 17  | #FACE#B#O#O#K | ASIA | ASIAN DOLLAR | HONGKONG
EDDE | 98  | A##M#AZ#ON# | AFRICA | AF DOLLAR | CAPETOWN

答案 2 :(得分:1)

import sys

print("sys" in sys.modules) #True
print("datetime" in sys.modules) #False
print("json" in sys.modules) #False

工作原理:在第一行,要输出的字段数与该行上的字段数相同,因此将该数字保存为outNF。从那时起,任何具有多个outNF字段的后续行都会有从$ cat tst.awk BEGIN { FS=OFS="|" } NR==1 { outNf=NF; print; next } { end = beg + (NF - outNf) - 1 for (i=1; i<=NF; i++) { sep = (i>=beg && i<=end ? "#" : OFS) printf "%s%s", $i, (i<NF ? sep : ORS) } } $ awk -v beg=3 -f tst.awk file NAME | NUM | WEB | LOCATION | CURRENCY | PLACE ABCD | 04 | GO#OGLE | EUROPE | EURO | PARIS XYZE | 12 | Y#A#HOO | USA | DOLLAR | SEATTLE LMNO | 17 | #FACE#B#O#O#K | ASIA | ASIAN DOLLAR | HONGKONG EDDE | 98 | A##M#AZ#ON# | AFRICA | AF DOLLAR | CAPETOWN 开始的outNF-NF字段进行组合。所以在循环内部它在1到1之间的字段之间使用OFS,然后从beg + 1到beg +(outNF-NF)它使用字段之间的#从该范围内的输入字段创建一个合并输出字段,然后它返回到在字段之间使用OFS。

答案 3 :(得分:1)

如果您不介意 Perl

,则很容易

如果有空间;然后我们可以通过以下方式打印出来:

stackoverflow ❱ perl -F'\s+|\s+' -a -le  'print $F[5]' file
WEB
GO|OGLE
Y|A|HOO
|FACE|B|O|O|K
A||M|AZ|ON|
stackoverflow ❱  

因为我们可以修改 Perl 中的@F数组;因此我们可以:

$F[5] =~ s/\|/#/g;  

它仅修改此列而不修改其他列。

最终我们可以打印出来了:

stackoverflow ❱ perl -F'\s+|\s+' -lae  '$F[5] =~ s/\|/#/g;print "@F"' file
 NAME | NUM | WEB | LOCATION | CURRENCY | PLACE
 ABCD | 04 | GO#OGLE | EUROPE | EURO | PARIS
 XYZE | 12 | Y#A#HOO | USA | DOLLAR | SEATTLE
 LMNO | 17 | #FACE#B#O#O#K | ASIA | ASIAN DOLLAR | HONGKONG
 EDDE | 98 | A##M#AZ#ON# | AFRICA | AF DOLLAR | CAPETOWN
stackoverflow ❱  

如果你的文件没有空间,就像有人评论我一样;然后你可以传播其他列;只修改那个并将它们连接在一起:

stackoverflow ❱ cat file2
NAME|NUM|WEB|LOCATION|CURRENCY|PLACE
ABCD|04|GO|OGLE|EUROPE|EURO|PARIS
XYZE|12|Y|A|HOO|USA|DOLLAR|SEATTLE
LMNO|17||FACE|B|O|O|K|ASIA|ASIANDOLLAR|HONGKONG
EDDE|98|A||M|AZ|ON||AFRICA|AFDOLLAR|CAPETOWN
stackoverflow ❱ perl -F'\|' -le  '$s=$#F;$e="@F[2..$s-3]";$e=~s/ +/#/g;print join "|", @F[0..1],$e,join "|",@F[$s-2,$s-1,$s]' file2
NAME|NUM|WEB|LOCATION|CURRENCY|PLACE
ABCD|04|GO#OGLE|EUROPE|EURO|PARIS
XYZE|12|Y#A#HOO|USA|DOLLAR|SEATTLE
LMNO|17|#FACE#B#O#O#K|ASIA|ASIANDOLLAR|HONGKONG
EDDE|98|A#M#AZ#ON#|AFRICA|AFDOLLAR|CAPETOWN

答案 4 :(得分:1)

一个简单的 awk 解决方案:

awk  -F "|" '{printf $1} 
{for(i=2; i<=NF; i++) { if(i>3 && i<NF-2)printf "#"$i; else printf "|"$i } printf "\n"} ' file

NAME|NUM|WEB|LOCATION|CURRENCY|PLACE
ABCD|04|GO#OGLE|EUROPE|EURO|PARIS
XYZE|12|Y#A#HOO|USA|DOLLAR|SEATTLE
LMNO|17|#FACE#B#O#O#K|ASIA|ASIANDOLLAR|HONGKONG
EDDE|98|A##M#AZ#ON#|AFRICA|AFDOLLAR|CAPETOWN

if(i>3 && i<NF-2):此条件适用于第3个字段之后和NF-2nd字段之前的额外不需要的字段。如果它满足,前缀&#34;#&#34;在打印这些额外字段之前。

答案 5 :(得分:0)

我没有尝试将它放在一行中,而是让它更容易阅读。那些玩perl golf的人将能够大大减少它。想法是锚定前两个字段和后三个字段。

#!/usr/bin/perl

while(<DATA>) {
  chomp;
  if(($name, $num, $web, $location, $currency, $place) = $_ =~
     /^([^\|]+)\|([^\|]+)\|(.+)\|([^\|]+)\|([^\|]+)\|([^\|]+)$/) {
    $web =~ tr/\|/\_/;
    printf "%s\n", join('|', ($name, $num, $web, $location, $currency, $place));
  }
}
__DATA__
 NAME | NUM | WEB | LOCATION | CURRENCY | PLACE
 ABCD | 04  | GO|OGLE | EUROPE | EURO   | PARIS
 XYZE | 12  | Y|A|HOO | USA    | DOLLAR | SEATTLE
 LMNO | 17  | |FACE|B|O|O|K | ASIA | ASIAN DOLLAR | HONGKONG
 EDDE | 98  | A||M|AZ|ON| | AFRICA | AF DOLLAR | CAPETOWN

输出:

 NAME | NUM | WEB | LOCATION | CURRENCY | PLACE
 ABCD | 04  | GO_OGLE | EUROPE | EURO   | PARIS
 XYZE | 12  | Y_A_HOO | USA    | DOLLAR | SEATTLE
 LMNO | 17  | _FACE_B_O_O_K | ASIA | ASIAN DOLLAR | HONGKONG
 EDDE | 98  | A__M_AZ_ON_ | AFRICA | AF DOLLAR | CAPETOWN