无法在Bash中迭代数组

时间:2014-12-15 12:08:34

标签: bash awk

我需要在表格的最后一列之后添加一个带有(序号)数字的新列。

输入和输出文件都是.CSV表。

传入表有超过500 000行(行)数据和7列,例如https://www.dropbox.com/s/g2u68fxrkttv4gq/incoming_data.csv?dl=0

传入的CSV表格(这只是一个例子,所以" |"和" - "为了清楚起见,这里是:)

| id     | Name |
-----------------
| 1      | Foo  |
| 1      | Foo  |
| 1      | Foo  |
| 4242   | Baz  |
| 4242   | Baz  |
| 4242   | Baz  |
| 4242   | Baz  |
| 702131 | Xyz  |
| 702131 | Xyz  |
| 702131 | Xyz  |
| 702131 | Xyz  |

结果CSV(这只是一个例子,所以" |"和" - "为了清楚起见在这里):

| id     | Name |        |
--------------------------
| 1      | Foo  | 1      |
| 1      | Foo  | 2      |
| 1      | Foo  | 3      |
| 4242   | Baz  | 1      |
| 4242   | Baz  | 2      |
| 4242   | Baz  | 3      |
| 4242   | Baz  | 4      |
| 702131 | Xyz  | 1      |
| 702131 | Xyz  | 2      |
| 702131 | Xyz  | 3      |
| 702131 | Xyz  | 4      |

第一列是ID,因此我尝试将所有具有相同ID的行分组并迭代它们。脚本(说实话,我不知道bash脚本):

FILE=$PWD/$1
# Delete header and extract IDs and delete non-unique values. Also change \n to ♥, because awk doesn't properly work with it.
IDS_ARRAY=$(awk -v FS="|" '{for (i=1;i<=NF;i++) if ($i=="\"") inQ=!inQ; ORS=(inQ?"♥":"\n") }1' $FILE | awk -F'|' '{if (NR!=1) {print $1}}' | awk '!seen[$0]++')

for id in $IDS_ARRAY; do
  # Group $FILE by $id from $IDS_ARRAY.    
  cat $FILE | grep $id >> temp_mail_group.csv
  ROW_GROUP=$PWD/temp_mail_group.csv

  # Add a number after each row.
  # NF+1 — add a column after last existing.
  awk -F'|' '{$(NF+1)=++i;}1' OFS="|", $ROW_GROUP >> "numbered_mails_$(date +%Y-%m-%d).csv"
  rm -f $PWD/temp_mail_group.csv
done

现在这个脚本几乎像我想要的那样工作,除了它认为(例如)ID 2834和772834是相同的。

UPD:虽然我将一个答案标记为已批准,但它没有为具有相同ID的某些记录组分配正确的值(现在我没有看到模式)。

4 个答案:

答案 0 :(得分:3)

您可以在一个脚本中执行所有操作:

gawk 'BEGIN { FS="|"; OFS="|";}
/^-/ {print; next;}
$2 ~ /\s*id\s*/ {print $0,""; next;}
 {print "", $2, $3, ++a[$2];}
'

$1是输入中第一个|之前的空字段。我使用空输出列""来获取前导|

技巧是++a[$2],它接受​​每一行中的第二个字段(= ID列)并在关联数组a中查找它。如果没有条目,则结果为0。通过预先递增,我们从1开始,每次ID重新出现时添加1

答案 1 :(得分:2)

一种awk方式

不考虑延长的虚线。

awk 'NR>2{$0=$0 (++a[$2])"|"}1' file

输出

| id | Name |
-------------
| 1  | Foo  |1|
| 1  | Foo  |2|
| 1  | Foo  |3|
| 42 | Baz  |1|
| 42 | Baz  |2|
| 42 | Baz  |3|
| 42 | Baz  |4|
| 70 | Xyz  |1|
| 70 | Xyz  |2|
| 70 | Xyz  |3|
| 70 | Xyz  |4|

答案 2 :(得分:2)

每次在shell中编写循环只是为了操作文本时,你的方法都是错误的。发明shell的人也发明了awk for shell来调用操作文本 - 不要让他们失望: - )。

$ awk '
BEGIN{ w = 8 }
{
    if (NR==1) {
        val = sprintf("%*s|",w,"")
    }
    else if (NR==2) {
        val = sprintf("%*s",w+1,"")
        gsub(/ /,"-",val)
    }
    else {
        val = sprintf(" %-*s|",w-1,++cnt[$2])
    }
    print $0 val
}
' file
| id | Name |        |
----------------------
| 1  | Foo  | 1      |
| 1  | Foo  | 2      |
| 1  | Foo  | 3      |
| 42 | Baz  | 1      |
| 42 | Baz  | 2      |
| 42 | Baz  | 3      |
| 42 | Baz  | 4      |
| 70 | Xyz  | 1      |
| 70 | Xyz  | 2      |
| 70 | Xyz  | 3      |
| 70 | Xyz  | 4      |

答案 3 :(得分:0)

这是使用纯Bash的方法:

inputfile=$1

prev_id=
while IFS= read -r line ; do
    printf '%s' "$line"

    IFS=$'| \t\n' read t1 id name t2 <<<"$line"

    if [[ $line == -* ]] ; then
        printf '%s\n' '---------'
    elif [[ $id == 'id' ]] ; then
        printf ' Number |\n'
    else
        if [[ $id != "$prev_id" ]] ; then
            id_count=0
            prev_id=$id
        fi

        printf '%2d      |\n' "$(( ++id_count ))"
    fi
done <"$inputfile"