【问题标题】:sed backreferences and command interpolationsed 反向引用和命令插值
【发布时间】:2013-10-22 07:17:35
【问题描述】:

我遇到了一个有趣的问题,使用 only sed 将短月份字符串(例如“Oct”)替换为相应的数字值(例如“10”)给定一个字符串,如下所示:

Oct 14 09:23:35 some other input

通过sed 直接替换为:

14-10-2013 09:23:25 some other input

以下内容实际上与解决月份字符串->数字转换的琐碎问题无关;我更感兴趣的是了解我在尝试完全使用 sed 解决此问题时遇到的一些奇怪行为。

没有任何这种字符串替换的尝试(echo 语句代替了我脚本中的实际输入):

    ...
    MMM_DD_HH_mm_SS="([A-Za-z]{3}) ([0-9]{2}) (.+:[0-9]{2})"
    echo "Oct 14 09:23:35 some other input" | sed -r "s/$MMM_DD_HH_mm_ss (.+)/\2-\1-\3 \4/"

那么如何将反向引用\1转化为数字。当然可以考虑使用带有反向引用作为参数的命令插值:

...
TestFunc()
{
    echo "received input $1$1"
}
...
echo "Oct 14 09:23:35 some other input" | sed -r "s/$MMM_DD_HH_mm_ss (.+)/\2-$(TestFunc \\1)-\3 \4/"

TestFunc 将是 date 命令的变体(由下面的 Jotne 提出),其中 echo'd 日期时间组作为输入。这里的 TestFunc 只是一个 echo,因为我对函数认为是 $1 的值的行为更感兴趣。

在这种情况下,sedTestFunc 会产生输出:

14-received input OctOct-09:23:35 some other input

这表明 sed 实际上正在将反向引用 \1 插入命令替换 $(...) 以供 TestFunc 处理(它似乎接收 \1 作为局部变量 $1) .

但是,所有使用本地 $1 做更多事情的尝试都失败了。例如:

TestFunc()
{
    echo "processed: $1$1" > tmp.txt # Echo 1

    if [ "$1" == "Oct" ]; then
       echo "processed: 10"
    else
       echo "processed: $1$1"        # Echo 2
    fi
}

返回:

14-processed: OctOct-09:23:35 some other input

$1 已被替换为 Echo 2,但 tmp.txt 包含值 processed: \1\1;就好像反向引用没有被插入到命令替换中一样。更奇怪的是,if 条件因$1 != "Oct" 而失败,但它落入echo 语句,表明$1 = "Oct"。

我的问题是为什么反向引用插入在 Echo 2 的情况下有效但在 Echo 1 的情况下无效?我怀疑反向引用插入根本不起作用(鉴于TestFunc 中的if 语句失败),而是发生了一些微妙的事情,这使得替换在Echo 2 的情况下似乎正常工作;那是什么微妙之处?

解决方案

经过进一步思考,我相信我明白发生了什么:

  • \\1 作为文字 \1 传递给命令替换子例程/子函数。这就是子函数中的相等性测试失败的原因。

  • 但是echo 函数 正确地将字符串\\1 处理为$1。所以echo "aa$1aa" 将命令替换的结果返回到sed 作为aa\1aarev 等其他函数也“看到”$1\1

  • sed 然后在aa\1aa 中插入\1 作为Oct 或任何反向引用,以将aaOctaa 返回给用户。

由于正则表达式中的命令替换显然有效,如果sed\\1(或\1,等等)的值替换为反向引用在执行命令替换之前,那将是非常酷的$(...);这将显着增加 sed 的能力......

【问题讨论】:

    标签: bash sed


    【解决方案1】:

    这可能对你有用(GNU sed):

    s/$/\nJan01...Oct10Nov11Dec12/;s/(...) (..) (..:..:.. .*)\n.*\1(..).*/\2-\4-2013 \3/;s/\n.*//' file
    

    在行尾添加一个查找并使用反向引用来匹配它,确保在所有情况下都删除查找表。

    这是一个将反向引用传递给函数的示例:

    f(){ echo "x$1y$1z"; }
    echo a b c | sed -r  's/(.) (.) (.)/'"$(f \\2)"'/'
    

    返回:

    xbybz
    

    HTH

    【讨论】:

    • 非常感谢。你的建议和 Jotne 的建议都很有帮助,我真希望我明白为什么 ...$(TestFunc \\1)... 几乎 - 但不完全 - 将反向引用的值传递给 TestFunc 但我需要重新开始工作。
    • @bjsdaiyu 查看编辑示例,该示例将反向引用传递给替换的 RHS 中的函数。
    • 谢谢 - 有趣的是,并非所有版本的 sed 都允许 $(...) 执行。我不确定是 sed 还是 bash 正在执行 $(...)
    • @bjsdaiyu 在这种情况下 $(...) 在当前 bash 进程中被评估(跟随引号)。要在运行 sed 命令的过程中对其进行评估,您需要使用 e 标志并事先导出函数。
    【解决方案2】:

    使用正确的工具:

    date -d "Oct 14 09:23:35" +"%d-%m-%Y %H:%M:%S"
    14-10-2013 09:23:35
    

    日期确实会读取您的输入并将其转换为您喜欢的任何格式

    【讨论】:

    • 那不是很便携。在我的系统上,-d 设置系统的夏令时位。
    • 如果它适用于 OP,那很好。并非所有代码都需要可移植,但最好注意它可能不适用于所有系统。
    • @Jotne 感谢您的建议;我应该澄清(并将相应地编辑)sed 的实际输入(用echo 模拟)包括日期时间组(DTG)之后的文本,所以我不能简单地保存独立的结果date 命令;这就是为什么我在 sed 语句中探索date 命令,其中一个匹配的字段作为输入。
    • 如果需要回显可以这样操作:d=$(echo "Oct 14 09:23:35")date -d "$d" +"%d-%m-%Y %H:%M:%S"
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-27
    相关资源
    最近更新 更多