スペースを保持しながらコマンドライン引数を変更する

スペースを保持しながらコマンドライン引数を変更する

実行中のダウンストリームコマンドに合わせて自動的にフォーマットするために渡されるコマンドライン引数をオプションで置き換えたいと思います。論争には空白があり、それが議論の内容です。

私は現在これをやっています:

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=をに置き換えるには--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

置換される最後の一致と呼ばれる点と一致しない場合は、最初に挿入される点以外は同じです。

1つのパラメータを複数のパラメータに置き換えることもできます。

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=、最後に1つを追加します。

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

4.4+でbashパラメータを一部変換するもう1つのオプションは、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[@]}"

sedSEARCHとREPLACEをいくつかのエスケープ処理よりも優れています。


1 例外は累積されます。たとえば、いくつかのユーティリティよりも--quiet --quiet静かな、または複数の出力フィールドを指定する--quietために使用されます。場合によっては、順序が重要です。たとえば、に変更すると、に変更するのと同じ操作が実行されない可能性があります。-o pid -o ppidps--description=foo --no-description--description=bar --no-description--description=foo --no-description --description=bar

答え2

コードにはいくつかの問題があります。その1つは引用符なしで使用することです$*。その後、シェルは生の引数を単語のすべての文字$IFS(デフォルトでは空白、タブ、改行)に分割し、結果の単語にファイル名のグロービングを適用します。スペース、タブ、または改行文字を含む複数の引数をサポートしたい場合は、単一の文字列になるため、引用することも$*望ましくありません。読みやすくするためにスペースを入れて各引数に1つだけを生成するので、"$*"usingに切り替えるのは"$@"役に立ちません。echosed

echoバックスラッシュシーケンス(たとえば、および)を含む文字列は、シェルと現在の設定に従って\n特別に処理できます。\t一部のシェルにはecho -n出力がない可能性があります-n(たとえば、他の問題のある文字列がある可能性があります-e)。

これをテキストとして処理したい場合(パラメータは複数行の文字列にすることができます)、修正パラメータをsed使用すると単一のパラメータに対して機能できますが、この場合、すべてのパラメータに一度にいくつかの編集スクリプトを適用するため、誤って実行される可能性があります。あります。

ただし、結果の文字列を分割することは、コマンドで使用される引用符ではなく置換ですset。これにより結果が再分割され、sedファイル名のグロービングが結果に再適用されます。

変更するコマンドラインオプションを解析する必要があります。つまり、パラメータを繰り返して、変更したいパラメータを変更します。

次のスクリプトは、長いオプションの各インスタンスのオプション引数の先頭にsh文字列を追加します。長いオプションの後にスペースがある場合(たとえば)、呼び出しスクリプトを使用するのと同じように、スクリプトは最後に変更される前にaに書き換えられます。hello ---description--description "my thing"=--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=value からプレフィックス文字列を削除し、元の$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上記と同じパラメータを使用してテストを実行します(2番目のインスタンスはパターンと一致しないため変更されません--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[[ ... ]]代わりに、シェルパターンマッチングを使用し、case ... esac配列を使用してループ中に変更できるパラメータを保持する上記の短い2番目のスクリプトのバリアントです。

#!/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

コマンド置換の出力は、シェルから単一の文字列として読み取られるバイトストリームです。単一のパラメーターの一部でなければならないスペースと、パラメーターを分離する必要があるスペースに関する情報はありません。

ただし、GNUツールセットとBashがあり、sedを使用して引数を処理したい場合は、引数(Bashおよび他のほとんどのシェルの変数と同様)はC文字列であり、NULバイトを含めることができないという事実を使用できます。 GNU sedはNULを行区切り文字として使用でき、BashはNULで終わる文字列のセットをreadarray

たとえば、次のようになります。

# 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コマンドはsed -zinの内容を$SOME_VARIABLE末尾に追加します。=--description=...

これは$SOME_VARIABLEsedコマンドに含まれているため、通常sedに特別なエントリ(/&\改行文字など)がコマンドを中断することに注意することが重要です。また、printf位置パラメータがなくても少なくとも1つの要素が印刷されるため、これが問題になる場合は、要素全体をif [[ "$#" -gt 0 ]]; then ...

関連情報