我有一个脚本,其函数源自其他脚本。我试图逐行进行,但 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
||
如果前一个命令的退出代码非零(假/错误),则运行以下命令。[ -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
不带参数将返回最后一个要执行的命令的退出代码,因此调用者将能够检测成功或失败。作者似乎不明白引号在 shell 中的用途,或者它是如何工作的,或者大括号,例如
${var}
,是不是引用的替代品,例如"$var"
.看$VAR 与 ${VAR} 以及引用或不引用和为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?。作者
$2
在应该引用的时候始终没有引用(文件名可以包含空格、制表符、换行符和 shell 元字符,如果不加引号使用,这些字符可能会破坏 shell 脚本)。三个 if/elif 测试检查第一个参数 (
$1
) 是否包含=
符号、空格或两个制表符。它根据找到的版本运行 sed 脚本的三个略有不同的版本之一。sed 脚本都会检查
=
文件中是否存在其后跟 、空格或两个制表符的“key”变体,并可以选择用#
.如果找到匹配项,它将用 的值替换它$1
,并运行一个循环来读取和输出文件的其余部分。我认为这里的目的是仅替换第一次出现的key=value
.如果未找到匹配项(因此未执行循环和退出),则会将其附加
$1
到文件末尾。作者似乎对此想得太多了(或者可能想得不够)。如果
$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=123
和foobar=123
。单词\b
边界标记用于执行此操作。在 sed 中,您可以使用\<
和\>
包围键模式。顺便说一句,该
X unless Y
构造只是if not Y, then do X
.它可以被写成if (! s/^#?.*$key.*$/$r/m) {s/\z/$r\n/}
并且仍然可以完全相同地工作。函数名称
lineinfile
非常通用,但它的作用却非常具体。更糟糕的是,该名称与该函数的实际用途不匹配,甚至不暗示。这通常被认为是不好的做法。