替换命令行参数同时保留空格

替换命令行参数同时保留空格

我想有选择地替换正在传递的命令行参数,以自动格式化它以适应正在执行的下游命令。论证会有空格,这就是争论的焦点。

我目前正在这样做:

set -- $(echo $* | sed -e "s/$_ARG/--description=\"$_ID - $_SUMMARY\"/")

新的论点--description="$_ID - $_SUMMARY"被分裂了。

我运行下游命令:

<cmd> "$@"

我可能有任意数量的参数,但示例用例是:

activity --description='handle null'

到:

activity --description='$SOME_VARIABLE - handle null'

最终,当我运行下游命令时,即使使用“$@”,它也已经在那里分割,所以它不能按我的预期工作。它最终就像

activity --description=value - handle null

--description=value-handlenull然后被视为单独的参数。

答案1

在 ksh93、zsh 或 bash 中,您可以执行以下操作:

set -- "${@/#--description=*/--description=$NEW_DESCRIPTION}"

要将开始位置参数(#将模式锚定在开头)替换为--description=with --description=<contents-of-NEW_DESCRIPTION-variable

使用ksh93,可以缩短为:

set -- "${@/#@(--description=)*/\1$NEW_DESCRIPTION}"

相当于zsh -o extendedglob

set -- "${@/#(#b)(--description=)*/$match[1]$NEW_DESCRIPTION}"

但也许你也可以这样做:

set -- "$@" "--description=$NEW_DESCRIPTION"

大多数实用程序接受多次使用相同的选项,并且最后一次出现的选项优先。例如:

$ echo x | grep -H --label=foo --label=bar .
bar:x

在 中zsh,您可以执行以下操作:

argv[(i)--description=*]=--description=$NEW_DESCRIPTION

替换以 开头的第一个参数--description=--description=<contents-of-NEW_DESCRIPTION-variable或者如果没有找到,则将其添加为新参数。

或者:

argv[(I)--description=*]=--description=$NEW_DESCRIPTION

相同,只是它是被替换的最后一个匹配项,并且如果找不到则将其插入到开头。

也可以用多个参数替换一个参数:

argv[(i)--description=*]=(--description=$NEW_DESCRIPTION --other-args)

--description或者将参数及其后续参数替换为--description=$NEW_DESCRIPTION

argv[n=argv[(i)--description],n+1]=--description=$NEW_DESCRIPTION

--description(再次,在元素中找不到if 在末尾添加)。

去除全部参数以以下形式开头--description=并在末尾加一:

set -- "${@:#--description=*}" --description=$NEW_DESCRIPTION

bash4.4+ 中,对参数进行一些转换的另一种选择是诉诸perl,将位置参数作为参数传递,并将它们作为 NUL 分隔列表读回(因为bash变量无论如何都不能包含 NUL):

readarray -td '' newargs < <(
  SEARCH="$_ARG" REPLACE='--description=something' perl -l0e '
    for (@ARGV) {
      s/\Q$ENV{SEARCH}\E/$ENV{REPLACE}/;
      print;
    }' -- "$@"
)
set -- "${newargs[@]}"

sed比您必须对 SEARCH 和 REPLACE 进行一些转义更合适。


1 累积式除外,例如比某些实用程序--quiet --quiet更安静的--quiet,或用于-o pid -o ppid指定ps多个输出字段的 。在某些情况下,顺序很重要。例如,更改--description=foo --no-description--description=bar --no-description可能不会与更改为 执行相同的操作--description=foo --no-description --description=bar

答案2

您的代码中存在一些问题。其中之一是使用$*不带引号的,这将导致 shell 将原始参数拆分为单词中的任何字符$IFS(默认情况下是空格、制表符、换行符),并对生成的单词应用文件名通配。如果您想要支持包含空格、制表符或换行符的多个参数,那么引用$*as也不是您想要的,因为这将是单个字符串。"$*"切换到 using"$@"不会有帮助,因为echo只会产生一个每个参数,中间有空格以供sed阅读。

echo可以对任何包含反斜杠序列(如\n和)的字符串进行特殊处理\t,具体取决于 shell 及其当前设置。在某些 shell 中,echo -n可能不会输出-n(也可能存在其他有问题的字符串,例如-e)。

如果您愿意将其视为文本(参数可能是多行字符串),则使用sed修改参数可能适用于单个参数,但在这种情况下,您一次对所有参数应用一些编辑脚本,这可能失火。

然而,分割结果字符串的是与 一起使用的命令替换的非引用set。这会重新分割结果sed并再次对结果应用文件名通配。

您将需要解析要修改的命令行选项。简而言之,循环遍历参数,并修改您想要修改的参数。

以下sh脚本将字符串添加hello -到长选项的每个实例的选项参数的开头--description。如果长选项后紧跟一个空格(如 中所示) ,则在将其修改为最终的 之前,--description "my thing"将使用 a 重写=该脚本,就好像使用 调用脚本一样。--description="my thing"--description="hello - my thing"

#!/bin/sh

SOME_VARIABLE=hello

skip=false

for arg do
    if "$skip"; then
        skip=false
        continue
    fi

    # Re-write separate option-argument with "=".
    # This consumes an extra argument, so need to skip
    # next iteration of the loop.
    case $arg in
        --description)
            arg=--description=$2
            shift
            skip=true
    esac

    # Add the value "$SOME_VARIABLE - " to the start of the
    # option-argument of the --description long option.
    case $arg in
        --description=*)
            arg=--description="$SOME_VARIABLE - ${arg#--description=}"
    esac

    # Put the (possibly modified) argument back at the end
    # of the list of arguments and shift off the first item.
    set -- "$@" "$arg"
    shift
done

# Print out the list of arguments as strings within "<...>":
printf '<%s>\n' "$@"

${arg#--description=}--description=从 的值中删除前缀字符串$arg,留下原始选项参数字符串。

示例运行:

$ sh ./script -a -b --description="my thing" -c -d --description "your thing" -e
<-a>
<-b>
<--description=hello - my thing>
<-c>
<-d>
<--description=hello - your thing>
<-e>

如果您总是期望有长选项及其由字符分隔的选项参数=,则代码可能会显着简化:

#!/bin/sh

SOME_VARIABLE=hello

for arg do
    # Add the value "$SOME_VARIABLE - " to the start of the
    # option-argument of the --description long option.
    case $arg in
        --description=*)
            arg=--description="$SOME_VARIABLE - ${arg#--description=}"
    esac

    # Put the (possibly modified) argument back at the end
    # of the list of arguments and shift off the first item.
    set -- "$@" "$arg"
    shift
done

printf '<%s>\n' "$@"

--description使用与上面相同的参数进行测试运行(不会修改 的第二个实例,因为它与模式不匹配--description=*):

$ sh ./script -a -b --description="my thing" -c -d --description "your thing" -e
<-a>
<-b>
<--description=hello - my thing>
<-c>
<-d>
<--description>
<your thing>
<-e>

bash上面较短的第二个脚本的变体,使用 shell 模式匹配来代替[[ ... ]]case ... esac并使用数组来保存循环过程中可能修改的参数:

#!/bin/bash

SOME_VARIABLE=hello

args=()
for arg do
    if [[ $arg == --description=* ]]; then
        arg=--description="$SOME_VARIABLE - ${arg#--description=}"
    fi

    args+=( "$arg" )
done

set -- "${args[@]}"

printf '<%s>\n' "$@"

答案3

命令替换的输出是字节流,在 shell 中读取为单个字符串。那里没有任何信息哪个空格应该是单个参数的一部分,以及哪个空格应该将参数彼此分隔开。

但是,如果您有 GNU 工具集和 Bash,并且想要使用 sed 来处理参数,则可以使用以下事实:参数(与 Bash 和大多数其他 shell 中的任何变量一样)是 C 字符串,并且不能包含 NUL字节。 GNU sed 可以使用 NUL 作为行分隔符,Bash 可以将一组以 NUL 结尾的字符串读取到带有readarray. (您不能使用分词来分割 NUL 上命令替换的输出。)

例如这样的事情:

# test arguments
set -- activity --description='handle null'
SOME_VARIABLE="foo bar"

# prints args NUL-terminated, run through sed, read them in to the array 'new_args'
new_args=()
readarray -t -d '' new_args < <(printf "%s\0" "$@" | sed -ze "s/^--description=/&$SOME_VARIABLE - /")
# move the values from the array to the positional parameters $1, $2 ...
set -- "${new_args[@]}"

printf "%s\0" "$@"打印末尾带有 NUL 的位置参数,告诉 sed 使用 NUL 作为行分隔符,并且 sed 命令将insed -z的内容添加到$SOME_VARIABLE后面。=--description=...

请注意,它$SOME_VARIABLE嵌入在 sed 命令中,因此通常需要注意,即 sed 的任何特殊内容(如/&\换行符)都会破坏该命令。另外,printf即使没有位置参数,也会打印至少一个元素,因此如果这是一个问题,请将整个元素包装在if [[ "$#" -gt 0 ]]; then ....

相关内容