Info
#

2025/06/23 (Mon) 08:03:49 GMT+0000 (UTC)
Type: PC | System: Unknown | Browser: Unknown ... More

Menu
.
+
#
  • @ /note/bash/post-Utility
Content
.
+
#

Post: Nekoformi
Date: 2024/02

Utility

Bashを効率的に動かしたり可読性を高めるには、幾つかの機能を関数として纏めるべきでしょう。

関数

関数は以下のように記述します。また、ファイルは以下のように実行します。

~/Some Bash Script A.sh
+
#
1:
2:
3:
4:
5:
6:
7:
#!/bin/bash function testFunction() { echo "Hello, $1!" } testFunction "John Smith"
Bash
+
#
1:
2:
$ bash ~/"Some Bash Script A.sh" Hello, John Smith!
  • Bashのコマンドをファイルに記述する際は、1行目にシバン・シェバング(#!/bin/bash)を書くことで機能を明示します。
  • 文字列に空白( )が含まれる場合は空白をエスケープ(\ )するか、引用符(")で囲む必要があります。

他のファイルに記述された関数を使用する場合は、sourceコマンドでスクリプトを呼び出します。ただし、先程の状態ではtestFunction "John Smith"が実行されてしまうので、関数用のファイルには関数だけを記述しましょう。

~/Some Bash Script B.sh
+
#
1:
2:
3:
4:
5:
#!/bin/bash source ~/"Some Bash Script A.sh" testFunction "Jane Smith"
Bash
+
#
1:
2:
$ bash ~/"Some Bash Script B.sh" Hello, Jane Smith!

Install Advanced Packaging Tool, etc.

コンピューターのセットアップでは多くのパッケージをインストールすると思います。それを何度も繰り返す場合は、操作をファイルに纏めたり、その操作すらも関数に纏めます。

Some Bash Script.sh
+
#
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
#!/bin/bash function aptInstall() { sudo apt -y install "$1" } function aptDownload() { # If you need the Advanced Packaging Tool sudo apt -y --reinstall --download-only install "$1" } function snapInstall() { sudo snap install "$@" } function pipInstall() { pip install "$1" }
Some Bash Script.sh
+
#
1:
2:
3:
4:
#!/bin/bash aptInstall curl snapInstall blender --classic

Copy File / Folder / Zip Data

ファイルやフォルダーを複製したり圧縮ファイルを展開する場合、指定されたディレクトリーが存在しなければ成功しません。それを回避するため、自動的にディレクトリーを作成します。

Some Bash Script.sh
+
#
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
#!/bin/bash function cpFile() { if [ -d "$2" ]; then rm -f "$2" else mkdir -p "${2%/*}" fi echo "Copy file ... $1 -> $2" cp -fp "$1" "$2" } function cpFolder() { if [ -d "$2" ]; then rm -rf "${2%/}"/* else mkdir -p "${2%/}" fi echo "Copy folder ... ${1%/}/ -> ${2%/}/" cp -RTfp "${1%/}"/ "${2%/}"/ } function cpZipData() { if [ -d "$2" ]; then rm -rf "${2%/}"/* else mkdir -p "${2%/}" fi echo "Extract file ... $1 -> ${2%/}/" unzip "$1" -d "${2%/}"/ }
Some Bash Script.sh
+
#
1:
2:
3:
4:
5:
#!/bin/bash cpFile ~/"Test File.txt" ~/"New File/Copy File.txt" cpFolder ~/"Test Directory" ~/"New Directory/Copy Directory" cpZipData ~/"Test Data.zip" ~/"New Data/Copy Data"
  • 変数展開とパターンマッチ(${変数名(#|##|%|%%)パターン})を用いることでパスを正規化します。
  • 複製先にファイルやディレクトリーが存在する場合に対象を削除している(=複製先を置換している)ため、それを望まない場合は関数内のrmコマンドを削除する必要があります。

Create Empty File / Insert New Line

空のファイルを作成する場合はtouchコマンドが有効ですが、空の文字列をリダイレクション(>)して作成することも可能です。

Some Bash Script.sh
+
#
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
#!/bin/bash function createEmptyFile() { echo "" > "$1" sed '1d' "$1" > "$1" } function sudoCreateEmptyFile() { echo "" | sudo tee "$1" 1> /dev/null sudo sed '1d' "$1" | sudo tee "$1" 1> /dev/null }
Some Bash Script.sh
+
#
1:
2:
3:
#!/bin/bash sudoCreateEmptyFile /root/joke
Some Bash Script.sh
+
#
1:
2:
3:
4:
5:
6:
7:
8:
9:
#!/bin/bash function insertNewLine() { echo -e "$2" >> "$1" } function sudoInsertNewLine() { echo -e "$2" | sudo tee -a "$1" 1> /dev/null }
Some Bash Script.sh
+
#
1:
2:
3:
#!/bin/bash sudoInsertNewLine /root/joke "Hello, world!"
  • 出力をリダイレクションした場合は改行が挿入される(2行になる)ため、sedコマンドを用いて1行目の改行を削除したもの(無)をファイルに書き込みます。
  • リダイレクションによるファイルの書き込みはsudoで行えないため、teeコマンドを使用します。また、インターフェースに出力しないよう1> /dev/nullで結果を虚無に放り込みます。
  • echoコマンドでは-eオプションを付与することでエスケープシーケンス(改行を表す\n等)を解釈します。

Change Permission / Timestamp

権限や更新日時を一括で変更したいですか? findコマンドとxargsコマンドを駆使しましょう!

Some Bash Script.sh
+
#
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
#!/bin/bash function changeDefaultPermission() { echo "Change permission (755) ... $1" find "$1" -name '*' -print0 | xargs -0 -i sudo chmod 755 {} find "$1" -name '*' -print0 | xargs -0 -i sudo chown $USER {} } function changeMasterPermission() { echo "Change permission (777) ... $1" find "$1" -name '*' -print0 | xargs -0 -i sudo chmod 777 {} find "$1" -name '*' -print0 | xargs -0 -i sudo chown $USER {} } function changeTimestamp() { echo "Change timestamp ($2) ... $1" find "$1" -name '*' -print0 | xargs -0 -i touch -c -d "$2" {} }
Some Bash Script.sh
+
#
1:
2:
3:
4:
5:
#!/bin/bash changeDefaultPermission ~/"Test Directory A" changeMasterPermission ~/"Test Directory B" changeTimestamp ~/"Test Directory C" "1970/01/01 00:00:00"
  • xargsコマンドは改行や空白を区切りとして処理するため、パスに空白が含まれると正常に動作しません。それを解決するため、findコマンドでは-print0オプションを付与することで一覧の区切りをNULL文字(/0)で記述します。また、xargsコマンドでは-0オプションを付与することでNULL文字を区切りとして処理します。

Input Data

スクリプトで対話(ユーザーが入力した文字列を変数に出力)する処理は煩雑なので、一行で実現できるように工夫します。

Some Bash Script.sh
+
#
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
#!/bin/bash function inputData() { # set +x local data echo -e "\e[33;1m$1を入力してください: \e[m" 1>&2 read -p "" data echo "$data" # set -x }
Some Bash Script.sh
+
#
1:
2:
3:
#!/bin/bash file=$(inputData "ファイル")
  • 関数内で変数を定義する場合は基本的にlocalコマンドの使用が推奨されます。
  • 関数は返り値としてechoコマンドの内容を出力するため、インターフェースのみに出力する場合はechoコマンドの結果を標準エラー(1>&2)で出力する必要があります。

Select Option

対話では選択肢を入出力する状況も想定されます。エラーやミスを防ぐ手段として機能の共通化は最も効果的です。

Some Bash Script.sh
+
#
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
#!/bin/bash function selectOption() { # set +x local option local select eval option=($1) echo -e "\e[33;1m-Option-------------------------------------------------------------------------\e[m" local i=1 for item in "${option[@]}"; do echo -e "\e[33;1m- [${i}] ${item}\e[m" echo $((i++)) >& /dev/null done local label="$2" if [[ -z "$label" ]]; then label="オプション" fi echo -e "\e[33;1m- [*] キャンセル\e[m" echo -e "\e[33;1m--------------------------------------------------------------------------------\e[m" echo -e "\e[33;1m${label}を選択してください: \e[m" read -p "" select if [[ $select =~ ^([0-9]{1,2})|([0-1][0-9]{2})|(2[0-4][0-9])|(25[0-9])$ ]]; then # 0 - 255 # set -x return $((select)) else # set -x return 0 fi }
Some Bash Script.sh
+
#
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
#!/bin/bash myOption=( "'Option A'" "'Option B'" "'Option C'" ) selectOption "${myOption[*]}" option=$? if [ $option = 0 ]; then exit fi case "$option" in 1 ) echo "Select Option A";; 2 ) echo "Select Option B";; 3 ) echo "Select Option C";; esac
  • 空白を含む文字列の配列は扱いが難しいため、以下の処理で関数に入力します。
    1. 配列の各値を二重の引用符("'文字列'")で囲みます。
    2. 関数の引数として配列を入力する際に、配列全体を配列として参照(array[@])せずに文字列として参照(array[*])します。
    3. evalコマンドで文字列を評価(内容を展開して配列に変換)します。
    4. 必要に応じて配列全体を配列として参照します。ただし、空白を扱うために変数を引用符で囲みます。
  • \e[XX;XXm文字列\e[XX;XXmはANSIエスケープシーケンスです。
  • 数値の演算を行う場合はecho $((変数の演算)) >& /dev/nullで出力結果を虚無に放り込みます。
  • 関数がreturnで戻り値を出力する場合の制約として、終了ステータス:8ビットの数値(0 - 255)しか出力できないことに注意してください。また、その場合は$?で終了ステータスを取得します。

Confirm Execution

人間は何かしらのミスを犯します。入力した情報を確認してから処理を実行しましょう。

Some Bash Script.sh
+
#
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
#!/bin/bash function confirmExecution() { # set +x local confirm echo -e "\e[33;1m本当に実行しますか? (\"Yes\"で実行): \e[m" read -p "" confirm case "$confirm" in Yes|yes ) echo -e "\e[32;1m処理を開始します\e[m" # set -x return 1;; * ) echo -e "\e[31;1m処理を中止します\e[m" # set -x return 0;; esac }
Some Bash Script.sh
+
#
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
#!/bin/bash confirmExecution confirm=$? if [ $confirm = 0 ]; then exit fi echo "Running..."