27475


xargs みたいなことをしたいです。  
ただし、 -I をすべての引数の前に付与したいです。

例:

[code]

    : 入力
    foo
    bar
    piyo
    
    : 実行してほしいコマンド
    some_command -I foo -I bar -I piyo
    
[/code]

特に、 ARG_MAX の処理を適切に行ってほしいと考えているのですが、これを実現する方法などありますか？


CC BY-SA 3.0
2016-07-07T13:12:34.820
27434
2016-07-09T09:30:07.400
754
question
linux shellscript
xargs みたいなことをしたい、ただし、 -I をすべての引数の前に付与したい


下記は`ARG_MAX`の考慮は`xargs`にしてもらう例です。  
組み立てたコマンドは`sh`に渡していますが、並行処理させたい場合は再度`xargs`や`parallel`に渡すとよいと思います。

[code]

    引数生成コマンド |sed "s/$/ -I/" |xargs echo some_command -I |sed "s/^some_command -I -I/some_command -I/; s/ -I$//" |sh
    
[/code]

流れ：

  * `sed`で全ての引数の後ろに`-I`を付ける。  
前に付けるのが素直だが、`ARG_MAX`の関係上、後から足す事は避けたい。余分な`-I`は出来ても、足りなくは成らないように、こうしておく。

  * `xargs`で先頭を`some_command -I`としたコマンド列にする。  
この段階で引数が多ければ、`xargs`が複数行にしてくれる。

  * `sed`で不要な`-I`を削除。先頭付近でダブっているか、行末にあるはず。

* * *

引数毎にプロセスを生成してよいなら`xargs`の`-I`オプションで可能ですが当然遅いです。

[code]

    引数生成コマンド |xargs -I {} some_command -I {}
    
[/code]


CC BY-SA 3.0
2016-07-07T15:49:59.277
27441
2016-07-07T15:49:59.277
3054
27434
answer


shellscript というタグが付いてたので、シェルスクリプト（bash限定）で `xargs` もどきの実装と、そこに `-I`
追加を埋め込んでみました:

[code]

    #!/bin/bash
    # ${#var} が文字数でなくバイト数を返してくれる…はず。
    LC_ALL=C
    
    readonly ARG_MAX=$(getconf ARG_MAX)
    # -1 の hex 表現からポインターサイズを推測
    readonly PTR_SIZE=$(( $(printf "%x" -1 | wc -c) / 2 ))
    
    # 与えられた引数のそのまま配列にコピー
    args_base=("$@")
    
    # 与えられた引数と環境変数のバイト数の計算
    declare -i i len_base
    joined="$*"
    # 最後の引数以外、終端の \0 は空白に置き換えられているので、
    # 最後の引数分だけ +1
    len_base=$((${#joined} + 1))
    len_base+=$(( PTR_SIZE * $# ))
    # 環境変数の \0 は \n で置き換えられているので値はそのまま
    len_base+=$(env | wc -c)
    len_base+=$(( PTR_SIZE * $(env | wc -l) ))
    
    exit_status=0
    declare -a args
    declare -i len
    
    #######################################
    # global 変数 args と len を初期化
    # Globals:
    #   args (modified)
    #   len (modified)
    #   args_base
    #   len_base
    # Arguments:
    #   None
    # Returns:
    #   None
    #######################################
    function command::reset(){
      args=("${args_base[@]}")
      len=${len_base}
    }
    #######################################
    # コマンドを実行
    # Globals:
    #   args
    #   len
    #   len_base
    # Arguments:
    #   erro_if_no_arg: "false" が与えられた場合、入力がなくてもエラーにしない
    # Returns:
    #   args の終了ステータス、何も実行しなかった場合には 0
    #######################################
    function command::run(){
      local error_if_no_arg="${1:-true}"
      if ((len == len_base)); then
        if ${error_if_no_arg}; then
          # 入力が ARG_MAX-4 より大きい場合、追加の引数なしでここが実行される
          echo 'one of given item is too long' 1>&2
          exit 126
        fi
        return 0
      fi
      "${args[@]}" < /dev/null
      local status="$?"
      if [ $status -gt 0 ]; then
        exit_status=123
      fi
    }
    
    command::reset
    while read line; do
      # +4 is for "-I\0" and "\0" for $line
      if ((len + 4 + ${#line} + PTR_SIZE * 2 >= ARG_MAX)); then
        command::run
        command::reset
      fi
      args+=(-I "$line")
      ((len += 4 + ${#line} + PTR_SIZE * 2)) || :
    done
    command::run false
    exit $exit_status
    
[/code]

というファイルを、例えば `xargs_i.sh` と作成して

[code]

    引数生成コマンド | xargs_i.sh some_command
    
[/code]

とすれば、多分、それっぽい動作をするかと思います。

> 特に、 ARG_MAX の処理を適切に

ということなので、その辺は頑張ってバイト数を計算するようにしてみましたが、やたらサンプルが長くなってしまいました。頑張ってないところとして

  * 「引数生成コマンド」が何も出力しない場合、何もしない (`xargs` の場合、追加引数なしでコマンドを実行するっぽい）
  * エラーハンドリングは適当なので、非正常系の動作は `xargs` コマンドと異なる部分が多々ある
  * これをベースに `xargs -0` 相等の動作に改造するのはほぼ不可能
  * バイト数を計算する都合上、勝手に `LC_ALL=C` をセットしている(ので、locale に依存するコマンドは期待した通りに動かない）

などがあります。


CC BY-SA 3.0
2016-07-09T09:30:07.400
27475
2016-07-09T09:30:07.400
2521
27434
answer