スクリプトは shebang "/usr/bin/env sh" では動作しませんが、sh は bash を指しますが、 "/usr/bin/env bash" では動作しません。

スクリプトは shebang "/usr/bin/env sh" では動作しませんが、sh は bash を指しますが、 "/usr/bin/env bash" では動作しません。

私は、最高のパフォーマンスを持つプログラムを選択するのに役立つ引数として渡されたさまざまなプログラムのパフォーマンスを記録するシェルスクリプトを持っています。関連スニペット:

#!/usr/bin/env sh
function cal_perf () {
        real_t=0
        user_t=0
        sys_t=0
        for (( trial=1 ; trial<=$3 ; ++trial )) ; do
                shopt -s lastpipe
                /usr/bin/time -f '%e:%S:%U' $1 $2 |& IFS=":" read real sys user
                real_t=$(echo "$real + $real_t" | bc)
                user_t=$(echo "$user + $user_t" | bc)
                sys_t=$(echo "$sys + $sys_t" | bc)
        done
        real_t=$(echo "scale=2 ; $real_t / $3" | bc)
        user_t=$(echo "scale=2 ; $user_t / $3" | bc)
        sys_t=$(echo "scale=2 ; $sys_t / $3" | bc)
        printf "%s\t%d\t%.2f\t%.2f\t%.2f\n" $2 $3 $real_t $user_t $sys_t >> timings_$(date '+%Y%m%d')
}

# main
printf "program\t#trials\treal_time_am\tuser_time_am\tsys time_am\n" > timings_$(date '+%Y%m%d')
translator=$1
shift
while [ $# -gt 1 ] ; do cal_perf $translator $1 ${!#} ; shift ; done

次のようにコマンドラインから実行する必要があります。

perf <translator_progam> <list_of_programs_to_compare> <number_of_trials>

xip.py, foo.py, bar.py, bas.py, qux.py...たとえば、作業ディレクトリの正味コンテンツパフォーマンスを比較し、統計を生成する前にこれを50回実行したいとします。

perf python *py 50

ここでは何か明白なものが欠けているようですが、このスクリプトを呼び出すと、すべてが期待bash $HOME/bin/perf ...どおりに機能します。ただし、次の2つの呼び出しは失敗します(追加のエラーを含む)。

  1. perf ...
  2. あるいは、作業ディレクトリに入れて、次のように呼び出すこともできます。./perf ...

ここに画像の説明を入力してください。

shebangを変更すると/usr/bin/env bash問題は解決しましたが、私のシステム/usr/bin/shで問題が発生しました。/usr/bin/bash

答え1

shスクリプトをインタプリタとして実行していますが、関数を使用していますbash。それがそれを指していると仮定してもsh、実行するように要求されたときに多くのカスタム機能が無効になるbashため、まだこれを行うことはできません。bashsh

1つの解決策は、スクリプトを正しく宣言することです。bash関数を使用しているので、これをbashスクリプトとして宣言してください。

#!/bin/bash

envまたは継続して依存する必要がある場合

#!/usr/bin/env bash

別のオプションは、bash特定の構文を削除することです。

どちらの場合も、変数を使用するときに二重引用符で囲む必要があることを忘れないでください。たとえば、引用符のない変数(スクリプト引数を含む)の1つにスペースが含まれている場合、シェルはそれを2つ以上の単語に分割します。

答え2

$3空の文字列に展開するとエラーが発生します。

for (( trial=1 ; trial<=$3 ; ++trial ))

吹くするこれはPOSIXモードでもサポートされているため、for (( .. ))それ自体は問題になりません。この特別なケースでは、問題は${!#}関数を呼び出すときに使用される拡張機能にあります。 2つの方法で解析できます。

${var#word}標準シェルは、特殊パラメータに適用されるプレフィックス削除操作!で空の文字列を削除します。空の文字列を削除すると何も起こりませんが、たとえば文字を削除できます。

$ dash -c 'sleep 123 & echo $! ${!#?};'
27920 7920

ただし、Bashでは、「ポインタ」として使用される間接${!#}拡張として扱われ、最後の位置引数を提供します。${!var}$#

BashはPOSIXモードでも間接拡張をサポートしますが、ここではより標準に準拠する方法でこのコーナーケースを処理します。これ文書には実際にこのように記載されています。:

  1. 変数間接参照は可能ですが、「#」と「?」特殊パラメータでは機能しない可能性があります。

$!最後のバックグラウンドプロセスのPIDが含まれていますが、スクリプトはどのプロセスも起動しなかったため、標準的な意味は空の文字列のみを提供します。最終的にエラーが発生します。

"${@: -1}"Bashで最後の位置引数を取得するためにも使用できます。また、標準ではありませんが、POSIXモードで動作します。

とにかく、通常、BashがサポートするPOSIX以外の機能(例えば、、、(( .. ))run、not)を使用したい場合。非互換性の問題に対処する手間を省くことができます。${!var}${var:n:m}bashsh

関連情報