无法理解 sed 命令在此脚本中执行的操作

无法理解 sed 命令在此脚本中执行的操作

我有一个脚本,其函数源自其他脚本。我试图逐行进行,但 sed 正则表达式太复杂了。

#!/usr/bin/env bash

# This function will update the value associated with a key,
# remove a comment from the beginning of the line,
# or append the key value pair to the end of the file if the key is not found

# To use this function in a script
# source this script:
#   . lineinfile
#
# To invoke the function:
#   lineinfile "key=value" "filename"
# OR
#   lineinfile "key value" "filename"


lineinfile() {
  [ -s $2 ] || echo "${1}" >> ${2}
  if [[ "$1" == *"="* ]]; then
    sed -i -e "/^#\?.*\(${1%%=*}\).*/{s@@${1}@;:a;n;ba;q}" -e "\$a${1}" ${2}
  elif [[ "$1" == *" "* ]]; then
    sed -i -e "/^#\?.*\(${1%% *}\).*/{s@@${1}@;:a;n;ba;q}" -e "\$a${1}" ${2}
  elif [[ "$1" == *$'\t\t'* ]]; then
    sed -i -e "/^#\?.*\(${1%%$'\t\t'*}\).*/{s@@${1}@;:a;n;ba;q}" -e "\$a${1}" ${2}
  fi
}

函数的第一行[ -s $2 ] || echo "${1}" >> ${2}- 检查第二个位置参数是否是一个存在且大小非零的文件,然后将 的内容附加到文件$1末尾。$2为什么||用在这里呢?

我真的不确定 if-elif 块正在测试什么。if 条件中*"="* *" "*and试图匹配什么?*$'\t\t'*此外,我不知道 sed 命令在做什么。正则表达式很复杂。任何人都可以为我分解 sed 命令吗?

答案1

  1. || 如果前一个命令的退出代码非零(假/错误),则运行以下命令。

    [ -s $2 ] || echo "${1}" >> ${2}
    

    相当于:

    if ! [ -s $2 ] ; then echo "${1}" >> ${2} ; fi
    

    如果文件不存在或为空,则其中任何一个都会将第一个 arg ( $1) 附加到文件 ( ) 中。$2顺便说一句,请参阅下面关于引用的下一点 2。

    顺便说一句,该函数可以(并且应该)在此时返回。无需运行sed该文件,因为它现在包含所需的值。例如(并使用printf而不是echo- 请参阅为什么 printf 比 echo 更好?):

    [ -s "$2" ] || printf '%s\n' "$1" >> "$2" && return
    

    或更好:

    if ! [ -s "$2" ] ; then printf '%s\n' "$1" >> "$2" ; return ; fi
    

    对于第一种形式,return只有前一个命令 ( printf) 成功时才会执行。无论printf成功与否,第二种形式总是返回。没有充分的理由使return依赖项依赖于printf后续项(这只是这种简写 if/then/fi 结构中“链接”命令的常见习惯用法)。大多数时候,printf会成功,但有时(例如权限或磁盘已满)会失败。如果失败,该函数应该无论如何返回 -sed脚本也会失败,所以没有必要运行它们。顺便说一句,return不带参数将返回最后一个要执行的命令的退出代码,因此调用者将能够检测成功或失败。

  2. 作者似乎不明白引号在 shell 中的用途,或者它是如何工作的,或者大括号,例如${var},是不是引用的替代品,例如"$var".看$VAR 与 ${VAR} 以及引用或不引用为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?

    作者$2在应该引用的时候始终没有引用(文件名可以包含空格、制表符、换行符和 shell 元字符,如果不加引号使用,这些字符可能会破坏 shell 脚本)。

  3. 三个 if/elif 测试检查第一个参数 ( $1) 是否包含=符号、空格或两个制表符。它根据找到的版本运行 sed 脚本的三个略有不同的版本之一。

    sed 脚本都会检查=文件中是否存在其后跟 、空格或两个制表符的“key”变体,并可以选择用#.如果找到匹配项,它将用 的值替换它$1,并运行一个循环来读取和输出文件的其余部分。我认为这里的目的是仅替换第一次出现的key=value.

    如果未找到匹配项(因此未执行循环和退出),则会将其附加$1到文件末尾。

  4. 作者似乎对此想得太多了(或者可能想得不够)。如果$1首先将其分为键变量和值变量,则只需一个 sed 脚本即可完成此操作。即首先提取数据并将其“规范化”为$1一致的形式,然后在一个sed脚本中使用它。

    或者只是用 perl 重写整个内容,当您想做的事情需要 shell 和 sed 的优点(并且具有比大多数版本的 sed 更好、更强大的正则表达式引擎)时,这是一个不错的选择。if/elif/elif/fi例如,将函数的一部分替换为:

     perl -0777 -i -pe '
          BEGIN { $r = shift; ($key,$val) = split /(=| |\t\t)/, $r };
          s/\z/$r\n/ unless (s/^#?.*\b$key\b.*$/$r/m)' key=value filename
    

    这个 Perl 版本适用于所有三种变体(=、空格、两个制表符 - 后两个需要引用)。它立即吞入整个文件(-0777选项),并尝试执行多行搜索和替换操作(/m正则表达式修饰符)。如果该操作失败,则会将第一个参数(加上换行符)附加到文件末尾 ( \z)。它还修复了原版中的一个错误,该错误无法区分例如 foo=123foobar=123。单词\b边界标记用于执行此操作。在 sed 中,您可以使用\<\>包围键模式。

    顺便说一句,该X unless Y构造只是if not Y, then do X.它可以被写成if (! s/^#?.*$key.*$/$r/m) {s/\z/$r\n/}并且仍然可以完全相同地工作。

  5. 函数名称lineinfile非常通用,但它的作用却非常具体。更糟糕的是,该名称与该函数的实际用途不匹配,甚至不暗示。这通常被认为是不好的做法。

相关内容