从组合字段中提取“lastname,firstname middle names”

时间:2014-03-10 00:01:37

标签: regex macos sed

这是我在这里看到的问题的一个更复杂的版本。我有一个数据集,其中第四个字段联系了一个组合的"姓氏,名字中间名"我需要使用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。

3 个答案:

答案 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

让我们开始形成我们的答案。我们首先声明您希望输入和输出字段分隔符为\tBEGIN块为您完成此操作。

使用上述测试,您知道您的姓名(完整)位于第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并推送中间名EJohn }。

答案 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+)