1. 概要

カウンターの実装は、ほとんどすべてのプログラミング言語で一般的な手法です。 このチュートリアルでは、Bashスクリプトでカウンターを実装する方法を見ていきます。 さらに、いくつかの一般的な落とし穴と、問題を正しく解決する方法についても説明します。

2. Bashで整数をインクリメントする

通常、カウンターが必要な場合は、ループ内でその値をインクリメントする必要があります。 したがって、Bashで整数をインクリメントする方法を知っていれば、カウンターの実装は問題になりません。

2.1. letコマンドの使用

let コマンドは、算術式を評価するための組み込みのBashコマンドです。

簡単なシェルスクリプトlet_cmd.shを作成して、letコマンドを使用して整数変数をインクリメントする方法を示しましょう。

$ cat let_cmd.sh
#!/bin/bash
COUNTER=0
printf "Initial value of COUNTER=%d\n" $COUNTER

let COUNTER=COUNTER+1
printf "After 'let COUNTER=COUNTER+1', COUNTER=%d\n" $COUNTER

let COUNTER++
printf "After 'let COUNTER++', COUNTER=%d\n" $COUNTER

let_cmd.sh スクリプトを実行すると、次の出力が得られます。

$ ./let_cmd.sh 
Initial value of COUNTER=0
After 'let COUNTER=COUNTER+1', COUNTER=1
After 'let COUNTER++', COUNTER=2

コードは非常に単純で、 させてコマンドは整数変数をインクリメントしましたカウント。 

2.2. Bash算術展開の使用

((math expression))を使用して、Bashの数式を評価できます。

同様に、短いスクリプト math_exp.sh を作成して、算術展開を使用して数式を評価する方法を示します。

$ cat math_exp.sh 
#!/bin/bash
COUNTER=0
printf "Initial value of COUNTER=%d\n" $COUNTER

COUNTER=$(( COUNTER + 1 ))
printf "After 'COUNTER=\$(( COUNTER + 1 ))', COUNTER=%d\n" $COUNTER

(( COUNTER++ ))
printf "After '(( COUNTER++ ))', COUNTER=%d\n" $COUNTER

スクリプトを実行すると、次の出力が得られます。

$ ./math_exp.sh
Initial value of COUNTER=0
After 'COUNTER=$(( COUNTER + 1 ))', COUNTER=1
After '(( COUNTER++ ))', COUNTER=2

コードが示すように、計算結果を取得する場合は、((…))。の前にドルの「$」記号を追加できます。

3. カウンターの実装

次に、シェルスクリプトにカウンターを実装します。

コマンドの出力の行数をカウントするシェルスクリプトcounter.shを作成するとします。

確認を容易にするために、テストでは「seq5」を使用します。

$ cat counter.sh
#!/bin/bash
COUNTER=0
for OUTPUT in $(seq 5)
do
    let COUNTER++
done
printf "The value of the counter is COUNTER=%d\n" $COUNTER

スクリプトを実行すると、カウンターの値は5になります。

$ ./counter.sh
The value of the counter is COUNTER=5

素晴らしい、私たちのカウンターは機能します!

4. サブシェルの落とし穴

for ループでカウンターを作成し、その値をインクリメントする方法を見てきました。 ここまでは順調ですね。 コマンドの出力を読み取ってカウントを行う場合、whileループを使用することがよくあります。

whileループで同じカウントを行いましょう。

$ cat pipe_count.sh 
#!/bin/bash
COUNTER=0
seq 5 | while read OUTPUT
do
    let COUNTER++
done
printf "The value of the counter is COUNTER=%d\n" $COUNTER

上記のスクリプトでは、forループをwhileループに変更し、「seq5」コマンドの出力をにパイプしました。 whileループ。

カウンターも機能するかどうかを見てみましょう。

$ ./pipe_count.sh
The value of the counter is COUNTER=0

おっとっと! なぜそれが起こったのですか? letコマンドがwhileループで機能しませんでしたか? 各ループステップの後にいくつかのデバッグメッセージを出力してみましょう。

#!/bin/bash
COUNTER=0
seq 5 | while read OUTPUT
do
    let COUNTER++
    printf "[DEBUG] After a loop step COUNTER=%d\n" $COUNTER
done
printf "The value of the counter is COUNTER=%d\n" $COUNTERbbvg

それでは、スクリプトを再実行してみましょう。

$ ./pipe_count.sh
[DEBUG] After a loop step COUNTER=1
[DEBUG] After a loop step COUNTER=2
[DEBUG] After a loop step COUNTER=3
[DEBUG] After a loop step COUNTER=4
[DEBUG] After a loop step COUNTER=5
The value of the counter is COUNTER=0

デバッグ出力は、letコマンドがwhileループで正しく機能したことを示しています。 ただし、 COUNTER は、ループ後に0に「リセット」されました。

この奇妙な問題の原因はパイプです。 command1 |のように、パイプを使用する場合 command2 command2はサブシェルで実行されます。 サブシェルで発生する変更は、同じ変数であっても、現在のシェルには影響しません。

pipe_count.shスクリプトに戻ります。seq5の出力をwhileループにパイプするため、whileループが実行されます。サブシェルで。 したがって、ループの後で変数 COUNTER をチェックすると、値は0のままです。

次に、この問題を解決する方法を見てみましょう。

4.1. プロセス置換の使用

サブシェルの問題を解決するために、seqコマンドのプロセス置換whileループにリダイレクトできます。

$ cat ps_count.sh
#!/bin/bash
COUNTER=0
while read OUTPUT
do
    let COUNTER++
done < <(seq 5)
printf "The value of the counter is COUNTER=%d\n" $COUNTER

このように、 while ループはサブシェルで実行されておらず、カウンターは最終的に正しい値を報告します。

$ ./ps_count.sh
The value of the counter is COUNTER=5

4.2. 名前付きパイプの使用

または、名前付きパイプを使用してこの問題を解決することもできます。

$ cat ./fifo_count.sh
#!/bin/bash
COUNTER=0
NAMED_PIPE="./myFifo"
if [[ ! -p "$NAMED_PIPE" ]]; then
    mkfifo $NAMED_PIPE
fi
seq 5 > "$NAMED_PIPE" &
while read OUTPUT
do
    let COUNTER++
done < "$NAMED_PIPE"
rm "$NAMED_PIPE"
printf "The value of the counter is COUNTER=%d\n" $COUNTER

上記のスクリプトでは、名前付きパイプを作成し、 seq5コマンドでフィードします。 その後、 while ループは名前付きパイプを読み取り、seqコマンドの出力を取得します。

変数COUNTERは、whileループの後に期待値を持ちます。

$ ./fifo_count.sh
The value of the counter is COUNTER=5

5. 結論

この記事では、Bashスクリプトでカウンターを実装する方法を学びました。 さらに、サブシェルの落とし穴と問題の解決策についても説明しました。