这是我在这里看到的问题的一个更复杂的版本。我有一个数据集,其中第四个字段联系了一个组合的"姓氏,名字中间名"我需要使用sed将其分解为" lastname" "姓名" " middlenames"
字段是制表符分隔的,数据如下所示:
01/31/2014 Teaching/Grad Ass 12345 Johnson,Robert S 09 etc
输出应如下所示(最后,第一,中间名称之间的标签):
01/31/2014 Teaching/Grad Ass 12345 Johnson Robert S 09 etc
Lastname位于第四个标签和逗号之间,因此非常容易抓取。遗憾的是,第一名和中间名捆绑在一起,必须作为空格分开,有些名称字段不包含中间的首字母或空格。即使这样,它也不是那么简单 - 有些名字可能包含标点符号,有些名字含有引导性内容。我的目标是输出第一个名字,中间名如下:
Name:
Adam,John
Beta,Sara X
Cruise,Jack A B
Derp,E John A
Egbert,Mary-Lu
Lname: Fname: Mname:
Adam John
Beta Sara X
Cruise Jack A B
Derp E John A
Egbert Mary-Lu
我使用http://regexpal.com/来构建一个效果很好的正则表达式!但是同样的正则表达式在我的Mac上失败了,可能是因为Mac正则表达式有点不同。然后它在我的linux主机上失败了(sed:-e expression#1,char 79:前面的正则表达式无效)。这是regexpal中使用的正则表达式:
^([^\t]*\t){4}([^,]*)[,]{0,1}((.( )*([^[ \t]*]*))[ ]{0,1}([^\t]*)\t
更新:有人向我指出必须更好的测试网站regexr.com,这帮助我大大改进了正则表达式。我还没有对它进行过测试,但我发现我的一部分问题是,否定的类匹配仍然是一个匹配,并且该字符被认为是#34;找到了。"所以我对([^,]*),
没有发现任何逗号,因为逗号已经被它之前的否定集找到了。
(^(?:.*?\t){4})([^,]*),(.[ ]*(?:[^[ |\t]*]*))[ ]{0,1}([^\t]*)\t?
这是失败的sed:
$ sed -r 's/^(?:[^\t]*\t){4}([^,]*)[,]{0,1}(.( )*([^[ \t]*]*))[ ]{0,1}([^\t]*)/foo/g' *
sed: -e expression #1, char 74: Invalid preceding regular expression
我试图将它逐个分解并尝试每个grep,但即使这样也失败了。
$ grep -or '^[^\t]*\t' *
工作正常,但
$ grep -or '^([^\t]*\t){4}' *
一无所获。这似乎是正确的语法,并在测试站点工作,但显然我对一些基本概念毫无头绪。任何建议或指示将不胜感激。
更新2:我发现\ t在我的正则表达式,扩展或其他方面没有匹配标签。它匹配" t"无论我做什么。其他特殊字符工作正常; \ w匹配单词字符,\ s匹配空格。 \ t匹配t。
答案 0 :(得分:1)
如果您的文件是\t
分隔的,则可以使用awk
进行格式化。这可能不是完整的答案,但应该足以让您入门。
$ cat file
01/31/2014 Teaching/Grad Ass 12345 Johnson,Robert S 09 etc
以下不是答案,只是一个测试,以显示awk
被理解为您的字段。
$ awk -F'\t' '{ for(i=1;i<=NF;i++) print "$"i" is "$i}' file
$1 is 01/31/2014
$2 is Teaching/Grad Ass
$3 is 12345
$4 is Johnson,Robert S
$5 is 09
$6 is etc
让我们开始形成我们的答案。我们首先声明您希望输入和输出字段分隔符为\t
。 BEGIN
块为您完成此操作。
使用上述测试,您知道您的姓名(完整)位于第4列。由于您在第一个,最后一个和中间名称之间有,
和space
,因此我们调用awk's split
函数并将其作为标识符进行分割。您可以根据输入数据在此处添加更多内容。
然后按照您想要的顺序重新组织第4个字段。
$ awk '
BEGIN { FS = OFS = "\t" }
{
split($4, name, /[ ,]/)
$4 = name[1] FS name[2] FS name[3]
}1' file
01/31/2014 Teaching/Grad Ass 12345 Johnson Robert S 09 etc
因为我们永远无法知道初始名称会有多少中间名,所以迭代它们会有一点灵活性。关于split
函数的好处是它可以告诉我们它有多少替换。利用我们的优势,我们可以做到:
$ cat file
01/31/2014 Teaching/Grad Ass 12345 Johnson,Robert S 09 etc
01/31/2014 Teaching/Grad Ass 12345 Cruise,Jack A B 09 etc
01/31/2014 Teaching/Grad Ass 12345 Derp,E John A 09 etc
01/31/2014 Teaching/Grad Ass 12345 Egbert,Mary-Lu 09 etc
01/31/2014 Teaching/Grad Ass 12345 Adam,John 09 etc
awk '
BEGIN { FS = OFS = "\t" }
{
n = split($4,name,/[ ,]/)
$4 = name[1] FS name[2]
for(i = 3; i <= n; i++) {
$4 = (i==3) ? $4 FS name[i] : $4" "name[i]
}
}1' file
01/31/2014 Teaching/Grad Ass 12345 Johnson Robert S 09 etc
01/31/2014 Teaching/Grad Ass 12345 Cruise Jack A B 09 etc
01/31/2014 Teaching/Grad Ass 12345 Derp E John A 09 etc
01/31/2014 Teaching/Grad Ass 12345 Egbert Mary-Lu 09 etc
01/31/2014 Teaching/Grad Ass 12345 Adam John 09 etc
<强>警告:强>
我们硬编码$4 = name[1] FS name[2]
,因为我们知道人在这两个数组中会有他的姓氏和名字,然后我们迭代n
个中间首字母。目前,我们告诉Derp E John A
姓氏为awk
,名字为Derp
并推送中间名E
和John
}。
答案 1 :(得分:0)
在不使用正则表达式的情况下编写起来要简单得多。
#!/bin/bash
printf '%-20s %-20s %-20s\n' "First" "Middle" "Last"
while IFS=$'\t' read -r date position number name _; do
read -r -a name_pieces <<<"$name" # split name into an array
if (( ${#name_pieces[@]} == 2 )); then
# no middle name
printf '%-20s %-20s %-20s\n' "${name_pieces[0]}" "" "${name_pieces[1]}"
else
printf '%-20s %-20s %-20s\n' \
"${name_pieces[0]}" \
"${name_pieces[*]:1:${#name_pieces[@]}-2}" \
"${name_pieces[${#name_pieces}]}"
fi
done
像这样调用(使用文字制表符而不是适当的空格):
./split_names <<'EOF'
123 foo 123 Joe William Jay Barker Foo
123 foo 123 Jim Barker Bar
EOF
...以上产生了输出:
First Middle Last
Joe William Jay Barker
Jim Barker
答案 2 :(得分:0)
步骤1:用([空格])
替换(,)步骤2:使用([space])
将数组中的字符串拆分步骤3:检查[1]中的char是否为1,如果是,则连接[2]。
步骤4:同样的方式,检查一下[4]中的字符。如果是,则连接。
我设法为名字和姓氏写了正则表达式。希望它会有所帮助
正则表达式获取姓氏:([^\s|^,]+)
正则表达式获取名字:\s\S(\s\w*\s+|\w*\s+)