【问题标题】:manipulation of date fields in multiple file using bash in GNU/Linux在 GNU/Linux 中使用 bash 操作多个文件中的日期字段
【发布时间】:2021-02-12 04:27:34
【问题描述】:

我们有一些 |分隔的输入 CSV 文件。我们的要求是,如果最后一个字段值包含 9999-12-31,那么我们需要从第二个字段中减去 3 天并保持最后一个字段不变。如果最后一个字段值没有 9999-12-31,则从最后一个字段和倒数第二个字段中减去 3 天。

示例输入文件:-

#Code|sequence|Code_ID|margin_ID|Eff_date|End_date
M|X19|0002|F3|2020-12-10|9999-12-31
M|X19|0002|F3|2021-01-15|9999-12-31
M|X19|0002|F3|2010-10-10|2019-11-09
M|X19|0002|F3|2010-03-09|2011-04-02

预期的输出文件:-

#Code|sequence|Code_ID|margin_ID|Eff_date|End_date
M|X19|0002|F3|2020-12-07|9999-12-31
M|X19|0002|F3|2021-01-12|9999-12-31
M|X19|0002|F3|2010-10-07|2019-11-06
M|X19|0002|F3|2010-03-06|2011-03-30

谁能建议使用脚本或命令来获得预期的输出?

我正在尝试使用以下命令,但出现错误。

for i in `ls *.csv`
do
awk -F"|" -v OFS="|" '{if(NR>1) { if ($NF~/9999-12-31/) { $(NF-1)="date -d \"$(date -d \""$(NF-1)"\")-3days\" \"+%Y-%m-%d \""; print $0} 
else {$(NF-1)="date -d \"$(date -d \""$(NF-1)"\")-3days\" \"+%Y-%m-%d \""; $NF="date -d \"$(date -d \""$NF"\")-3days\" \"+%Y-%m-%d \""; 
print $0} } }' $i >> temp/new_$i
done

获取 awk:第 1 行附近的语法错误


由 Ed Morton 编辑我通过gawk -o-(漂亮的打印)运行上述 awk 脚本以使其清晰易读,这是更清晰的结果:

{
    if (NR > 1) {
        if ($NF ~ /9999-12-31/) {
            $(NF - 1) = "date -d \"$(date -d \"" $(NF - 1) "\")-3days\" \"+%Y-%m-%d \""
            print $0
        } else {
            $(NF - 1) = "date -d \"$(date -d \"" $(NF - 1) "\")-3days\" \"+%Y-%m-%d \""
            $NF = "date -d \"$(date -d \"" $NF "\")-3days\" \"+%Y-%m-%d \""
            print $0
        }
    }
}

【问题讨论】:

  • 修复“第 1 行附近的语法错误”的第一步是编写脚本,使其占据多行,使其具有一定的可读性。就目前而言,它几乎是完全难以理解的,因为它不可读。
  • 将一个大脚本塞进 1 行总是一个坏主意,原因有很多,其中一个原因是您收到的每条错误消息都会告诉您错误在第 1 行,这是没有用的。话虽如此 - 你的脚本不包含任何语法错误,所以要么你没有发布你运行的脚本,要么调用 shell 脚本有问题,或者你的 awk 坏了(例如,让它变旧、坏了 awk - /bin/在 Solaris 上使用 awk)。

标签: linux bash awk


【解决方案1】:

编辑:使用awk 中的函数方法尝试以下操作。仅在 GNU awk 中使用所示示例编写和测试。我已经在单个文件上运行了这个,我们也可以在不使用 bash 循环的情况下将 csv 文件传递​​到这个程序中。

awk '
BEGIN{
  threedaysSecs=(3 * 24 * 60 * 60)
  FS=OFS="|"
}
function getDate(field){
    value=(mktime(substr($field,1,4)" "substr($field,6,2)" "substr($field,9,2) " 00 00 00"))-threedaysSecs
    $field=strftime("%Y-%m-%d", value)
}
$NF=="9999-12-31"{
  getDate((NF-1))
}
$NF!="9999-12-31"{
  getDate((NF-1))
  getDate(NF)
}
1
'  Input_file


请您尝试以下操作。没有函数方法。

awk '
BEGIN{
  threedaysSecs=(3 * 24 * 60 * 60)
  FS=OFS="|"
}
$NF=="9999-12-31"{
  value=(mktime(substr($5,1,4)" "substr($5,6,2)" "substr($5,9,2) " 00 00 00"))-threedaysSecs
  $5=strftime("%Y-%m-%d", value)
}
$NF!="9999-12-31"{
  value1=(mktime(substr($5,1,4)" "substr($5,6,2)" "substr($5,9,2) " 00 00 00"))-threedaysSecs
  value2=(mktime(substr($6,1,4)" "substr($6,6,2)" "substr($6,9,2) " 00 00 00"))-threedaysSecs
  $(NF-1)=strftime("%Y-%m-%d", value1)
  $NF=strftime("%Y-%m-%d", value2)
}
1
' Input_file

【讨论】:

  • 如果你写3 * 86400 甚至3 * 24 * 60 * 60 而不是259200 可能会更清楚。要求似乎是“从第 5 个(倒数第二个)字段(无条件)中减去三天,如果不是 9999-12-31,则从第 6 个(最后一个)字段中减去三天。我想我会编写一个函数来从日期中减去 3 天,或者编写一个函数来从日期中减去 N 天并用 N=3 调用它。然后在最后一列有条件地调用该函数,并在倒数第二列无条件地调用它。
  • @JonathanLeffler,当然,函数方法正在开发中。
【解决方案2】:

另一个 awk,在 shell 中使用 getlinedate

$ awk '
BEGIN {
    FS=OFS="|"
}
FNR>1{
    for(i=5;i<=5+($5=="9999-12-31");i++)
        if(("date -d \"" $i " -3days\" +\"%Y-%m-%d\"" | getline res)>0)
            $i=res
        else
            exit 1
}1' file

【讨论】:

    【解决方案3】:

    使用来自 GNU coreutils 的普通 bashdate

    #!/bin/bash
    
    for file in *.csv; do
        while IFS='|' read -ra field; do
            if [[ ${field[0]} != \#* ]]; then
                field[-2]=$(date -d "${field[-2]} - 3 days" +%F)
                if [[ ${field[-1]} != '9999-12-31' ]]; then
                    field[-1]=$(date -d "${field[-1]} - 3 days" +%F)
                fi
            fi
            (IFS='|'; printf '%s\n' "${field[*]}")
        done < "$file" > "temp/new_$file"
    done
    

    【讨论】:

      【解决方案4】:

      将 GNU awk 用于日期函数(将比任何调用 Unix date 命令的代码快几个数量级)和 gensub():

      $ cat tst.awk
      BEGIN {
          FS=OFS="|"
          deltaSecs = -3*24*60*60
      }
      NR > 1 {
          $(NF-1) = applyDelta($(NF-1),deltaSecs)
          if ( $NF != "9999-12-31" ) {
              $NF = applyDelta($NF,deltaSecs)
          }
      }
      { print }
      
      function applyDelta(date,delta) { return secs2date( date2secs(date) + delta ) }
      function date2secs(date) { return mktime(gensub(/-/," ","g",date) " 12 0 0") }
      function secs2date(secs) { return strftime("%Y-%m-%d",secs) }
      

      $ awk -f tst.awk file
      #Code|sequence|Code_ID|margin_ID|Eff_date|End_date
      M|X19|0002|F3|2020-12-07|9999-12-31
      M|X19|0002|F3|2021-01-12|9999-12-31
      M|X19|0002|F3|2010-10-07|2019-11-06
      M|X19|0002|F3|2010-03-06|2011-03-30
      

      如果您没有 GNU awk 但不知何故有 GNU date,那么只需将上面最后两个函数的定义更改为:

      function date2secs(date,        cmd, line, secs) {
          cmd = "date +\"%s\" --date=\"" date " 12:00:00\""
          secs = ( (cmd | getline line) > 0 ? line : -1 )
          close(cmd)
          return secs
      }
      
      function secs2date(secs,        cmd, line, date) {
          cmd = "date +\"%Y-%m-%d\" --date=\"@" secs "\""
          date = ( (cmd | getline line) > 0 ? line : -1 )
          close(cmd)
          return date
      }
      

      但如前所述,它的运行速度将比 gawk 版本慢得多,因为它每次调用 date 时都会生成一个子 shell。

      【讨论】:

        猜你喜欢
        • 2019-08-20
        • 2020-07-15
        • 1970-01-01
        • 2015-05-20
        • 2013-10-24
        • 2013-03-11
        • 1970-01-01
        • 2021-12-21
        • 1970-01-01
        相关资源
        最近更新 更多