【问题标题】:Can I change a cut command for a sed?我可以更改 sed 的剪切命令吗?
【发布时间】:2021-03-09 07:10:28
【问题描述】:

我正在编写一个脚本,其中一部分是将日期值格式化为与 SQL*Plus 8 兼容的值。 日期格式为:

20191115103845

我需要把这个字符串改成

to_date('2019/11/15:10:38:45', 'yyyy/mm/dd:hh24:mi:ss')

我使用剪切工具:

funcion(){
        data=$1
        year=$(echo $data| cut -c1,2,3,4)
        month=$(echo $data | cut -c5,6)
        day=$(echo $data | cut -c7,8)
        hour=$(echo $data| cut -c9,10)
        min=$(echo $data| cut -c11,12)
        sec=$(echo $date | cut -c13,14)
        echo "to_date('"$year"/"$month"/"$day":"$hour":"$min":"$sec"', 'yyyy/mm/dd:hh24:mi:ss')"
}

funcion $data_to_format

(data_to_format=20191115103845)

我的问题是这需要几秒钟,我将使用这个脚本处理一千多个文件,每个文件有 100 多个日期。所以我认为也许'sed'命令可能会更好。 但我使用的是 ksh88(旧 ksh),而 sed 没有 -E 选项,无法使其工作。

谁能帮我解决一个旧的正则表达式 sed 解决方案?

【问题讨论】:

  • 看看bash:data="20191115103845"; echo "${data:0:4}"
  • 你已经标记了这个bash,但你的文字是ksh88;我将其重新标记为ksh
  • 在您的代码中,有时会有echo $data(以a 结尾),有时还有echo $date(以e 结尾)。
  • 您拥有的 sed 版本与您使用的 shell 无关 - sed 和 bash(或 ksh88 或任何其他 shell)是完全不同的工具。
  • 我刚刚注意到您说i'm going to use this script for more than thousand files with 100+ dates each one - 鉴于此,请不要执行此处任何答案中显示的任何操作,因为您应该使用 awk 脚本来处理文件,而不是使用 shell 脚本调用 awk(或执行其他任何操作)来转换单个字符串。

标签: shell awk sed ksh cut


【解决方案1】:

您能否尝试在 GNU awk 中使用所示示例进行跟踪、编写和测试。

awk -v s1="\047" -v firstPart="to_date(" -v lastPart="yyyy/mm/dd:hh24:mi:ss" '
{
  print firstPart s1 substr($0,1,4) "/" substr($0,5,2) "/" substr($0,7,2)":"\
        substr($0,9,2)":"substr($0,11,2)":"substr($0,13,2) s1 ", " s1\
        lastPart s1 ")"
}
' Input_file

解释:

  • -v s1="\047":创建名为 s1 的变量,其中的值为 '
  • -v firstPart="to_date(":创建变量firstPart,其中包含字符串to_date(
  • -v lastPart="yyyy/mm/dd:hh24:mi:ss":创建变量 lastPart,其中包含字符串 yyyy/mm/dd:hh24:mi:ss
  • print:使用 print 函数打印变量和值。
  • firstPart s1 substr($0,1,4) "/" substr($0,5,2) "/" substr($0,7,2)":"\ substr($0,9,2)":"substr($0,11,2)":"substr($0,13,2) s1 ", " s1\ lastPart s1 ")":在此处按照 OP 的要求打印变量和子字符串。


第二个解决方案:在这里也添加一个sed 解决方案,在这里使用sed 的反向引用功能。

echo "20191115103845" |
sed 's/\(....\)\(..\)\(..\)\(..\)\(..\)\(..\)/to_date(\x27\1\/\2\/\3:\4:\5:\6\x27, \x27yyyy\/mm\/dd:hh24:mi:ss\x27)/'

感谢tripleee,因为\x27 可能在少数sed 中不受支持,因此在sed 代码中添加另一种使用' 的方式。

echo "20191115103845" |
sed 's/\(....\)\(..\)\(..\)\(..\)\(..\)\(..\)/to_date('"'"'\1\/\2\/\3:\4:\5:\6'"'"', '"'"'yyyy\/mm\/dd:hh24:mi:ss'"'"')/'

【讨论】:

  • 谢谢!这已经奏效了!最终版本是: echo $data| awk -v s1="\047" -v firstPart="to_date(" -v lastPart="yyyy/mm/dd:hh24:mi:ss" ' { print firstPart s1 substr($0,1,4) "/" substr($0,5,2) "/" substr($0,7,2)":"\ substr($0,9,2)":"substr($0,11,2)":"substr($0,13,2) s1 ", " s1\ lastPart s1 ")" } '
  • \x27 不能移植到所有 sed 方言。如果您没有,您可以轻松替换,例如一个字面上的单引号,尽管您必须了解引用的工作原理。简而言之,'"single\27quote"' 可以改写为'single'"'"'quote',其中三个引用字符串'single'"'"'quote' 彼此相邻粘贴。
  • @tripleee,非常感谢您的告知,我现在将在解决方案中添加该注释。
【解决方案2】:

可能更好的是一直使用 shell 内置参数扩展。

没有简单的方法可以一步完成,但您可以一次删除一个前缀,然后处理剩余的尾部。简而言之,${variable#pattern} 返回 $variable 并删除 pattern 上的任何前缀匹配,${variable%pattern} 对后缀匹配执行相同操作。

funcion(){
    data=${1#[0-9][0-9][0-9][0-9]}
    year=${1%"$data"}
    data=${data#"$year"}
    tail=${data#[0-9][0-9]}
    month=${data%"$tail"}
    data=${data#"$month"}
    tail=${data#[0-9][0-9]}
    day=${data%"$tail"}
    data=${data#"$day"}
    tail=${data#[0-9][0-9]}
    hour=${data%"$tail"}
    data=${data#"$hour"}
    tail=${data#[0-9][0-9]}
    min=${data%"$tail"}
    sec=${tail#"$min"}
    echo "to_date('"$year"/"$month"/"$day":"$hour":"$min":"$sec"', 'yyyy/mm/dd:hh24:mi:ss')"
}

这看起来很麻烦,但您应该会发现它比调用外部子进程更快(更不用说六个,就像在您的原始示例中一样)。

【讨论】:

    【解决方案3】:

    为了提高效率,如果您只是转换字符串,我不会为此使用任何外部工具,只需更改您的 shell 并使用 bash:

    $ cat tst.sh
    #!/usr/bin/env bash
    
    funcion() {
        local date=$1
        if [[ "$date" =~ ^(....)(..)(..)(..)(..)(..)$ ]]; then
            printf "todate('%s/%s/%s:%s:%s:%s', 'yyyy/mm/dd/hh24:mi:ss')\n" \
                "${BASH_REMATCH[1]}" \
                "${BASH_REMATCH[2]}" \
                "${BASH_REMATCH[3]}" \
                "${BASH_REMATCH[4]}" \
                "${BASH_REMATCH[5]}" \
                "${BASH_REMATCH[6]}"
        fi
    }
    
    funcion '20191115103845'
    

    $ ./tst.sh
    todate('2019/11/15:10:38:45', 'yyyy/mm/dd/hh24:mi:ss')
    

    如果您在循环中执行此操作,那么将整个 shell 循环替换为一次调用 awk。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-06-13
      • 2018-10-07
      • 2015-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-15
      • 2020-09-14
      相关资源
      最近更新 更多