在我的bash脚本中,我有一个外部(从用户接收)字符串,我应该在sed模式中使用。

REPLACE="<funny characters here>"
sed "s/KEYWORD/$REPLACE/g"

我如何转义$REPLACE字符串,以便它被sed安全地接受为文字替换?

注意:KEYWORD是一个哑子字符串,没有匹配等。不是用户提供的。


当前回答

答案有很多……如果您不介意使用bash函数模式,下面是一个很好的答案。下面的目标是允许将sed与几乎任何参数一起作为关键字(F_PS_TARGET)或作为REPLACE (F_PS_REPLACE)使用。我们在许多情况下进行了测试,它似乎相当安全。下面的实现对KEYWORD和replace都支持制表符、换行符和单引号。

注意:这里的思想是使用sed来转义另一个sed命令的条目。

CODE

F_REVERSE_STRING_R=""
f_reverse_string() {
    : 'Do a string reverse.

    To undo just use a reversed string as STRING_INPUT.

    Args:
        STRING_INPUT (str): String input.

    Returns:
        F_REVERSE_STRING_R (str): The modified string.
    '

    local STRING_INPUT=$1
    F_REVERSE_STRING_R=$(echo "x${STRING_INPUT}x" | tac | rev)
    F_REVERSE_STRING_R=${F_REVERSE_STRING_R%?}
    F_REVERSE_STRING_R=${F_REVERSE_STRING_R#?}
}

# [Ref(s).: https://stackoverflow.com/a/2705678/3223785 ]
F_POWER_SED_ECP_R=""
f_power_sed_ecp() {
    : 'Escape strings for the "sed" command.

    Escaped characters will be processed as is (e.g. /n, /t ...).

    Args:
        F_PSE_VAL_TO_ECP (str): Value to be escaped.
        F_PSE_ECP_TYPE (int): 0 - For the TARGET value; 1 - For the REPLACE value.

    Returns:
        F_POWER_SED_ECP_R (str): Escaped value.
    '

    local F_PSE_VAL_TO_ECP=$1
    local F_PSE_ECP_TYPE=$2

    # NOTE: Operational characters of "sed" will be escaped, as well as single quotes.
    # By Questor
    if [ ${F_PSE_ECP_TYPE} -eq 0 ] ; then
    # NOTE: For the TARGET value. By Questor

        F_POWER_SED_ECP_R=$(echo "x${F_PSE_VAL_TO_ECP}x" | sed 's/[]\/$*.^[]/\\&/g' | sed "s/'/\\\x27/g" | sed ':a;N;$!ba;s/\n/\\n/g')
    else
    # NOTE: For the REPLACE value. By Questor

        F_POWER_SED_ECP_R=$(echo "x${F_PSE_VAL_TO_ECP}x" | sed 's/[\/&]/\\&/g' | sed "s/'/\\\x27/g" | sed ':a;N;$!ba;s/\n/\\n/g')
    fi

    F_POWER_SED_ECP_R=${F_POWER_SED_ECP_R%?}
    F_POWER_SED_ECP_R=${F_POWER_SED_ECP_R#?}
}

# [Ref(s).: https://stackoverflow.com/a/24134488/3223785 ,
# https://stackoverflow.com/a/21740695/3223785 ,
# https://unix.stackexchange.com/a/655558/61742 ,
# https://stackoverflow.com/a/11461628/3223785 ,
# https://stackoverflow.com/a/45151986/3223785 ,
# https://linuxaria.com/pills/tac-and-rev-to-see-files-in-reverse-order ,
# https://unix.stackexchange.com/a/631355/61742 ]
F_POWER_SED_R=""
f_power_sed() {
    : 'Facilitate the use of the "sed" command. Replaces in files and strings.

    Args:
        F_PS_TARGET (str): Value to be replaced by the value of F_PS_REPLACE.
        F_PS_REPLACE (str): Value that will replace F_PS_TARGET.
        F_PS_FILE (Optional[str]): File in which the replacement will be made.
        F_PS_SOURCE (Optional[str]): String to be manipulated in case "F_PS_FILE" was
    not informed.
        F_PS_NTH_OCCUR (Optional[int]): [1~n] - Replace the nth match; [n~-1] - Replace
    the last nth match; 0 - Replace every match; Default 1.

    Returns:
        F_POWER_SED_R (str): Return the result if "F_PS_FILE" is not informed.
    '

    local F_PS_TARGET=$1
    local F_PS_REPLACE=$2
    local F_PS_FILE=$3
    local F_PS_SOURCE=$4
    local F_PS_NTH_OCCUR=$5
    if [ -z "$F_PS_NTH_OCCUR" ] ; then
        F_PS_NTH_OCCUR=1
    fi

    local F_PS_REVERSE_MODE=0
    if [ ${F_PS_NTH_OCCUR} -lt -1 ] ; then
        F_PS_REVERSE_MODE=1
        f_reverse_string "$F_PS_TARGET"
        F_PS_TARGET="$F_REVERSE_STRING_R"
        f_reverse_string "$F_PS_REPLACE"
        F_PS_REPLACE="$F_REVERSE_STRING_R"
        f_reverse_string "$F_PS_SOURCE"
        F_PS_SOURCE="$F_REVERSE_STRING_R"
        F_PS_NTH_OCCUR=$((-F_PS_NTH_OCCUR))
    fi

    f_power_sed_ecp "$F_PS_TARGET" 0
    F_PS_TARGET=$F_POWER_SED_ECP_R
    f_power_sed_ecp "$F_PS_REPLACE" 1
    F_PS_REPLACE=$F_POWER_SED_ECP_R

    local F_PS_SED_RPL=""
    if [ ${F_PS_NTH_OCCUR} -eq -1 ] ; then
    # NOTE: We kept this option because it performs better when we only need to replace
    # the last occurrence. By Questor

        # [Ref(s).: https://linuxhint.com/use-sed-replace-last-occurrence/ ,
        # https://unix.stackexchange.com/a/713866/61742 ]
        F_PS_SED_RPL="'s/\(.*\)$F_PS_TARGET/\1$F_PS_REPLACE/'"
    elif [ ${F_PS_NTH_OCCUR} -gt 0 ] ; then
        # [Ref(s).: https://unix.stackexchange.com/a/587924/61742 ]
        F_PS_SED_RPL="'s/$F_PS_TARGET/$F_PS_REPLACE/$F_PS_NTH_OCCUR'"
    elif [ ${F_PS_NTH_OCCUR} -eq 0 ] ; then
        F_PS_SED_RPL="'s/$F_PS_TARGET/$F_PS_REPLACE/g'"
    fi

    # NOTE: As the "sed" commands below always process literal values for the "F_PS_TARGET"
    # so we use the "-z" flag in case it has multiple lines. By Quaestor
    # [Ref(s).: https://unix.stackexchange.com/a/525524/61742 ]
    if [ -z "$F_PS_FILE" ] ; then
        F_POWER_SED_R=$(echo "x${F_PS_SOURCE}x" | eval "sed -z $F_PS_SED_RPL")
        F_POWER_SED_R=${F_POWER_SED_R%?}
        F_POWER_SED_R=${F_POWER_SED_R#?}
        if [ ${F_PS_REVERSE_MODE} -eq 1 ] ; then
            f_reverse_string "$F_POWER_SED_R"
            F_POWER_SED_R="$F_REVERSE_STRING_R"
        fi
    else
        if [ ${F_PS_REVERSE_MODE} -eq 0 ] ; then
            eval "sed -i -z $F_PS_SED_RPL \"$F_PS_FILE\""
        else
            tac "$F_PS_FILE" | rev | eval "sed -z $F_PS_SED_RPL" | tac | rev > "$F_PS_FILE"
        fi
    fi

}

模型

f_power_sed "F_PS_TARGET" "F_PS_REPLACE" "" "F_PS_SOURCE"
echo "$F_POWER_SED_R"

例子

f_power_sed "{ gsub(/,[ ]+|$/,\"\0\"); print }' ./  and eliminate" "[ ]+|$/,\"\0\""  "" "Great answer (+1). If you change your awk to awk '{ gsub(/,[ ]+|$/,\"\0\"); print }' ./  and eliminate that concatenation of the final \", \" then you don't have to go through the gymnastics on eliminating the final record. So: readarray -td '' a < <(awk '{ gsub(/,[ ]+/,\"\0\"); print; }' <<<\"$string\") on Bash that supports readarray. Note your method is Bash 4.4+ I think because of the -d in readar"
echo "$F_POWER_SED_R"

如果您只想将参数转义到sed命令

模型

# "TARGET" value.
f_power_sed_ecp "F_PSE_VAL_TO_ECP" 0
echo "$F_POWER_SED_ECP_R"

# "REPLACE" value.
f_power_sed_ecp "F_PSE_VAL_TO_ECP" 1
echo "$F_POWER_SED_ECP_R"

重要提示:如果KEYWORD和/或replace replace的字符串包含制表符或换行符,您将需要在“sed”命令中使用“-z”标志。详情请点击这里。

例子

f_power_sed_ecp "{ gsub(/,[ ]+|$/,\"\0\"); print }' ./  and eliminate" 0
echo "$F_POWER_SED_ECP_R"
f_power_sed_ecp "[ ]+|$/,\"\0\"" 1
echo "$F_POWER_SED_ECP_R"

注意:上面的f_power_sed_ecp和f_power_sed函数是作为ez_i项目的一部分完全免费提供的-轻松创建shell脚本安装程序!

其他回答

基于钢琴龙的正则表达式,我做了一个bash函数,逃脱关键字和替换。

function sedeasy {
  sed -i "s/$(echo $1 | sed -e 's/\([[\/.*]\|\]\)/\\&/g')/$(echo $2 | sed -e 's/[\/&]/\\&/g')/g" $3
}

下面是如何使用它:

sedeasy "include /etc/nginx/conf.d/*" "include /apps/*/conf/nginx.conf" /etc/nginx/nginx.conf

标准建议:使用perl:)

echo KEYWORD > /tmp/test

REPLACE="<funny characters here>"
perl -pi.bck -e "s/KEYWORD/${REPLACE}/g" /tmp/test
cat /tmp/test

使用awk -它更干净:

$ awk -v R='//addr:\\file' '{ sub("THIS", R, $0); print $0 }' <<< "http://file:\_THIS_/path/to/a/file\\is\\\a\\ nightmare"
http://file:\_//addr:\file_/path/to/a/file\\is\\\a\\ nightmare

以下是我找到的转义码:

* = \x2a
( = \x28
) = \x29

" = \x22
/ = \x2f
\ = \x5c

' = \x27
? = \x3f
% = \x25
^ = \x5e

下面是我之前用过的一个AWK的例子。它是一个AWK,打印新的AWKS。AWK和SED相似,可能是一个很好的模板。

ls | awk '{ print "awk " "'"'"'"  " {print $1,$2,$3} " "'"'"'"  " " $1 ".old_ext > " $1 ".new_ext"  }' > for_the_birds

这看起来有点过分,但不知何故,引号的组合可以保持“打印为字面量”。然后,如果我没记错的话,变量只是用引号括起来:“$1”。试试吧,让我知道它对SED的效果如何。