重新格式化表格

重新格式化表格

我有以下类型的表:

ID   date     DailyFlow
a 1972-01-01 17.0265761797993
b 1972-01-02 17.200476457399
c 1972-01-03 17.2926436045271
d 1972-01-04 17.3900277599829
e 1972-01-05 17.5987080931028
f 1972-01-06 17.6334881486229
g 1972-01-07 17.7030482596626
...

我想重新格式化它们,如下所示:

YYYY    DDD sim
1972    1   17.0265761797993
1972    2   17.200476457399
1972    3   17.2926436045271
1972    4   17.3900277599829
1972    5   17.5987080931028
1972    6   17.6334881486229
1972    7   17.7030482596626
1972    8   17.7204382874227

第一行包含在表中。文件是纯文本 (*.txt),带有“制表符”分隔符。ID 列是虚拟的,我想删除它!在我想要的输出中,DDD 列上的数字 (1、2、3、...) 应与相应年份的日期相符。

有人知道如何做到这一点吗(使用 bash)?谢谢!

答案1

这本来是 的工作awk,但第二列中的替换需要gensub,因此gawk,默认情况下不会安装 ,所以我最终找到了一个sed解决方案:

sed -i.bak 's/[^\t]*\t\([^-]*\)-[0-9][0-9]-[0-9]\([0-9]\)[^\t]*\t\([^\t]*\)/\1\t\2\t\3/' infile

或者,使用 ERE 缩短(感谢 user1598390):

sed -E -i.bak 's/.*([0-9]{4})-[0-9]{2}-([0-9]{2})(.*)/\1\t\2\3/' infile
  • -i.bak:就地处理文件,将原始文件备份到infile.bak

sed命令分解:

  • s:断言执行替换;
  • /:开始模式
  • [^\t]*: 匹配任意数量的任意字符,不匹配\t
  • \t: 匹配一个\t字符
  • \(:开始第一个捕获组
  • [^-]*: 匹配任意数量的任意字符,不匹配-
  • \):停止第一个捕获组
  • -: 匹配一个-字符
  • [0-9]: 匹配任意数字
  • [0-9]: 匹配任意数字
  • -: 匹配一个-字符
  • [0-9]: 匹配任意数字
  • \(:开始第二个捕获组
  • [0-9]: 匹配任意数字
  • \):停止第二个捕获组
  • [^\t]*: 匹配任意数量的任意字符,不匹配\t
  • \t: 匹配一个\t字符
  • \(:开始第三个捕获组
  • [^\t]*: 匹配任意数量的任意字符,不匹配\t
  • \):停止第三个捕获组
  • /:停止模式/开始替换字符串
  • \1: 反向引用被第一个捕获组替换
  • \2: 反向引用被第二个捕获组替换
  • \3: 反向引用被第三个捕获组替换
  • /:停止替换字符串/启动修饰符

示例文件的输出:

user@debian ~/tmp % cat infile 
a   1972-01-01  17.0265761797993
b   1972-01-02  17.200476457399
c   1972-01-03  17.2926436045271
d   1972-01-04  17.3900277599829
e   1972-01-05  17.5987080931028
f   1972-01-06  17.6334881486229
g   1972-01-07  17.7030482596626
user@debian ~/tmp % sed 's/[^\t]*\t\([^-]*\)-[0-9][0-9]-[0-9]\([0-9]\)[^\t]*\t\([^\t]*\)/\1\t\2\t\3/' infile
1972    1   17.0265761797993
1972    2   17.200476457399
1972    3   17.2926436045271
1972    4   17.3900277599829
1972    5   17.5987080931028
1972    6   17.6334881486229
1972    7   17.7030482596626

答案2

用于awk解析和date格式化日期,还有什么;)

awk 'BEGIN {printf "%s\t%s\t%s\n","YYYY","DDD","sim"} NR != 1 {system("date -d \""$2"\" +\"%Y\t%-d\t"$3"\"")}' your_file

显示一年中的某一天(使用%j代替%d或更好地%-j代替%-d-避免使用0

awk 'BEGIN {printf "%s\t%s\t%s\n","YYYY","DDD","sim"} NR != 1 {system("date -d \""$2"\" +\"%Y\t%-j\t"$3"\"")}' your_file

例子

输入文件

% cat foo
ID   date     DailyFlow
a 1972-01-01 17.0265761797993
b 1972-01-02 17.200476457399
c 1972-01-03 17.2926436045271
d 1972-01-04 17.3900277599829
e 1972-01-05 17.5987080931028
f 1972-01-06 17.6334881486229
g 1972-01-07 17.7030482596626
h 1972-02-01 17.7030482596626
i 1972-02-02 17.7030482596626

输出(包含月份日期)

% awk 'BEGIN {printf "%s\t%s\t%s\n","YYYY","DDD","sim"} NR != 1 {system("date -d \""$2"\" +\"%Y\t%-d\t"$3"\"")}' foo
YYYY    DDD sim
1972    1   17.0265761797993
1972    2   17.200476457399
1972    3   17.2926436045271
1972    4   17.3900277599829
1972    5   17.5987080931028
1972    6   17.6334881486229
1972    7   17.7030482596626
1972    1   17.7030482596626
1972    2   17.7030482596626

输出(包含一年中的某一天)

% awk 'BEGIN {printf "%s\t%s\t%s\n","YYYY","DDD","sim"} NR != 1 {system("date -d \""$2"\" +\"%Y\t%-j\t"$3"\"")}' foo
YYYY    DDD sim
1972    1   17.0265761797993
1972    2   17.200476457399
1972    3   17.2926436045271
1972    4   17.3900277599829
1972    5   17.5987080931028
1972    6   17.6334881486229
1972    7   17.7030482596626
1972    32  17.7030482596626
1972    33  17.7030482596626

答案3

使用awk

awk 'BEGIN{print "YYYY\tDDD\tsim"} NR!=1{printf "%s\t%s\t%s\n",substr($2,0,5),$1,$3}' file

解释

  1. BEGIN{}部分格式化标题行。
  2. NR!=1省略文件的标题行
  3. printf()格式化输出
  4. substr($2,0,5)从日期中删除日期和月份

输出结果如下:

YYYY    DDD     sim
1972    1       17.0265761797993
1972    2       17.200476457399
1972    3       17.2926436045271
1972    4       17.3900277599829
1972    5       17.5987080931028
1972    6       17.6334881486229
1972    7       17.7030482596626

答案4

仅使用bash

#!/bin/bash
shopt -s extglob
printf "YYYY\tDDD\tsim\n"
while IFS=$'\t' read -r first second third; do
    day="$(date --date="$second" '+%j')"
    printf "%s\t%s\t%s\n" "${second%%-*}" "${day##*(0)}" "${third}"
done < <(tail -n +2 foo.txt)
  • 我们从第二行开始读取输入文件的每一行,并将制表符分隔的部分作为变量firstsecond然后third依次

  • 然后我们使用bash参数扩展来获得所需的输出模式。有关参数扩展的信息,请阅读GNU 文档

  • extglob用于删除天数中的填充零。

例子 :

输入 :

ID  date        DailyFlow
a   1972-01-01  17.0265761797993
b   1972-01-02  17.200476457399
c   1972-01-03  17.2926436045271
d   1972-01-04  17.3900277599829
e   1972-01-05  17.5987080931028
f   1972-01-06  17.6334881486229
g   1972-01-07  17.7030482596626
h   1972-02-01  17.7030482596626
i   1972-02-02  17.7030482596626

输出 :

YYYY    DDD   sim
1972    1     17.0265761797993
1972    2     17.200476457399
1972    3     17.2926436045271
1972    4     17.3900277599829
1972    5     17.5987080931028
1972    6     17.6334881486229
1972    7     17.7030482596626
1972    32    17.7030482596626
1972    33    17.7030482596626

相关内容