シェルスクリプトで直下のファイル一覧を表示する

Linux

普段、gitのコミットなどの操作は基本的にターミナルで、ときどきコミット履歴を確認するためにSourceTree を使ったり、VSCodeのgit Graph で差分を確認したりします。だから、ターミナルでbashを操作してはいるのですが、なかなかシェルスクリプトまで書くということをしないので、いざ書こうと思った時のポイントをまとめておこうと思った次第です。

単一な直下のファイルを表示するとかであれば。ターミナルからコマンドを打ち込んで終了なのですが、繰り返し操作や複数のパスに対して再起的に行うなどであれば、スクリプトを書いた方が再利用性が高いと思います。

シェルスクリプトを基本

シェルスクリプトの実行のお作法と文法を軽く触れておきます。

実行の仕方

シェルスクリプトの実行で一番メジャーな方法は、ファイル名の前に ./ をつけて実行します。これはカレントディレクトリであることを示すためで、ファイル名のみだとシェルが環境変数に設定したファイルパスから該当のファイルを探しに行きます。

# dispDir.sh の1行目にシバンをつける
#!/bin/bash

# ファイル名の前に ./ をつけて実行する
$ ./dispDir.sh

source コマンド(またはドット)は、指定したファイルのソースコードをそのままターミナルに打ち込んだときと同じように実行します。ですので、この場合bashで実行しているならば、ファイル内のシバンの設定は不要です。

$ source ./dispDir.s
# 以下同じ
$ . ./dispDir.s

厳密には ./ で実行する場合は、source コマンドで実行する場合と違って、サブシェルを立ち上げて実行するので現在の実行しているシェルの影響を受けないのですが、基本的に ./ の方で覚えておけば問題ないかと。

文法

# 変数: $変数名で変数の値を参照する、=の前後にはスペースを入れない
xdir=Users/Username/Desktop/
echo $xdir # >Users/Username/Desktop/

# シングルクォート: 変数展開が無効になる
echo '$xdir' # >$xdir
# ダブルクォート: 変数展開が有効になる
echo "$xdir" # >Users/Username/Desktop/

# コマンド置換: $(コマンド)
# コマンドの出力結果をシェルスクリプトの中で利用する
git switch $(git branch | grep "ブランチ名に含まれる文字列")

# 位置パラメータ: コマンドラインから引数を受け取ってシェルスクリプトで利用する
$ ./parmeters.sh x y z
# ./parmeters.sh で参照するには、$1, $2, $3 で順に参照、$0は実行時のファイル名
# 引数っ全体を参照する変数として、$@ で参照できる(そのまま別の関数に渡す場合など)/

IF

if 文はちょっと他の言語と違って特殊なので触れておきます。

#!/bin/bash

num="$1"
if [ $num -gt 10 ]; then
    echo "変数 num は 10 より大きいです"
elif [ $num -eq 10 ]; then
    echo "変数 num は 10 と等しいです"
else
    echo "変数 num は 10 より小さいです"
fi

[ は単なるカッコではなくbashの組み込み関数で、最後の ] は引数になります。[ は実際には test コマンドのエイリアスです。test コマンドは条件を評価するためのコマンドですが、[ は Bash に組み込まれており、test コマンドの代わりとして利用されます。だから、[ と ] の間にはスペースを入れる必要があります。

# 実行例
$ ./parmeters 10
変数 num は 10 と等しいです

# [ は単なるカッコではなくbashの組み込み関数
$ type [
[ is a shell builtin

シェルスクリプトを実行する

次のコードはファイル直下のディレクトリ一覧を表示するサンプルです。冒頭にファイルを作成するパスに移動してますが、Windows, Mac で使う場合はそれぞれ異なるので適宜変えてください。

#!/bin/bash

set -euxo pipefail
# cd "$(dirname "$0")"
# ファイルを作成するパスに移動する
# Windows の git bash for windows で行う場合
cd "/c/Users/Username/path"
# Mac の場合
cd "/Users/Username/Desktop/"

# 出力ファイル名を指定
output_file="result.txt"

append_dirs_to_file() {
    local path="$1"
    find "$path" -maxdepth 1 -type d -exec basename {} \; >> "$output_file"
    echo -e "" >> "$output_file"
}

# 出力ファイルを初期化
> "$output_file"

# 各パスに対して関数を呼び出す
append_dirs_to_file "./css"
append_dirs_to_file "./script"
append_dirs_to_file "./WEB-INF/pages"

# 完了メッセージを表示
echo "フォルダ一覧が $output_file に出力されました。"

シェルスクリプトを実行します。権限エラーが出る場合は追加します。

# シェルスクリプトを実行する
$ ./dispDir.sh
-bash: ./dispDir.sh: Permission denied

# パーミッションの確認
$ ls -l ./dispDir.sh
-rw-r--r--  1 username  staff  597 Jul 15 11:22 ./dispDir.sh

# ugo 全てにx(実行)権限を付与する
$ chmod +x dispDir.sh

# パーミッションの確認
$ $ ls -l ./dispDir.sh
-rwxr-xr-x  1 username  staff  597 Jul 15 11:22 ./dispDir.sh

ちなみにシンボルモードによるパーミッションの変更は次のような感じです。詳細は本質がズレるので割愛します。

$ chmod [ugoa][+-=][rwx] <ファイル名>
記号意味
uowner
ggroup
oother user
aall ugo
+add
prohibit
=equal
rread
wwrite
xexecute

再起的にディレクトリ内のファイルを探索する

次のコードは、指定したパスの直下のディレクトリ内で階層的にファイルが存在すれば、その指定したパスの直下のディレクトリ一覧を表示するスクリプトです。だいぶベタ書きしたので、重複してますが雰囲気を掴む感じでお願いします。

#!/bin/bash

# set -euxo pipefail

current_dir=$(pwd)
# cd "$(dirname "$0")"
cd "/Users/Username/"
# 出力ファイル名を指定
output_file="result.txt"

# 出力ファイルを初期化
> "$output_file"

directories_with_files() {
    local path="$1"
    echo "ディレクトリ: $path" >> "$output_file"
    
    # 指定されたディレクトリ内のサブディレクトリを取得
    subdirs=$(find "$path" -maxdepth 1 -type d)
    for subdir in $subdirs; do
        if [ "$(find "$subdir" -maxdepth 1 -type f)" ]; then
            echo "$(basename "$subdir")" >> "$output_file"
        else
            check_including_file "$subdir"
            if [ $? -eq 1 ]; then
                echo "$(basename "$subdir")" >> "$output_file"
            fi
        fi
    done
    echo "" >> "$output_file"
}

# サブディレクトリにファイルがあるかどうかを再起的に確認する
check_including_file() {
    local path="$1"
    # 指定されたディレクトリ内のサブディレクトリを取得
    subdirs=$(find "$path" -mindepth 1 -maxdepth 1 -type d)
    for subdir in $subdirs; do
        # サブディレクトリ直下にファイルが存在するか
        if [ "$(find "$subdir" -maxdepth 1 -type f)" ]; then
            return 1
        else
            check_including_file "$subdir"
            if [ $? -eq 1 ]; then
                return 1 # 再起でファイルが見つかった場合も1を返す
            fi
        fi
    done
    return 0
}

# 各パスに対して関数を呼び出す
directories_with_files "./css"
directories_with_files "./script"
directories_with_files "./WEB-INF/pages"

# 完了メッセージを表示
echo "ファイルが含まれているディレクトリの一覧が $output_file に出力されました。"
cd "$current_dir"

参照

新しいLinuxの教科書

タイトルとURLをコピーしました