web-dev-qa-db-ja.com

入力をループしてbashでコマンドを実行する汎用関数?

入力をループしてコマンドを実行する関数を作成しようとしていました-それらがどのように区切られているかに関係なく。

function loop {
    # Args
    #     1: Command
    #     2: Inputs
    for input in "$2" ; do                     
        $1 $input
    done
}
declare -a arr=("1" "2" "3")

$ loop echo "$arr[@]"
1

$ loop echo 1 2 3
1

$ loop echo $arr
1

ただし、 this の回答に従って、for .. in ..は配列に対して機能します。

for item in "${arr[@]}" ; do
   echo "$item"
done

スペースで区切られた値に対しても機能します。

for item in 1 2 3 ; do
   echo "$item"
done

簡単に言えば、引数を渡しながら"${arr[@]}"1 2 3の効果を得るにはどうすればよいですか。

また、このループの概念を、ファイルのように\nで区切られたコンテンツなど、あらゆる種類の区切られたアイテムに拡張することもできますか? Pythonにはiteratorsの概念があります。bashにも同様のものはありますか?

2
Nishant

アレイを正しく呼び出していません。 $arrは配列の最初の要素にのみ展開され、$arr[@]はリテラル文字列[@]が追加された最初の要素に展開されます。

配列のすべての要素を呼び出すには、次を使用します:"${arr[@]}"

もう1つの問題は、$2に含まれているのは2番目の位置パラメータだけで、3番目、4番目、5番目などを反復しようとしていることです。これらはすべて$@に格納されます。

あなたの目標を達成するためにあなたは次のようなことをすることができます:

function loop {
    local command=$1
    shift
    for i in "$@"; do
        "$command" "$i"
    done
}

これにより、commandが最初の定位置パラメーターに設定され、次にシフトされて、$@を使用して残りのパラメーターをループできるようになります。次に、配列を適切に呼び出すだけです:

$ declare -a arr=("element1" "element2" "element3")
$ loop echo "${arr[@]}"
element1
element2
element3
$ loop printf 'hello ' 'world\n'
hello world
$ loop touch file1 file2 file3
$ ls
file1  file2  file3

この関数でさまざまな区切り文字を受け入れることができるようにしたい場合は、次のようにします。

function loop {
    local command=$1
    local delim=$2
    shift 2
    set -- $(tr "$delim" ' ' <<<"$@")
    for i in "$@"; do
        "$command" "$i"
    done
}

つまり、次のように、2番目のパラメーターを介して、使用する区切り文字を指定する必要があります。

$ loop echo '|' 'one|two|three'
one
two
three
$ loop echo '\n' "$(printf '%s\n' 'one' 'two' 'three')"
one
two
three

ただし、これにはいくつかのバグがあります(カスタム区切り文字を指定した場合でも、空白で区切られます)

4
jesse_b

あなたのループはGNU Parallelに非常に似ています:

declare -a arr=("1 a" "2 b" "3 c")
var1="1 a,2 b,3 c"
var2="1-a 2-b 3-c"
parallel -j1 echo ::: "${arr[@]}"
parallel -j1 -d , echo ::: "$var1"
parallel -j1 echo ::: $var2

-j1は、一度に1つのジョブを強制的に実行します。

1
Ole Tange

コードに構文エラーがあります。 loop関数の呼び出しで、"${arr[@]}"を使用して、arr配列を、個別に引用された各要素のリストに展開します。これは、表示するforループで行うことであり、関数を呼び出すときにも行う必要があります。

loop echo "${arr[@]}"

また、関数は渡すコマンドの名前を選択する必要があり、残りの引数をループする必要があることにも注意してください。

loop () {
    local cmd=$1; shift
    for arg do
        "$cmd" "$arg"
    done
}

ここでは、最初の引数を変数cmd(これはコマンドです)に割り当て、次に引数のリストからこの引数をshiftします。引数のリストには、ループしたい文字列のみが含まれています。

次に、ループは残りの各引数のコマンドを順番に呼び出します。

これはxargsユーティリティの目的を限られた方法で複製し、関数はこのユーティリティを使用して再実装できます(ただし、引数の1つに改行が埋め込まれていることが予想される場合を除きます)。オプションをxargsに微調整するには:)

loop () {
    local cmd=$1; shift
    printf '%s\n' "$@" | xargs -L 1 "$cmd"
}

xargsユーティリティはデータが標準入力を介して配信されることを想定しているため、printfを使用してこれを調整し、loopに指定された各引数を独自の行に出力します( xargsは、-L 1)を使用して、改行で区切られた引数ごとに1回、指定されたユーティリティを呼び出します。

これにより、-P nを使用して並列プロセスを開始するなど、xargsのいくつかの実装の他の機能を使用できるようになります。ここで、nは並列で実行するプロセスの数です。

テスト:

$ cat script.sh
loop () {
    local cmd=$1; shift
    printf '%s\n' "$@" | xargs -L 1 "$cmd"
}

arr1=(1 2 3)
arr2=("hello world" "home sweet home")

loop echo "${arr1[@]}"
loop echo "${arr2[@]}"
$ bash script.sh
1
2
3
hello world
home sweet home
1
Kusalananda