スペースで区切られた単語のリストで、一定の数の値が互いに離れている特定の値を反復処理する最も効率的な方法を見つけようとしています(配列を使用したくありません)。例えば、
list="1 ant bat 5 cat dingo 6 emu fish 9 gecko hare 15 i j"
したがって、リストを繰り返し処理して、1、5、6、9、15にのみアクセスできるようにしたいと考えています。
EDIT:リストから取得しようとしている値は、リストの他の部分とは形式が異なります。それらを特別なものにしているのは、リスト内でのそれらの位置のみです(この場合、位置1、4、7 ...)。したがって、リストは1 2 3 5 9 8 6 90 84 9 3 2 15 75 55
ですが、同じ番号を引き続き使用します。また、リストの長さがわからない場合でも実行できるようにしたいと考えています。
これまでに考えた方法は次のとおりです。
方法1
set $list
found=false
find=9
count=1
while [ $count -lt $# ]; do
if [ "${@:count:1}" -eq $find ]; then
found=true
break
fi
count=`expr $count + 3`
done
方法2
set list
found=false
find=9
while [ $# ne 0 ]; do
if [ $1 -eq $find ]; then
found=true
break
fi
shift 3
done
方法パイプ処理でこれが最悪のオプションになると確信していますが、好奇心から、セットを使用しない方法を見つけようとしました。
found=false
find=9
count=1
num=`echo $list | cut -d ' ' -f$count`
while [ -n "$num" ]; do
if [ $num -eq $find ]; then
found=true
break
fi
count=`expr $count + 3`
num=`echo $list | cut -d ' ' -f$count`
done
それで、最も効率的なものは何ですか、またはより簡単な方法がありませんか?
awk
を使用すると、非常に簡単です。これにより、任意の長さの入力に対して4つおきのフィールドの値が得られます。
$ awk -F' ' '{for( i=1;i<=NF;i+=3) { printf( "%s%s", $i, OFS ) }; printf( "\n" ) }' <<< $list
1 5 6 9 15
これは、awk
(レコード内のフィールド数)などの組み込みのNF
変数を活用し、いくつかの単純なfor
ループを実行してフィールドに沿って反復し、いくつあるかを事前に知る必要がなく、あなたが欲しいもの。
または、実際に必要な場合は、例に指定されている特定のフィールドのみを使用します。
$ awk -F' ' '{ print $1, $4, $7, $10, $13 }' <<< $list
1 5 6 9 15
効率に関する質問については、最も簡単な方法は、これまたは他の各メソッドをテストし、time
を使用して所要時間を示すことです。 strace
などのツールを使用して、システムコールのフローを確認することもできます。 time
の使用法は次のようになります。
$ time ./script.sh
real 0m0.025s
user 0m0.004s
sys 0m0.008s
さまざまなメソッド間でその出力を比較して、時間に関して最も効率的な方法を確認できます。他のツールを他の効率性メトリックに使用できます。
ソフトウェア最適化の最初のルール:禁止。
プログラムの速度が問題であることがわかるまで、それがどれほど速いかを考える必要はありません。リストがその長さか、約100〜1000アイテムの長さである場合、どれだけ時間がかかるかさえ気付かないでしょう。最適化について考えるのに、違いよりも多くの時間を費やす可能性があります。
2番目のルール:Measure。
それが確実に調べる方法であり、システムに答えを与える方法です。特にシェルでは非常に多くあり、それらはすべて同じではありません。 1つのシェルに対する答えは、あなたには当てはまらないかもしれません。
大規模なプログラムでは、プロファイリングもここに適用されます。最も遅い部分は、あなたが思っているものではないかもしれません。
第三に、シェルスクリプト最適化の最初のルール:シェルを使用しないでください。
ええ、本当に。多くのシェルは高速に作られていません(外部プログラムの起動はそうである必要がないため)。また、毎回ソースコードの行を再度解析することさえあります。
代わりにawkやPerlなどを使用してください。私が行った些細なマイクロベンチマークでは、awk
は、単純なループ(I/Oなし)の実行において、一般的なシェルより数十倍高速でした。
ただし、シェルを使用する場合は、外部コマンドの代わりにシェルの組み込み関数を使用してください。ここでは、expr
を使用しています。これは、システムで見つけたシェルには組み込まれていませんが、標準の算術展開で置き換えることができます。例えば。 i
をインクリメントするには、i=$((i+1))
の代わりにi=$(expr $i + 1)
を使用します。最後の例でのcut
の使用は、標準のパラメーター展開で置き換えることもできます。
手順#1および#2が質問に適用されます。
この回答では、ベンチマークではなく、一般的なアドバイスのみを行います。ベンチマークは、パフォーマンスに関する質問に確実に回答する唯一の方法です。しかし、操作しているどのくらいデータであり、どのくらいの頻度この操作を実行しているのかを述べていないため、有用なベンチマークを行う方法はありません。多くの場合、10個のアイテムの効率と1000000個のアイテムの効率は同じではありません。
一般的な経験則として、純粋なシェルコードにループが含まれていない限り、外部コマンドの呼び出しは、純粋なシェル構成で何かを行うよりもコストがかかります。一方、大きな文字列または大量の文字列を反復するシェルループは、専用ツールを1回呼び出すよりも遅くなる可能性があります。たとえば、実際にcut
を呼び出すループは著しく遅くなる可能性がありますが、cut
を1回呼び出すだけですべてを実行する方法を見つけると、同じことを行うよりも高速になる可能性があります。シェルでの文字列操作に関すること。
カットオフポイントはシステム間で大きく異なる可能性があることに注意してください。これは、カーネル、カーネルのスケジューラの構成方法、外部実行可能ファイルを含むファイルシステム、現時点でのCPUとメモリの負荷の程度、およびその他の多くの要因に依存します。
パフォーマンスがまったく気になる場合は、expr
を呼び出して算術演算を実行しないでください。実際、算術を実行するためにexpr
を呼び出さないでください。シェルには組み込み演算があり、expr
を呼び出すよりも明確で高速です。
Shには存在しないbash構成を使用しているため、bashを使用しているようです。では、なぜ配列を使用しないのでしょうか。アレイは最も自然なソリューションであり、最も高速になる可能性もあります。配列のインデックスは0から始まることに注意してください。
list=(1 2 3 5 9 8 6 90 84 9 3 2 15 75 55)
for ((count = 0; count += 3; count < ${#list[@]})); do
echo "${list[$count]}"
done
システムがbashではなくsh
としてダッシュまたはkshを持っている場合、shを使用すると、スクリプトがより高速になる可能性があります。 shを使用する場合、名前付き配列は取得されませんが、配列はset
で設定できる位置パラメーターの1つを取得します。実行時までわからない位置の要素にアクセスするには、eval
を使用する必要があります(適切に引用符を付けるように注意してください)。
# List elements must not contain whitespace or ?*\[
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
set $list
count=1
while [ $count -le $# ]; do
eval "value=\${$count}"
echo "$value"
count=$((count+1))
done
配列に一度だけアクセスし、左から右へ(一部の値をスキップして)行く場合は、変数インデックスの代わりにshift
を使用できます。
# List elements must not contain whitespace or ?*\[
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
set $list
while [ $# -ge 1 ]; do
echo "$1"
shift && shift && shift
done
どちらの方法が速いかは、シェルと要素の数によって異なります。
別の可能性は、文字列処理を使用することです。位置パラメーターを使用しないという利点があるため、他のパラメーターに使用できます。大量のデータの場合は遅くなりますが、少量のデータの場合に顕著な違いが生じることはほとんどありません。
# List elements must be separated by a single space (not arbitrary whitespace)
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
while [ -n "$list" ]; do
echo "${list% *}"
case "$list" in *\ *\ *\ *) :;; *) break;; esac
list="${list#* * * }"
done
awk
は素晴らしい選択ですif Awkスクリプト内ですべての処理を実行できます。それ以外の場合は、Awk出力を他のユーティリティにパイプして、awk
のパフォーマンス向上を破壊するだけです。
bash
配列の繰り返し処理も素晴らしいです。リスト全体を配列内に収めることができる場合(最新のシェルではこれがおそらく保証されます)and配列の構文を気にしません体操。
ただし、パイプラインアプローチ:
xargs -n3 <<< "$list" | while read -ra a; do echo $a; done | grep 9
どこ:
xargs
は、空白で区切られたリストを3つのバッチにグループ化し、各改行を区切りますwhile read
そのリストを使用して、各グループの最初の列を出力しますgrep
最初の列をフィルタリングします(元のリストの3番目ごとの位置に対応)私の意見では、理解しやすさが向上します。人々はこれらのツールの機能をすでに知っているので、左から右に読みやすく、何が起こるかについての理由がわかります。このアプローチでは、歩幅(-n3
)とフィルターパターン(9
)なので、変動させるのは簡単です:
count=3
find=9
xargs -n "$count" <<< "$list" | while read -ra a; do echo $a; done | grep "$find"
「効率」について質問するときは、必ず「トータルライフタイム効率」を考えてください。この計算には、コードを機能させ続けるためのメンテナーの努力が含まれています。
たぶんこれ?
cut -d' ' -f1,4,7,10,13 <<<$list
1 5 6 9 15
効率を高めたい場合は、シェルコマンドを使用しないでください。パイプ、リダイレクト、置換など、およびプログラムに制限します。これがxargs
およびparallel
ユーティリティが存在する理由です。bashwhileループは非効率的で非常に遅いためです。 bashループは最後の解決としてのみ使用してください。
list="1 ant bat 5 cat dingo 6 emu fish 9 gecko hare 15 i j"
if
<<<"$list" tr -d -s '[0-9 ]' |
tr -s ' ' | tr ' ' '\n' |
grep -q -x '9'
then
found=true
else
found=false
fi
echo ${found}
しかし、良いawk
を使用すると、おそらく多少速くなるはずです。
[〜#〜] gnu [〜#〜]sed
および[〜#〜] posix [〜#〜]シェルスクリプトを使用:
echo $(printf '%s\n' $list | sed -n '1~3p')
またはbash
の-パラメータ置換を使用:
echo $(sed -n '1~3p' <<< ${list// /$'\n'})
非-[〜#〜] gnu [〜#〜](ie[〜#〜] posix [〜#〜])sed
、およびbash
:
sed 's/\([^ ]* \)[^ ]* *[^ ]* */\1/g' <<< "$list"
または、より移植性の高い[〜#〜] posix [〜#〜]sed
とシェルスクリプトの両方を使用:
echo "$list" | sed 's/\([^ ]* \)[^ ]* *[^ ]* */\1/g'
これらのいずれかの出力:
1 5 6 9 15
私の意見では、最も明確な解決策(そしておそらく最もパフォーマンスも高い)は、RSおよびORS awk変数を使用することです。
awk -v RS=' ' -v ORS=' ' 'NR % 3 == 1' <<< "$list"