シェルスクリプト チートシート

cheatsheet

変数の基本的な操作と、便利なパラメータ展開についてまとめます。

操作 コマンド / 構文 説明
変数宣言と代入
VAR_NAME="value"
変数名を指定し、値を代入します。= の前後にスペースは入れません。
変数参照
echo "$VAR_NAME"
$ を変数名の前に付けます。ダブルクォートで囲むと、空白を含む値も正しく扱えます。
コマンド実行結果の代入
RESULT=$(command)
または
RESULT=`command`
コマンドの標準出力を変数に代入します。$() の方がネスト可能で推奨されます。
未定義または空の場合のデフォルト値
${VAR:-default}
VAR が未定義または空文字列の場合、default を返します。VAR の値は変更されません。
未定義または空の場合にデフォルト値を代入
${VAR:=default}
VAR が未定義または空文字列の場合、defaultVAR に代入し、その値を返します。
未定義または空の場合にエラー
${VAR:?error message}
VAR が未定義または空文字列の場合、error message を標準エラー出力に出力してスクリプトを終了します。
未定義または空でない場合に代替値
${VAR:+alternative}
VAR が未定義または空文字列でない場合、alternative を返します。空の場合は空文字列を返します。
前方最短一致で削除
${VAR#pattern}
変数VARの先頭からpatternに最短一致する部分を削除します。 (例: path="/usr/bin/gcc"; echo "${path#*/}" -> usr/bin/gcc)
前方最長一致で削除
${VAR##pattern}
変数VARの先頭からpatternに最長一致する部分を削除します。 (例: path="/usr/bin/gcc"; echo "${path##*/}" -> gcc)
後方最短一致で削除
${VAR%pattern}
変数VARの末尾からpatternに最短一致する部分を削除します。 (例: filename="script.sh.bak"; echo "${filename%.*}" -> script.sh)
後方最長一致で削除
${VAR%%pattern}
変数VARの末尾からpatternに最長一致する部分を削除します。 (例: filename="script.sh.bak"; echo "${filename%%.*}" -> script)
最初の一致部分を置換
${VAR/pattern/string}
変数VAR内で最初にpatternに一致した部分をstringに置換します。
全ての一致部分を置換
${VAR//pattern/string}
変数VAR内でpatternに一致した全ての部分をstringに置換します。
文字列の長さを取得
${#VAR}
変数VARに含まれる文字列の長さを返します。

特殊変数 ✨

シェルが自動的に設定する特殊な変数です。

変数 説明
$? 直前に実行されたコマンドの終了ステータス (0は成功、それ以外はエラー)。
$# スクリプトや関数に渡された引数の数。
$@ 全ての引数を個別の文字列として展開 ("$1" "$2" ...)。ループなどで引数を扱う場合に推奨。
$* 全ての引数を一つの文字列として展開 ("$1c$2c..." ここで c$IFS の最初の文字)。
$$ 現在のシェルのプロセスID (PID)。
$! 最後にバックグラウンドで実行したジョブのプロセスID (PID)。
$0 実行されているスクリプト自身の名前。
$1, $2, … スクリプトや関数に渡された個々の引数。
$_ 直前に実行したコマンドの最後の引数。
$IFS Internal Field Separator. シェルが単語分割や`read`コマンドでの区切り文字として使用する文字セット (デフォルトはスペース、タブ、改行)。

条件分岐 (if, case)

条件に応じて処理を分岐させるための構文です。

if 文

# 基本形
if command; then
  # command の終了ステータスが 0 (成功) の場合に実行
  echo "Success!"
elif other_command; then
  # command が失敗し、other_command が成功した場合に実行
  echo "Alternative success!"
else
  # 上記のいずれの条件も満たさない場合に実行
  echo "Failure!"
fi

# test コマンド ([ ]) を使用
NUM=10
if [ "$NUM" -eq 10 ]; then
  echo "Number is 10."
fi

# [[ ]] を使用 (bash/ksh/zsh 拡張)
STR="hello"
if [[ "$STR" == "hello" ]] && [[ -n "$STR" ]]; then
  echo "String is 'hello' and not empty."
fi

case 文

FRUIT="apple"
case "$FRUIT" in
  apple)
    echo "It's an apple. 🍎"
    ;; # 各パターンの終わりには ;; が必要
  banana|orange)
    echo "It's a banana or an orange. 🍌🍊"
    ;;
  *) # デフォルトの処理 (どのパターンにもマッチしない場合)
    echo "Unknown fruit."
    ;;
esac # case 文の終わり

test コマンドと条件式

if 文や while 文でよく使われる条件評価の方法です。[ expression ] または test expression、あるいはより高機能な [[ expression ]] (bash/ksh/zsh) を使用します。

種別 演算子 説明
数値比較 -eq 等しい (equal) [ "$a" -eq "$b" ]
-ne 等しくない (not equal) [ "$a" -ne "$b" ]
-gt より大きい (greater than) [ "$a" -gt "$b" ]
-lt より小さい (less than) [ "$a" -lt "$b" ]
-ge 以上 (greater or equal) [ "$a" -ge "$b" ]
-le 以下 (less or equal) [ "$a" -le "$b" ]
文字列比較 = or == 文字列が等しい (==[[ ]] 内で推奨) [ "$s1" = "$s2" ], [[ "$s1" == "$s2" ]]
!= 文字列が等しくない [ "$s1" != "$s2" ], [[ "$s1" != "$s2" ]]
-z 文字列が空 (zero length) [ -z "$str" ]
-n 文字列が空でない (non-zero length) [ -n "$str" ]
<, > 辞書順比較 ([[ ]] 内でのみ) [[ "$s1" < "$s2" ]]
ファイル/ディレクトリ
テスト
-e ファイルまたはディレクトリが存在する (exists) [ -e "/path/to/file" ]
-f 通常のファイルが存在する (file) [ -f "/path/to/file" ]
-d ディレクトリが存在する (directory) [ -d "/path/to/dir" ]
-s ファイルが存在し、サイズが 0 より大きい (size) [ -s "/path/to/file" ]
-r ファイルが存在し、読み取り可能 (readable) [ -r "/path/to/file" ]
-w ファイルが存在し、書き込み可能 (writable) [ -w "/path/to/file" ]
-x ファイルが存在し、実行可能 (executable) [ -x "/path/to/script" ]
-L or -h シンボリックリンクである [ -L "/path/to/link" ]
FILE1 -nt FILE2 FILE1 が FILE2 より新しい (newer than) [ "file1.txt" -nt "file2.txt" ]
FILE1 -ot FILE2 FILE1 が FILE2 より古い (older than) [ "file1.txt" -ot "file2.txt" ]
論理演算子 -a or && AND (&&[[ ]] 内または if command1 && command2 形式で使用) [ "$a" -gt 0 -a "$b" -lt 10 ], [[ "$a" -gt 0 && "$b" -lt 10 ]]
-o or || OR (||[[ ]] 内または if command1 || command2 形式で使用) [ -f "$f1" -o -d "$f2" ], [[ -f "$f1" || -d "$f2" ]]
! NOT [ ! -f "/tmp/lock" ], [[ ! -f "/tmp/lock" ]]

注意: [ ] 内では <> はリダイレクトとして解釈されるため、\<\> のようにエスケープする必要があります。[[ ]] を使う方が安全で、より多くの機能(パターンマッチング ==, != や正規表現 =~ など)を提供します。

繰り返し処理 (Loops) 🔄

特定の処理を繰り返し実行するための構文です。

for ループ

# リストを使う (スペース区切り)
echo "--- List loop ---"
for item in apple banana cherry; do
  echo "Fruit: $item"
done

# 配列を使う (Bash 4+)
echo "--- Array loop ---"
fruits=("apple" "banana" "cherry pie")
for item in "${fruits[@]}"; do # ダブルクォートと @ が重要
  echo "Fruit: $item"
done

# C スタイルの for ループ (bash/ksh/zsh)
echo "--- C-style loop ---"
for (( i=0; i<5; i++ )); do
  echo "Number: $i"
done

# コマンドの出力を行ごとに処理
echo "--- Command output loop ---"
find . -maxdepth 1 -type f -print0 | while IFS= read -r -d $'\0' file; do
  echo "Found file: $file"
done
# 注意: 上記の while ループはサブシェルで実行されることがあるため、
# ループ内で設定した変数がループの外で使えない場合があります。
# Bash 4.2+ では `shopt -s lastpipe` で回避できることもあります。

# find と xargs を使う方が堅牢な場合が多い
# find . -maxdepth 1 -type f -print0 | xargs -0 -I {} echo "Processing: {}"

while ループ

# 条件が真の間繰り返す
echo "--- While loop ---"
count=0
while [ "$count" -lt 3 ]; do
  echo "Count is $count"
  count=$((count + 1)) # または ((count++))
done

# ファイルを1行ずつ読み込む (一般的な方法)
echo "--- Reading file line by line ---"
while IFS= read -r line; do
  echo "Line: $line"
done < "input.txt"
# IFS= で行頭・行末の空白削除を防ぐ
# read -r でバックスラッシュのエスケープを防ぐ

until ループ

# 条件が真になるまで繰り返す
echo "--- Until loop ---"
counter=5
until [ "$counter" -le 0 ]; do
  echo "Counter: $counter"
  ((counter--))
done

ループ制御

コマンド 説明
break [n] 現在のループ (または n 階層分のネストしたループ) を抜けます。
continue [n] 現在のループの残りの処理をスキップし、次のイテレーションを開始します (または n 階層分のループで)。

関数

処理をまとめて再利用可能にするための仕組みです。

基本的な定義と呼び出し

# 書き方 1
function my_function {
  echo "Hello from my_function!"
  echo "引数1: $1"
  echo "引数2: $2"
  echo "全ての引数: $@"
  echo "引数の数: $#"
  local local_var="これはローカル変数です" # local で宣言
  return 0 # 終了ステータスを返す (0 は成功)
}

# 書き方 2 (推奨)
my_other_function() {
  echo "Hello from my_other_function!"
  if [ "$#" -lt 1 ]; then
    echo "エラー: 引数が必要です" >&2 # エラーメッセージは標準エラー出力へ
    return 1 # 失敗を示す終了ステータス
  fi
  echo "受け取った引数: $1"
}

# 関数の呼び出し
my_function "arg1" "argument 2"
status=$? # 関数の終了ステータスを取得
echo "my_function の終了ステータス: $status"

my_other_function # 引数なしで呼び出す -> エラー
my_other_function "Data"

関数の注意点

  • 関数内で宣言された変数は、デフォルトではグローバルスコープを持ちます。local キーワードを使ってローカル変数として宣言することが推奨されます。
  • 関数は呼び出される前に定義されている必要があります。
  • 関数はコマンドと同様に扱われ、終了ステータスを返します。return コマンドで明示的に指定できます。指定しない場合は、関数内で最後に実行されたコマンドの終了ステータスが返ります。
  • 関数への引数は $1, $2, …, $@, $# で参照します。これはスクリプト本体への引数と同じ仕組みです。
  • 関数からの「戻り値」として複雑なデータ(文字列など)を返したい場合は、標準出力に関数結果を出力し、呼び出し側でコマンド置換 result=$(my_function arg) を使って受け取るのが一般的です。

ファイル操作とリダイレクト 📂

ファイルの作成、操作、および入出力の制御に関するコマンドと構文です。

基本的なファイル操作コマンド

コマンド 説明
touch ファイルのタイムスタンプを更新、または存在しない場合は空ファイルを作成。 touch newfile.txt
mkdir ディレクトリを作成。-p オプションで親ディレクトリも同時に作成可能。 mkdir newdir, mkdir -p path/to/newdir
cp ファイルやディレクトリをコピー。-r オプションでディレクトリを再帰的にコピー。 cp file1.txt file2.txt, cp -r dir1/ dir2/
mv ファイルやディレクトリを移動または名前変更。 mv oldname.txt newname.txt, mv file.txt target_dir/
rm ファイルを削除。-r オプションでディレクトリを再帰的に削除。-f オプションで確認なしに強制削除。(注意して使用!) rm file.txt, rm -rf directory/
ln リンクを作成。デフォルトはハードリンク。-s オプションでシンボリックリンクを作成。 ln target link_name, ln -s target symlink_name
find ファイルやディレクトリを検索。豊富な検索条件とアクションを指定可能。 find /path -name "*.log" -type f -mtime -7
chmod ファイルやディレクトリのパーミッションを変更。 chmod 755 script.sh, chmod u+x script.sh
chown ファイルやディレクトリの所有者・グループを変更。 chown user:group file.txt

リダイレクトとパイプ

構文 説明
> file 標準出力 (STDOUT, fd 1) をファイルにリダイレクト (上書き)。 ls -l > filelist.txt
>> file 標準出力 (STDOUT, fd 1) をファイルにリダイレクト (追記)。 echo "Log message" >> app.log
< file ファイルの内容を標準入力 (STDIN, fd 0) としてコマンドに渡す。 sort < data.txt
2> file 標準エラー出力 (STDERR, fd 2) をファイルにリダイレクト (上書き)。 find / -name core 2> errors.log
2>> file 標準エラー出力 (STDERR, fd 2) をファイルにリダイレクト (追記)。 ./run_script.sh 2>> script_errors.log
&> file
>& file
標準出力と標準エラー出力の両方をファイルにリダイレクト (上書き)。(bash/zsh 拡張) make &> build.log
&>> file 標準出力と標準エラー出力の両方をファイルにリダイレクト (追記)。(bash 4+) ./long_process &>> process.log
2>&1 標準エラー出力 (fd 2) を標準出力 (fd 1) と同じ場所にリダイレクト。出力の順番に注意。 command > output.log 2>&1
| パイプ。前のコマンドの標準出力を、次のコマンドの標準入力に接続。 ps aux | grep httpd | wc -l
<<< "string" ヒアストリング (Here String)。文字列をコマンドの標準入力として渡す。(bash/ksh/zsh) bc <<< "10 + 5"
<< DELIMITER
…(内容)…
DELIMITER
ヒアドキュメント (Here Document)。複数行の文字列をコマンドの標準入力として渡す。区切り文字 (DELIMITER) は任意。
cat << EOF
これはヒアドキュメントです。
複数行の入力を
そのまま渡せます。
変数展開も可能: $HOME
EOF
<<- DELIMITER
…(内容)…
DELIMITER
ヒアドキュメント (タブ抑制)。行頭のタブ文字が無視されるため、インデントされたスクリプト内で使いやすい。
if true; then
  cat <<- MSG
    インデントされたメッセージ。
    行頭のタブは無視される。
  MSG
fi

テキスト処理 ✂️

テキストデータのフィルタリング、変換、抽出に役立つコマンド群です。

コマンド 主な用途 簡単な例
echo 文字列や変数の内容を標準出力に表示。-n で改行なし、-e でエスケープシーケンス解釈 (bash)。 echo "Hello, $USER", echo -e "Line1\nLine2"
printf 書式を指定して文字列を出力。echo より移植性が高く、細かな制御が可能。 printf "Name: %s, Age: %d\n" "Alice" 30
cat ファイルの内容を連結して標準出力に表示。-n で行番号表示。 cat file1.txt file2.txt
grep パターンに一致する行を検索・表示。-i 大小文字無視, -v 不一致行, -r 再帰検索, -E 拡張正規表現, -o 一致部分のみ。 grep "error" log.txt, ps aux | grep -i process
sed ストリームエディタ。テキストの置換、削除、挿入などを行う。-e スクリプト指定, -i ファイル直接編集 (注意)。 sed 's/old/new/g' file.txt, sed '/^#/d' config.conf
awk 強力なテキスト処理言語。列指向のデータ処理が得意。パターンとアクションで構成。 awk '{print $1, $3}' data.txt, awk -F':' '$3 >= 1000 {print $1}' /etc/passwd
cut 行の一部 (フィールドや文字) を切り出す。-d 区切り文字指定, -f フィールド指定, -c 文字位置指定。 cut -d':' -f1 /etc/passwd, cut -c1-10 file.txt
sort 行を並び替える。-n 数値順, -r 逆順, -k フィールド指定, -u 重複削除。 sort data.txt, sort -nr -k3 data.txt
uniq 隣接する重複行を削除またはカウント。通常 sort と組み合わせて使う。-c カウント表示, -d 重複行のみ表示。 sort data.txt | uniq -c
tr 文字の置換や削除。-d 文字削除, -s 連続する文字を1つに。 tr 'a-z' 'A-Z' < file.txt, echo "a,,b, c" | tr -s ','
head ファイルの先頭部分を表示。-n 行数指定 (デフォルト 10)。 head -n 5 access.log
tail ファイルの末尾部分を表示。-n 行数指定 (デフォルト 10), -f ファイルの更新を追跡表示。 tail -n 20 error.log, tail -f /var/log/syslog
wc ファイルや標準入力の行数、単語数、バイト数をカウント。-l 行数, -w 単語数, -c バイト数。 wc -l report.txt

プロセス管理 ⚙️

コマンドの実行方法や実行中のプロセスを制御するための機能です。

コマンド / 構文 説明
command & コマンドをバックグラウンドで実行する。シェルのプロンプトがすぐに返ってくる。 sleep 60 &
jobs 現在のシェルで実行中のバックグラウンドジョブを表示する。 jobs
fg [%job_id] バックグラウンドジョブをフォアグラウンドに戻す。%job_id を省略すると、直近のジョブが対象。 fg %1, fg
bg [%job_id] 停止しているバックグラウンドジョブを再開させる (バックグラウンドで)。 bg %2
kill [-signal] PID
kill [-signal] %job_id
プロセスやジョブにシグナルを送る。デフォルトは TERM (終了)。-9 は強制終了 (KILL)。 kill 12345, kill -9 %1, kill -HUP 5432
pkill [-signal] pattern プロセス名などのパターンにマッチするプロセスにシグナルを送る。 pkill firefox, pkill -f my_script.py
killall [-signal] process_name プロセス名に完全に一致するプロセスにシグナルを送る。 killall httpd
ps [options] 実行中のプロセスを表示する。aux (BSD形式) や -ef (SystemV形式) がよく使われる。 ps aux, ps -ef | grep sshd
top / htop 実行中のプロセスをリアルタイムで表示し、リソース使用状況を確認する。htop はより高機能。 top, htop
wait [PID | %job_id] 指定したプロセスやジョブが終了するまで待機する。引数なしの場合は、現在のシェルから起動した全てのバックグラウンドジョブの終了を待つ。 ./process1.sh & pid1=$!
./process2.sh & pid2=$!
wait $pid1 && echo "Process 1 finished"
wait $pid2
echo "All processes finished"
nohup command & ログアウト後もコマンドを実行し続ける。出力は通常 `nohup.out` にリダイレクトされる。 nohup ./long_running_task.sh &
Ctrl+C フォアグラウンドで実行中のプロセスに INT シグナルを送り、中断させる。 (実行中のターミナルで押下)
Ctrl+Z フォアグラウンドで実行中のプロセスに TSTP シグナルを送り、一時停止 (サスペンド) させてバックグラウンドに移す。bgfg で再開可能。 (実行中のターミナルで押下)

複数の値をまとめて格納できる変数です。(POSIX sh には配列はありません)

操作 構文 (Bash) 説明
配列の宣言と初期化
my_array=("apple" "banana" "cherry pie")
または
declare -a my_array
my_array[0]="apple"
my_array[1]="banana"
複数の値を括弧で囲み、スペースで区切って代入します。インデックスは 0 から始まります。
要素へのアクセス
echo "${my_array[1]}"
インデックスを指定して要素を取り出します (例: 2番目の要素 ‘banana’)。{} で囲むのが安全です。
全ての要素を取得 (個別の単語)
echo "${my_array[@]}"
配列の全ての要素を、スペースで区切られた個別の単語として展開します。ダブルクォートで囲むと、空白を含む要素も正しく扱われます ("${my_array[@]}" -> "apple" "banana" "cherry pie")。ループでよく使います。
全ての要素を取得 (単一の単語)
echo "${my_array[*]}"
配列の全ての要素を、$IFS の最初の文字で連結した単一の単語として展開します。ダブルクォートで囲むと、全体が1つの文字列になります ("${my_array[*]}" -> "apple banana cherry pie")。
要素数を取得
echo "${#my_array[@]}"
配列に含まれる要素の数を返します。
配列のインデックス一覧を取得
echo "${!my_array[@]}"
配列の全てのインデックス (キー) を返します (例: 0 1 2)。
要素の追加
my_array+=("new element")
または
my_array[3]="orange"
配列の末尾に新しい要素を追加したり、特定のインデックスに代入したりします。
要素の削除
unset my_array[1]
指定したインデックスの要素を削除します。配列全体の削除は unset my_array
連想配列 (Bash 4+)
declare -A assoc_array
assoc_array["name"]="Alice"
assoc_array["age"]=30
echo "${assoc_array["name"]}"
echo "${!assoc_array[@]}" # キー一覧
文字列をキーとして値を持つ配列 (ハッシュマップ、辞書)。declare -A で宣言が必要です。

スクリプト作成のTips 💡

より堅牢で保守しやすいシェルスクリプトを作成するためのヒントです。

Shebang (シバン)

#!/bin/bash
# または #!/usr/bin/env bash (環境に応じて bash を探す)

# スクリプトの内容...
echo "Hello from Bash script!"

スクリプトの最初の行に記述し、どのインタプリタでスクリプトを実行するかを指定します。chmod +x script.sh で実行権限を与えれば、./script.sh で直接実行できます。

エラー処理と安全性向上 (set コマンド)

set -e  # コマンドがエラー(非ゼロ終了)になったら直ちにスクリプトを終了する
set -u  # 未定義の変数を使用しようとしたらエラーにして終了する (set -o nounset)
set -o pipefail # パイプラインの途中でコマンドが失敗した場合、パイプライン全体の終了ステータスをその失敗したコマンドのものにする

# 上記をまとめて書く場合
set -euo pipefail

# スクリプトのデバッグ用
# set -x # 実行されるコマンドとその引数を標準エラー出力に表示する

これらのオプションをスクリプトの先頭に記述することで、予期せぬエラーによる問題を防ぎ、デバッグをしやすくします。

trap コマンド (シグナル処理)

#!/bin/bash
set -euo pipefail

cleanup() {
  echo "Cleaning up temporary files..."
  rm -f /tmp/myscript.*
  echo "Cleanup finished."
}

# スクリプト終了時(EXIT)や中断シグナル(INT, TERM)を受け取った時に cleanup 関数を実行
trap cleanup EXIT INT TERM

echo "Creating temporary file..."
TMPFILE=$(mktemp /tmp/myscript.XXXXXX)
echo "Temporary file: $TMPFILE"
echo "Data" > "$TMPFILE"

echo "Simulating work..."
sleep 10

echo "Script finished normally."
# cleanup 関数が EXIT シグナルで呼ばれる

trap コマンドを使うと、スクリプトが特定のシグナルを受け取ったとき(例えば Ctrl+C で中断された場合など)や、スクリプトが終了する際に特定のコマンドや関数を実行させることができます。一時ファイルの削除などの後始末処理に便利です。

コマンドライン引数の処理

# シンプルな位置引数
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "All arguments: $@"
echo "Number of arguments: $#"

# getopts を使ったオプション処理 (POSIX 準拠)
while getopts ":a:b:c" opt; do
  case $opt in
    a)
      option_a_arg="$OPTARG"
      echo "Option -a specified with argument: $option_a_arg"
      ;;
    b)
      option_b_arg="$OPTARG"
      echo "Option -b specified with argument: $option_b_arg"
      ;;
    c)
      option_c_flag=true
      echo "Option -c specified (flag)"
      ;;
    \?) # 不明なオプション
      echo "Invalid option: -$OPTARG" >&2
      exit 1
      ;;
    :) # 引数が必要なオプションに引数がない
      echo "Option -$OPTARG requires an argument." >&2
      exit 1
      ;;
  esac
done
shift $((OPTIND - 1)) # オプション部分を取り除く

# 残りの引数 (オプション以外の引数) を処理
echo "Remaining arguments: $@"

$1, $2 などで直接引数を参照できますが、オプション (-f--file など) を伴う場合は getopts (bash 組み込み) や getopt (外部コマンド、より高機能だが挙動に注意) を使うと便利です。

コメントと可読性

# これは一行コメントです

: <<'END_COMMENT'
これは複数行コメントです。
シングルクォートで囲まれたヒアドキュメントを
何もしないコマンド : にリダイレクトすることで実現します。
変数展開 ($VAR) やコマンド置換 $(cmd) も無効になります。
END_COMMENT

# 処理の区切りや複雑な部分にはコメントを入れる
# ファイルが存在するか確認
if [ -f "$input_file" ]; then
    # 存在すれば処理を実行
    process_file "$input_file"
else
    # 存在しなければエラーメッセージ
    echo "Error: Input file not found: $input_file" >&2
    exit 1
fi

後で自分や他の人が読んでも理解できるように、適切なコメントを追加し、インデントや空行を使ってコードを読みやすく構成しましょう。

コメント

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