我有一个CSV文件,我正在使用sed进行操作。我正在做的是将当前YYYY-MM-DD HH:MM:SS插入IP地址后的第5个字段。如下所示,每个值都用双引号括起来,每个CSV列用逗号分隔。
"12345","","","None","192.168.2.1","qqq","000"
"67890","ABC-1234-5678","9.9","Low","192.168.2.1","qqq","000"
使用命令:sed 'N;s/","/","YYYY-MM-DD HH:MM:SS","/5' FILENAME
我在第5场后添加日期。通常这有效,但经常
CSV文件中的某些值会使此计数混乱,从而将日期插入第5个字段。要解决此问题,我怎样才能不仅在第5个字段后添加日期,还要确保第5个字段是IP地址?
最终输出应为:
"12345","","","None","192.168.2.1","YYYY-MM-DD HH:MM:SS","qqq","000"
"67890","ABC-1234-5678","9.9","Low","192.168.2.1","YYYY-MM-DD HH:MM:SS","qqq","000"
请回答使用SED而非AWK完成此操作的方法。以及如何在添加日期之前确保第5个字段也是IP地址?
答案 0 :(得分:2)
此答案目前假设CSV文件非常一致且简单(如示例数据中所示),因此:
"…""…"
这样的字段来表示字符串中嵌入的双引号。"this,that"
)之间永远不会有逗号括号。鉴于这些先决条件,这个sed
脚本完成了这项任务:
sed 's/^\("[^"]*",\)\{4\}"\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}",/&"YYYY-MM-DD HH:MM:SS",/'
让我们将搜索模式分成几部分:
^\("[^"]*",\)\{4\}
匹配行的开头,然后是:4次重复双引号,零序或多次非双引号,双引号和逗号。
换句话说,这标识了前四个字段。
"\([0-9]\{1,3\}\.\)\{3\}
匹配双引号,然后3个重复的1-3个十进制数字后跟一个点 - IPv4点分十进制地址的前三个三元组。
[0-9]\{1,3\}",
匹配1-3个十进制数字后跟双引号和逗号 - IPv4点分十进制地址的最后一个三元组加上一个字段的结尾。
显然,对于您还需要处理的CSV文件的每个特性,您必须修改正则表达式。这不是微不足道的。
使用扩展的正则表达式(在GNU和BSD -E
上由sed
启用),您可以写:
sed -E 's/^("(([^"]*"")*[^"]*)",){4}"([0-9]{1,3}\.){3}[0-9]{1,3}",/&"YYYY-MM-DD HH:MM:SS",/'
识别前4个字段的模式比以前更复杂。它匹配4次重复:双引号,零次或多次出现{零次或多次非双引号后跟两次双引号}后跟零次或多次非双引号后跟双引号和逗号。
你也可以在经典sed
(基本正则表达式)中用自由的反斜杠写出来:
sed 's/^\("\(\([^"]*""\)*[^"]*\)",\)\{4\}"\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}",/&"YYYY-MM-DD HH:MM:SS",/'
给定数据文件:
"12345","","","None","192.168.2.1","qqq","000"
"67890","ABC-1234-5678","9.9","Low","192.168.2.1","qqq","000"
"23456","Quaternions","2.3","Pisces","Heredotus","qqq","000"
"34567","Commas, oh commas!","3.14159","""Quotes"" quoth he","192.168.99.37","zzz","011"
"45678","Commas, oh commas!","3.14159","""Quote me"",""or not""","192.168.99.37","zzz","011"
显示的第一个脚本产生输出:
"12345","","","None","192.168.2.1","YYYY-MM-DD HH:MM:SS","qqq","000"
"67890","ABC-1234-5678","9.9","Low","192.168.2.1","YYYY-MM-DD HH:MM:SS","qqq","000"
"23456","Quaternions","2.3","Pisces","Heredotus","qqq","000"
"34567","Commas, oh commas!","3.14159","""Quotes"" quoth he","192.168.99.37","zzz","011"
"45678","Commas, oh commas!","3.14159","""Quote me"",""or not""","192.168.99.37","zzz","011"
前两行正确映射;第三个是正确不变的,但最后两个应该已经映射而不是。
第二个和第三个命令产生:
"12345","","","None","192.168.2.1","YYYY-MM-DD HH:MM:SS","qqq","000"
"67890","ABC-1234-5678","9.9","Low","192.168.2.1","YYYY-MM-DD HH:MM:SS","qqq","000"
"23456","Quaternions","2.3","Pisces","Heredotus","qqq","000"
"34567","Commas, oh commas!","3.14159","""Quotes"" quoth he","192.168.99.37","YYYY-MM-DD HH:MM:SS","zzz","011"
"45678","Commas, oh commas!","3.14159","""Quote me"",""or not""","192.168.99.37","YYYY-MM-DD HH:MM:SS","zzz","011"
请注意,未正确修改Heredotus,最后两行获取IP地址后添加的日期字符串(也正确)。
那些最后的正则表达式不适合胆小的人。
显然,如果你想坚持IP地址只匹配每个组件中0..255范围内的数字,没有前导0,那么你必须加强正则表达式的IP地址匹配部分。可以办到;它不漂亮。使用扩展正则表达式最简单:
([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])
您可以在之前显示的正则表达式中使用该单位代替每个[0-9]{3}
单位。
请注意,这仍然不会尝试处理未被双引号括起来的字段。
它也不确定要从date
命令替换的值。这是可行的(如果不是基本的)例程shell脚本仔细管理引号:
dt=$(date +'%Y-%m-%d %H:%M:%S')
sed -E 's/^("(([^"]*"")*[^"]*)",){4}"([0-9]{1,3}\.){3}[0-9]{1,3}",/&"'"$dt"'",/'
'…"'"$dt"'",/'
序列是以单引号字符串开头的部分。第一个双引号是字符串中的简单数据;下一个单引号结束引用,"$dt"
在shell双引号内插入date
的值(因此空格不会造成任何麻烦),然后单引号恢复单引号符号,在字符串(sed
的参数)之前添加另一个双引号,逗号和斜杠终止。
答案 1 :(得分:1)
尝试:
awk -vdate1=$(date +"%Y-%m-%d") -vdate2=$(date +"%H:%M:%S") -F, '$5 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]/{$5=$5 FS date1 " " date2} 1' OFS=, Input_file
此外,如果您想编辑相同的Input_file,您可以将上面的命令输出到临时文件中,然后将命令重命名(mv命令)到同一个Input_file
现在也添加单线形式的解决方案。
awk -vdate1=$(date +"%Y-%m-%d") -vdate2=$(date +"%H:%M:%S") -F, '
$5 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]/{
$5=$5 FS date1 " " date2
}
1
' OFS=, Input_file