8

シェルスクリプトを書いているとき、私は自分の時間のほとんど(特にデバッグ時)を引数処理に費やしていることに気付くことがよくあります。私が作成または保守しているスクリプトの多くは、入力の解析とサニタイズの80%以上を簡単に実行できます。これをPythonスクリプトと比較すると、argparseがほとんどのうなり声を処理し、複雑なオプション構造とサニタイズ/文字列解析動作を簡単に構築できます。

したがって、ユーザーが指定した引数についてこれ以上心配することなく、Pythonにこの手間のかかる作業を実行させ、シェルスクリプトでこれらの単純化およびサニタイズされた値を取得できるようにしたいと思います。

具体的な例を挙げると、私が作業しているシェルスクリプトの多くは、特定の順序で引数を受け入れるように定義されています。呼び出すことはできますstart_server.sh --server myserver --port 80が、start_server.sh --port 80 --server myserver失敗しますYou must specify a server to start.-解析コードは非常に簡単になりますが、直感的ではありません。

したがって、ファーストパスソリューションは、Pythonに引数を取り、それらを並べ替えて(パラメーターを隣に置いて)、並べ替えられた引数を返すのと同じくらい簡単なものにすることができます。そのため、シェルスクリプトは引き続き解析とサニタイズを行いますが、ユーザーは、シェルスクリプトがネイティブに受け入れるよりもはるかに多くの任意のコンテンツを入力できます。たとえば、次のようになります。

# script.sh -o -aR --dir /tmp/test --verbose

#!/bin/bash

args=$(order.py "$@")
# args is set to "-a --dir /tmp/test -o -R --verbose"

# simpler processing now that we can guarantee the order of parameters

ここには明らかな制限がいくつかあります。特にparse.py、引数を含む最後のオプションとインデックス付き引数の開始を区別できませんが、それほどひどいようには見えません。

だからここに私の質問があります:1)サニタイズ後に私のbashスクリプトの残りの部分からアクセスできるbashよりも強力なものでCLI解析を有効にする既存の(Pythonが望ましい)ユーティリティはあります? ?私が気付いていない問題や落とし穴、またはより良い解決策はありますか?実装を共有してみませんか?


1つの(非常に中途半端な)アイデア:

#!/bin/bash

# Some sort of simple syntax to describe to Python what arguments to accept
opts='
"a", "append", boolean, help="Append to existing file"
"dir", str, help="Directory to run from"
"o", "overwrite", boolean, help="Overwrite duplicates"
"R", "recurse", boolean, help="Recurse into subdirectories"
"v", "verbose", boolean, help="Print additional information"
'

# Takes in CLI arguments and outputs a sanitized structure (JSON?) or fails
p=$(parse.py "Runs complex_function with nice argument parsing" "$opts" "$@")
if [ $? -ne 0 ]; exit 1; fi # while parse outputs usage to stderr

# Takes the sanitized structure and an argument to get
append=$(arg.py "$p" append)
overwrite=$(arg.py "$p" overwrite)
recurse=$(arg.py "$p" recurse)
verbose=$(arg.py "$p" verbose)

cd $(python arg.py "$p" dir)

complex_function $append $overwrite $recurse $verbose

2行のコードと、期待する引数の簡潔な説明、そして実際のスクリプトの動作について説明します。たぶん私は頭がおかしいのかもしれませんが、それは私が今しなければならないことよりもずっといいようです


シェルスクリプトの引数の解析や、CLI引数の簡単な解析に関するこのwikiページのようなものを見てきましたが、これらのパターンの多くは不格好でエラーが発生しやすいと感じており、特にシェルスクリプトを作成するたびに再実装する必要はありません。 Python、Javaなどには、このような優れた引数処理ライブラリがあります。

4

4 に答える 4

2

目標を達成するために、bashの連想配列を利用できる可能性があります。

declare -A opts=($(getopts.py $@))
cd ${opts[dir]}
complex_function ${opts[append]}  ${opts[overwrite]} ${opts[recurse]} \
                 ${opts[verbose]} ${opts[args]}

これを機能させるにgetopts.pyは、引数を解析してサニタイズするPythonスクリプトである必要があります。次のような文字列を出力する必要があります。

[dir]=/tmp
[append]=foo
[overwrite]=bar
[recurse]=baz
[verbose]=fizzbuzz
[args]="a b c d"

オプションが適切に解析およびサニタイズできることを確認するための値を取っておくこともできます。

から戻ったgetopts.py

[__error__]=true

bashスクリプトに追加:

if ${opts[__error__]}; then
    exit 1
fi

からの終了コードを使用したい場合は、次のgetopts.pyコマンドで遊ぶことができますeval

getopts=$(getopts.py $@) || exit 1
eval declare -A opts=($getopts)

または:

getopts=$(getopts.py $@)
if [[ $? -ne 0 ]]; then
    exit 1;
fi
eval declare -A opts=($getopts)
于 2012-07-27T06:01:17.110 に答える
2

編集: 私は(まだ)それを使用していませんが、今日この回答を投稿する場合は、以下に説明するようなカスタムアプローチではなく、https://github.com/docopt/docoptsをお勧めします。


私が望むことのほとんどを実行する短いPythonスクリプトをまとめました。私はまだそれが生産品質であるとは確信していません(特にエラー処理が不足しています)が、何もないよりはましです。フィードバックをお待ちしております。

setビルトインを利用して位置引数を再割り当てし、スクリプトの残りの部分が必要に応じてそれらを処理できるようにします。

bashparse.py

#!/usr/bin/env python

import optparse, sys
from pipes import quote

'''
Uses Python's optparse library to simplify command argument parsing.

Takes in a set of optparse arguments, separated by newlines, followed by command line arguments, as argv[2] and argv[3:]
and outputs a series of bash commands to populate associated variables.
'''

class _ThrowParser(optparse.OptionParser):
    def error(self, msg):
        """Overrides optparse's default error handling
        and instead raises an exception which will be caught upstream
        """
        raise optparse.OptParseError(msg)

def gen_parser(usage, opts_ls):
    '''Takes a list of strings which can be used as the parameters to optparse's add_option function.
    Returns a parser object able to parse those options
    '''
    parser = _ThrowParser(usage=usage)
    for opts in opts_ls:
        if opts:
            # yes, I know it's evil, but it's easy
            eval('parser.add_option(%s)' % opts)
    return parser

def print_bash(opts, args):
    '''Takes the result of optparse and outputs commands to update a shell'''
    for opt, val in opts.items():
        if val:
            print('%s=%s' % (opt, quote(val)))
    print("set -- %s" % " ".join(quote(a) for a in args))

if __name__ == "__main__":
    if len(sys.argv) < 2:
        sys.stderr.write("Needs at least a usage string and a set of options to parse")
        sys.exit(2)
    parser = gen_parser(sys.argv[1], sys.argv[2].split('\n'))

    (opts, args) = parser.parse_args(sys.argv[3:])
    print_bash(opts.__dict__, args)

使用例:

#!/bin/bash

usage="[-f FILENAME] [-t|--truncate] [ARGS...]"
opts='
"-f"
"-t", "--truncate",action="store_true"
'

echo "$(./bashparse.py "$usage" "$opts" "$@")"
eval "$(./bashparse.py "$usage" "$opts" "$@")"

echo
echo OUTPUT

echo $f
echo $@
echo $0 $2

これは、次のように実行すると./run.sh one -f 'a_filename.txt' "two' still two" three、次のように出力されます(内部位置変数がまだ正しいことに注意してください)。

f=a_filename.txt
set -- one 'two'"'"' still two' three

OUTPUT
a_filename.txt
one two' still two three
./run.sh two' still two

デバッグ出力を無視すると、強力な引数パーサーを構築するために約4行が表示されます。考え?

于 2012-08-13T22:19:06.703 に答える
2

まったく同じニーズを抱えていたので、最終的にbash用のoptparseに触発されたパーサーを作成しました(実際には内部でpythonを使用しています)。あなたはここでそれを見つけることができます:

https://github.com/carlobaldassi/bash_optparse

簡単な説明については、下部にあるREADMEを参照してください。次の場所で簡単な例を確認してください。

https://github.com/carlobaldassi/bash_optparse/blob/master/doc/example_script_simple

私の経験から、それは非常に堅牢で(私は非常に妄想的です)、機能が豊富であるなど、スクリプトで頻繁に使用しています。他の人にも役立つといいのですが。フィードバック/貢献を歓迎します。

于 2012-08-19T12:15:55.417 に答える
0

私の質問の元々の前提は、Pythonへの委任が引数の解析を単純化するための正しいアプローチであると想定しています。getopts言語要件を削除すると、Bashで実際にまともな仕事*を行うことができますeval

main() {
  local _usage='foo [-a] [-b] [-f val] [-v val] [args ...]'
  eval "$(parse_opts 'f:v:ab')"
  echo "f=$f v=$v a=$a b=$b -- $#: $*"
}

main "$@"

の実装parse_optsこの要点にありますが、基本的なアプローチは、オプションをlocal変数に変換して、通常のように処理できるようにすることです。すべての標準getoptsボイラープレートは隠されており、エラー処理は期待どおりに機能します。

local関数内で変数を使用するためparse_opts、コマンドライン引数に役立つだけでなく、スクリプト内の任意の関数で使用できます。


getopts* Bashはかなり限定されたパーサーであり、1文字のオプションしかサポートしていないため、「まともな仕事」と言います。エレガントで表現力豊かなCLIは、Pythonなどの他の言語でもより適切に実装されます。しかし、適度に小さい関数やスクリプトの場合、これは複雑さや肥大化をあまり追加することなく、優れた中間点を提供します。

于 2020-04-13T09:43:59.703 に答える