シェルスクリプトの基本と活用例まとめ|Linux作業を自動化する方法

シェルスクリプトの基本と活用例まとめ|Linux作業を自動化する方法

シェルスクリプトは、Linux環境での「作業を効率化したい」「定型的な業務を自動化したい」と考えるエンジニアにとって実務で力を発揮するスクリプト言語です。

この記事では、インフラやデータを扱う初中級者エンジニア向けに、シェルスクリプトの基本的な構文から、実務で役立つ活用例までを体系的に解説します。変数や条件分岐、ループ処理といった基本から、エラー対策まで、一通り学べる内容となっているので、ぜひ最後までご覧ください。

エンジニアファクトリーTOP

エンジニアファクトリーでは、フリーランスエンジニアの案件・求人をご紹介。掲載中の案件は9,000件以上。紹介する案件の平均年商は810万円(※2023年4月 首都圏近郊のITエンジニア対象)で、ご経験・志向に合った案件と出会えます。

簡単なプロフィール入力ですぐにサポートを開始。案件にお困りのITフリーランスの方やより高条件の案件と巡り合いたいと考えている方は、ぜひご登録ください。

シェルスクリプトとは?

シェルスクリプトは、LinuxやUNIX系のOSで、定型的な作業を自動化し、業務効率を飛躍的に向上させる際に効果的なスクリプト(自動化の仕組み)です。

ここでは、初心者の方でも、シェルスクリプトがなぜ必要なのか、どのような場面で活躍するのかをイメージできるよう説明します。

Linuxにおける「シェル」の役割と種類

シェルとは、ユーザーが入力したコマンドを解釈し、OSの中核である「カーネル」に伝えるためのインターフェースプログラムです。

キーボードからlsやcdといったコマンドを入力すると、シェルがそれを受け取り、「ファイルを一覧表示せよ」「ディレクトリを移動せよ」といった指示に変換してカーネルに渡します。カーネルがその指示を実行し、結果をシェルに返すと、最終的に私たちの画面に表示される、という仕組みです。

代表的なシェルとしては、「bash(Bourne-Again SHell)」や、「sh(Bourne SHell)」、C言語に似た文法を持つ「csh」や「tcsh」、より高機能な「zsh」などがあります。

どのシェルを使うかによって、コマンドの補完機能やスクリプトの書き方が少しずつ異なります。とくに、bashはデファクトスタンダードとして広く使われているため、まずはbashの学習から始めるのが良いでしょう。

シェルスクリプトとは?業務自動化を実現する手段

シェルスクリプトとは、シェルが実行できるコマンドや制御構文をひとつのファイルにまとめたものです。「シェルのコマンドを羅列した台本」であり、台本を読み込ませて、一連の処理を自動的に実行可能です。

たとえば、「特定のディレクトリに移動し、ファイルを圧縮してバックアップを作成し、古いバックアップを削除する」といった一連の作業を考えてみましょう。これを毎日手作業で行うのは非効率的で、ミスも起こりやすくなります。シェルスクリプトでコマンドを記述しておけば、スクリプトを実行するだけで、全ての処理が自動で完了します。この「自動化」こそが、シェルスクリプトを活用する最大の目的です。

定期的なサーバーのメンテナンスやログファイルの監視とレポート作成、複数のサーバーへの一括設定変更など、活用範囲は多岐にわたります。コマンドライン環境で同じ作業を何度も繰り返しているなら、シェルスクリプトによる自動化を検討しましょう。

他のプログラミング言語との違い

シェルスクリプトは、PythonやC言語といった他のプログラミング言語と比較されるものの、目的と特性には明確な違いがあります。

最大の違いは、シェルスクリプトが「OSのコマンドを組み合わせること」に特化している点です。ファイル操作やプロセス管理、ネットワーク設定など、Linuxに標準で備わっている強力なコマンド群をパイプやリダイレクトで連携させて使用します。既存のツールを「接着剤」のようにつなぎ合わせて、目的の処理を効率的に実現します。多くのシェルスクリプトはインタプリタ言語であり、コンパイルを必要としません。コードを記述したらすぐに実行できる手軽さが特徴です。

一方、PythonやC言語は、汎用的なプログラミング言語であり、OSのコマンドだけでは実現が難しい、複雑なロジックをゼロから構築できます。シェルスクリプトの方が短い記述で済む一方で、処理が複雑になるとPythonなどの方が可読性が高くなります。目的に応じて使い分けましょう。

シェルとシェルスクリプトの役割の違い

「シェル」と「シェルスクリプト」の役割には明確な違いがあります。シェルは「対話的な操作環境」であり、ユーザーがコマンドをひとつずつ入力し、結果を即座に確認するためのものです。ファイルの検索や設定の確認など、単発のタスクをアドホックに実行する場面で利用されます。

一方、シェルスクリプトは「非対話的な自動処理用のファイル」であり、あらかじめ処理の手順を記述して、一連のタスクをまとめて自動実行するために使われます。たとえば、夜間にサーバーのバックアップを取得する、システムの状態を定期的にチェックして異常があれば通知する、といった定型的な業務は、シェルスクリプトの得意分野です。

シェルの対話的環境では、手間がかかる上に、人的ミスも発生しやすくなります。処理の手順をスクリプトファイルに保存して実行すれば、手間やミスを削減可能です。単発や確認はシェル、反復や夜間処理はシェルスクリプト、という使い分けが実務では扱いやすいです。

シェルスクリプトの書き方と実行方法

シェルスクリプトの概念を理解したら、実際にスクリプトを作成し、実行する方法を学びましょう。この章では、スクリプト作成の第一歩として、ファイルの命名規則やShebang(シェバン)の意味、実行権限の設定方法までわかりやすく解説します。

スクリプトファイルの作成と拡張子(.sh)の付け方

シェルスクリプトの作成は、単純なテキストファイルの作成から始まります。最初のステップとして、touchコマンドを使って空のファイルを作成し、処理内容を記述していきましょう。

スクリプトファイルの名前は何でも構いませんが、シェルスクリプトであることが一目で分かるように、ファイル名の末尾に.shという拡張子を付けるのが一般的な慣習です。

ファイルの作成手順は以下の通りです。

STEP
touchコマンドで空のファイルを作成します。
touch myscript.sh
STEP
vimやnanoなどのテキストエディタでファイルを開き、処理内容を記述します。
vim myscript.sh
STEP
エディタ内で、実行したいコマンドを記述します。ここでは例として、画面にメッセージを表示するechoコマンドを記述します。
# ファイル作成
touch myscript.sh

# エディタで開く
vim myscript.sh

# 内容(shebangは1行目)
#!/bin/bash
echo "Hello, Shell Script!"

このように、普段使っているテキストエディタでコマンドを記述するだけで、スクリプトの本体は完成です。

Shebang(#!/bin/bash)の意味と使い方

スクリプトファイルを作成する際、ファイルの1行目に必ず記述すべき重要な記述があります。それが「#!/bin/bash」というShebang(シェバン)です。

Shebangは単なるコメントではなく、このスクリプトファイルをどのインタプリタ(シェル)で実行するかを指定するための、重要な役割を持っています。Linuxカーネルは、スクリプトが実行される際にこの1行目の「#!」を読み取り、その後ろに続くパス(この場合は/bin/bash)に指定されたプログラムを使って、ファイルの内容を解釈・実行します。

もしShebangを記述しなかった場合、ユーザーが使用しているシェルでスクリプトが実行されるため、意図しない動作を引き起こしかねません。互換性や動作の安定性を担保する上で、Shebangの記述は不可欠です。「/bin/sh」を指定すればより多くの環境での動作が期待できます。一般的には機能が豊富な「/bin/bash」を指定します。

実行権限の設定と実行方法(sh/bash)

シェルスクリプトのファイルを作成しただけでは、プログラムとして実行できません。セキュリティ上の理由から、Linuxではファイルが作成された時点では実行権限が与えられていないため、chmodコマンドを使って権限を付与する必要があります。

chmodはファイルのパーミッション(権限)を変更するコマンドで、以下のように+xを指定すれば実行権限を追加できます。

chmod +x myscript.sh

実行権限を付与した後のスクリプトの実行方法は主に以下の2種類です。

パスを指定して実行する

ファイル名の前に「./」を付けて、カレントディレクトリにあるファイルを直接実行します。使用されるのは、Shebangに記述されたインタプリタです。

./myscript.sh

シェルコマンドの引数として実行する

shや「bash」などのコマンドの引数にスクリプトファイルを指定する方法です。Shebangの指定は無視され、shやbashコマンドで強制的に実行されます。なお、shは環境によりdash等で実行されることがあり、Bash専用構文(配列・[[ ]]など)は動作しません。

bash myscript.sh

Shebangでインタプリタを明示している場合は、実行権限を付与して「./」で実行するのが一般的です。

基本コマンド(echo/date)で動作を確認する

スクリプトの実行環境が整ったら、正しく動作するかを確認しましょう。echoコマンドとdateコマンドは、特別な設定なしに利用でき、実行結果がすぐに画面に表示されるため、動作確認に最適です。

「echo」は、引数に指定した文字列や変数の内容を標準出力(通常は画面)に表示するコマンドです。一方、「date」を用いると、現在のシステム日時を表示します。実際に以下のスクリプトを作成し、実行してみましょう。

check.sh

#!/bin/bash

# 挨拶メッセージを表示
echo 

--- スクリプト開始 ---
現在の日時は以下の通りです:
Sat Aug 23 22:26:00 JST 2025
--- スクリプト終了 ---

# 完了メッセージを表示
echo "--- スクリプト終了 ---"

上記ファイルに実行権限を与えたのち、実行すると画面に以下の内容が表示されます。

--- スクリプト開始 ---
現在の日時は以下の通りです:
2025年 8月 23日 土曜日 22:26:00 JST
--- スクリプト終了 ---

まずは単純なスクリプトから始め、複雑な処理を追加していくのが、シェルスクリプト学習の近道です。

シェルスクリプトの変数とパラメータ展開の使い方

シェルスクリプトで自動化処理を実装する上で、変数の扱いは避けては通れません。変数を使えば、スクリプトの柔軟性と再利用性を向上させられます。ここからは、変数の基本的な定義方法から、参照、削除といった一連の操作方法を、具体的なコード例と共に紹介します。

変数の定義・参照・削除と特殊変数の使い方

シェルスクリプトにおける変数の扱いはシンプルです。変数を定義(代入)する際は「=」を使い、変数の値を参照(利用)する場合は、変数名の前に「$」を付けます。

# 変数 USER_NAME に "Taro" という文字列を定義USER_NAME="Taro"
# 変数 USER_NAME の内容を参照して表示echo "Hello, $USER_NAME"

また、シェルスクリプトには、予めシステムによって定義されている以下の「特殊変数」が存在します。特殊変数を使用すれば、スクリプトの情報を取得可能です。

特殊変数一覧

変数内容
$0実行しているスクリプト自身のファイル名
$1, $2, …スクリプト実行時に渡された引数(1番目、2番目…)
$#渡された引数の総数
$@渡されたすべての引数(個別の文字列として扱う)
$?直前に実行したコマンドの終了ステータス(成功=0、失敗≠0)
$$スクリプトのプロセスID

特殊変数を使いこなせれば、スクリプトの汎用性を向上させられます。

パラメータ展開とデフォルト値の指定方法

スクリプトを書いていると、変数が定義されているかどうか不確かな場合があります。変数が未定義の状態で参照しようとすると、意図しない挙動やエラーの原因となるため、${変数名:-デフォルト値}というパラメータ展開の記法が役立ちます。

この記法を使うと、「変数が未定義、または空文字列だった場合に、指定したデフォルト値を使用する」という安全な処理を一行で記述可能です。

パラメータ展開の主な記法

記法動作用途
${var:-word}var が未設定または空なら word を返す変数が未定義でも安全に値を使いたいとき
${var:=word}var が未設定または空なら word を返し、さらに var=word と代入するデフォルト値を設定しておきたいとき
${var:?message}var が未設定または空なら message をエラー表示してスクリプトを終了する必須パラメータをチェックしたいとき
${var:+word}var が設定されている場合に限り word を返す変数がセットされている場合だけ特定の処理をしたいとき

デフォルト値を指定したり、必須パラメータが欠けている場合にエラー終了させたりできるため、スクリプトの信頼性を向上させられます。

readonly/unsetによる変数制御

スクリプトの規模が大きくなると、意図しないところで変数の値が上書きされてしまうといった問題が発生しやすくなります。誤操作を防ぎ、スクリプトの安全性を高めるために使用するのが、readonlyとunsetというコマンドです。

readonlyコマンドは、指定した変数を「読み取り専用」にします。一度readonlyに設定した変数に再度値を代入しようとするとエラーが発生する仕組みです。

【readonly の実例】

#!/bin/bash
# 設定値を格納する変数を定義CONFIG_FILE="/etc/myapp.conf"
# 変数を読み取り専用にするreadonly CONFIG_FILE
echo "設定ファイルは: ${CONFIG_FILE} です"

一方、unset コマンドは、変数をメモリ上から削除する役割を持ちます。使い終わった変数を速やかに削除して、情報が意図せず残存するリスクを低減します。

【unset の実例】

#!/bin/bash
DB_PASSWORD="my-secret-password"
# ここでデータベースへの接続処理などを行うecho "データベースに接続します..."
# 使い終わったパスワード変数を削除unset DB_PASSWORD

readonlyによる重要な値の保護や、unsetでの不要な値の削除は、堅牢で安全なシェルスクリプトを作成するための重要な鍵です。

シェルスクリプトの条件分岐とループ処理の基本構文

これまでの解説で、コマンドを上から順に実行する方法を学びました。より実用的なスクリプトを作成するには、状況に応じて処理の流れを変える「条件分岐」と、同じ処理を何度も繰り返す「ループ処理」も不可欠です。構文の使い分けや処理を中断・スキップする方法などについて見ていきましょう。

if~then~fi構文とtest/[[ ]]の使い分け

シェルスクリプトで条件分岐を行う場合は、if 文を使います。基本の流れは「条件が真なら処理を実行する」というものです。if で始まり、fi(if を逆から書いたもの)で終わります。

基本構文

if 条件式; then
  # 条件が真のときの処理
elif 条件式; then
  # 別の条件が真のときの処理
else
  # どの条件にも当てはまらない場合の処理
fi

条件式には test コマンド([ ] と同義)か、拡張構文の [[ ]] を使います。

test([ ])

  • POSIX準拠で移植性が高い
  • 機能はシンプル
  • 論理演算に -a(AND)、-o(OR)を使用
if [ "$VAR" = "hello world" -a ${#VAR} -gt 10 ]; then
  echo "VARは 'hello world' と一致し、10文字より長いです。"
fi

[[ ]]

  • bash などで利用できる拡張構文
  • &&|| を使って直感的に書ける
  • パターンマッチ(== hello*)や正規表現(=~)も利用可能
  • クォート漏れなどのエラーを防ぎやすい
if [[ "$VAR" == hello* && ${#VAR} -gt 10 ]]; then
  echo "VARは 'hello' で始まり、10文字より長いです。"
fi

使い分けの目安

移植性を重視するなら [ ] を、bash での記述を前提とするなら [[ ]] を使うのがおすすめです。特別な理由がなければ、可読性や安全性の点から [[ ]] を使うのが一般的です。

case文で複数条件を処理する方法

if 文で elif を多用するとコードが長くなり、可読性が落ちやすくなります。ひとつの変数の値によって分岐を切り替えたいときは、case 文を使うとシンプルに書けます。

case 文は指定した変数の値を、in 以降に並べたパターンと順に照合し、最初に一致したものを実行します。

#!/bin/bash

echo "アクションを選択してください (start|stop|restart):"
read ACTION

case "$ACTION" in
  start)
    echo "サービスを開始します..."
    ;;
  stop)
    echo "サービスを停止します..."
    ;;
  restart)
    echo "サービスを再起動します..."
    ;;
  *)
    echo "無効なアクションです。"
    ;;
esac

各パターンの終わりには;;(セミコロン2つ)を記述しましょう。|を使えば複数のパターンをOR条件で結びつけられ、*は「その他すべて」を意味するデフォルトの分岐として機能します。esacはcaseを逆から書いたもので、case文の終わりを示します。

for/while/untilによるループ処理の基本

同じ、あるいは類似した処理を繰り返し実行したい場合、ループ構文が不可欠です。シェルスクリプトでは主にfor、while、untilの3種類のループが使われます。

forループは、あらかじめ決められたリスト(配列や数値の範囲など)の要素をひとつずつ取り出して処理する場合に最適です。

【forループの構文と例】

# 配列の要素をループ
SERVERS=("web01" "db01" "ap01")
for SVR in "${SERVERS[@]}"; do
  echo "${SVR}に接続します..."
done

# 連続する数値をループ
for i in {1..5}; do
  echo "カウント: $i"
done

# コマンドの出力結果をループ
for F in *.log; do
  [ -e "$F" ] || continue
  echo "ログファイル: $F を処理します"
done

whileループは、指定した条件式が真(true)である間、処理を繰り返します。ファイルを1行ずつ読み込む処理や、特定の状態になるまで待機する処理など、繰り返しの回数が事前に決まっていない場合に有効です。

【whileループの例:ファイルを1行ずつ読む】

# whileループの例:ファイルを1行ずつ読む
while read -r LINE; do
  echo "読み込んだ行: $LINE"
done < "input.txt"

untilループはwhileの逆で、指定した条件式が真(true)になるまで処理を繰り返します。

【until ループの例:コマンドの成功を待つ】

# untilループの例:コマンドの成功を待つ
# サーバーが起動するまで(pingが成功するまで)待つ
until ping -c 1 example.com; do
  echo "サーバーが起動していません。5秒後にリトライします..."
  sleep 5
done
echo "サーバーが起動しました。"

ループ構文を使い分ければ、さまざまな自動化処理を柔軟に実装可能です。

break/continueでループを制御する

ループ処理を実行している中で、特定の条件が満たされた場合に、ループを途中で抜けたり、現在の回の処理だけをスキップして次の回に進みたい場合があります。ループの制御を行うために、breakcontinue というコマンドが用意されています。

break コマンドは、現在実行中のループ(for、while、until)を完全に中断し、ループ構文の直後の処理に制御を移します。

【breakの例:特定の値を見つけたらループを抜ける】

for i in {1..10}; do
  if [[ $i -eq 5 ]]; then
    echo "5を見つけたのでループを中断します。"
    break
  fi
  echo "現在の数値は $i です。"
done

echo "ループが終了しました。"

一方、continueは、ループを完全に中断するのではなく、現在の回の処理のみをスキップし、すぐに次の回の処理を開始する際に使用するコマンドです。

【continue の例:特定のファイルを処理対象外にする】

for FILE in *.log; do
  # "secure.log" というファイルはスキップする
  if [[ "$FILE" == "secure.log" ]]; then
    continue
  fi
  echo "${FILE}をバックアップします..."
  # ここにバックアップ処理を記述
done

この例では、secure.log 以外のすべての .log ファイルに対してバックアップ処理が行われます。

シェルスクリプトで使う標準入出力と便利なコマンド

シェルスクリプトは、単体のコマンドが優れているのみならず、コマンドの「入力」と「出力」を自在に組み合わせられる点も強みです。ここからは、「リダイレクト」「パイプ」「ヒアドキュメント」といった、標準入出力の制御方法について詳しく見ていきましょう。

標準出力/エラー出力のリダイレクト

コマンドの実行結果を画面に表示するのではなく、ファイルに保存したい場面もあるでしょう。「出力の流れを別の向きに変える」操作をリダイレクトと呼び、記号 >>> を使って実現します。

>(上書き保存)は、コマンドの標準出力を指定したファイルに書き出す処理です。ファイルが既に存在していた場合、その内容は消去され、新しい内容で上書きされます。

【例:dateコマンドの結果を file.txt に上書き保存】

date > file.txt

>>(追記保存)は、コマンドの標準出力を指定したファイルに追記します。ファイルが存在しない場合は新規に作成されます。

【例:dateコマンドの結果を file.txt に追記保存】

date >> file.txt

また、コマンドは正常な出力(標準出力)とは別に、エラーメッセージ用の出力(標準エラー出力)を持っています。これらを区別するために使用されるのが、ファイルディスクリプタ番号(標準出力:1、標準エラー出力:2)です。

2>(エラー出力のリダイレクト)と記述すれば、標準エラー出力がファイルに保存されます。

【例:存在しないファイルにアクセスした際のエラーを error.log に保存】

ls non_existent_file 2> error.log

標準出力と標準エラーをまとめて保存したい場合は、次のように書きます。

コマンド > out.log 2>&1

このようにリダイレクトを使いこなせば、スクリプトの実行結果を確実に記録し、デバッグや監査に役立てることができます。

パイプ(|)でコマンドを連携させる方法

リダイレクトが出力をファイルに向ける操作だったのに対し、パイプは「あるコマンドの標準出力を、別のコマンドの標準入力に直接接続する」ための機能です。|(パイプ)記号でコマンド同士をつなげば、中間ファイルを作成せずに、複数のコマンドを連携させた一連の処理を構築できます。

【基本構文】

コマンドA | コマンドB | コマンドC

上記の例は、コマンドAの出力がコマンドBの入力となり、コマンドBの出力がコマンドCの入力となることを意味します。

【実行例:アクセスログから特定IPのアクセス数を数える】

# access.logから"192.168.1.10"を含む行を抽出し (grep)
# その結果をuniqで集計する
grep -F "192.168.1.10" access.log | wc -l

この例では、grep が大量のログから必要な行を絞り込み、その結果を uniq が受け取って集計しています。パイプを使いこなせれば、シンプルかつ効率的に記述できます。

ヒアドキュメントで複数行を扱う

スクリプトの中で、複数行にわたるまとまった文章や設定ファイルの内容を、コマンドの標準入力として渡したい場合があります。このような場合に便利なのが、<<EOF という構文、通称「ヒアドキュメント」です。

<< に続けて任意の識別子(EOFやENDがよく使われる)を記述し、次にその識別子だけが書かれた行が現れるまでの全ての行を、コマンドの標準入力として扱います。

【基本構文】

コマンド << 識別子
  ここに複数行のテキストを
  自由に記述できます。
  変数を展開することも可能です: $HOME
識別子

【用途例:メッセージの表示】

# catコマンドに複数行のテキストを入力し、画面に表示する
cat << EOF
----------------------------------
ようこそ!このスクリプトは〇〇を実行します。
実行日時: $(date)
----------------------------------
EOF

このように、ヒアドキュメントはスクリプトの可読性を損なわずに、複数行のデータをスマートに扱うための強力な手段です。

tee/xargs/read などの活用シーン

標準入出力の操作をさらに便利にする、いくつかの補助的なコマンドのうち、tee、xargs、readの3つは、実用的なスクリプトで頻繁に登場するコマンドです。

tee

tee は標準入力から受け取ったデータを、標準出力とファイルの両方に同時に出力します。コマンドの実行結果を画面で確認しつつ、ログファイルにも保存したい場合に便利です。

【使用例】

# some_commandの結果を表示しつつファイルに保存
some_command | tee command.log

xargs

xargs は標準入力から受け取ったテキストを、後続のコマンドの引数として渡します。rmcp のように引数でファイルを受け取るコマンドと相性が良いです。

【使用例】

# findで見つけた古いログファイルを削除
find /var/log -name "*.log.old" | xargs rm

read

read は標準入力から1行を読み込み、変数に格納します。ユーザーの入力を受け付ける対話的なスクリプトでよく使われます。

【使用例】

echo "お名前を入力してください:"
read -r USER_NAME
echo "こんにちは、${USER_NAME}さん!"

ヒアドキュメントを使えば、スクリプトの中で複数行のデータをすっきりと扱えます。また、teexargsread のような補助コマンドを組み合わせることで、スクリプトの柔軟性や効率性が格段に高まります。

業務に活かせるシェルスクリプトの実践例

この章では、日常運用で頻出するバックアップ、ログ監視、ファイル処理の三つのシナリオを取り上げ、すぐに導入できる完成版スクリプトを紹介します。

どの例も Bash を前提とし、エラー時の早期終了や空入力時の安全動作、並行実行の抑止など、実務でつまずきやすいポイントに配慮しています。パスやメールアドレスなどは環境に合わせて置き換えてお使いください。

cronと組み合わせたバックアップスクリプト

システムのデータを守る上で、定期的なバックアップは不可欠です。cronという定時実行の仕組みとシェルスクリプトを組み合わせれば、バックアップ処理を完全に自動化し、人的ミスや実行忘れを防止できます。

次の例は、対象ディレクトリを tar で固めて日付付きファイル名で保存し、世代管理で一定日数を越えたバックアップを削除します。並行実行を防ぐためのロック、権限の初期化、圧縮コマンドの切り替えにも対応しています。

【backup.sh】

#!/usr/bin/env bash
# backup.sh
set -euo pipefail
IFS=$'\n\t'

# 設定
SOURCE_DIR="/var/www/html"
BACKUP_DIR="/var/backups"
RETENTION_DAYS=7
COMPRESSOR="gzip"          # pigz があれば "pigz" でも可
DATE_FMT="%Y%m%d"

umask 027
mkdir -p "${BACKUP_DIR}"

today="$(date +"${DATE_FMT}")"
archive_basename="webapp_backup_${today}.tar"
archive_path="${BACKUP_DIR}/${archive_basename}"
final_path="${archive_path}.gz"
lockfile="${BACKUP_DIR}/.backup.lock"

# 競合防止
exec 9>"${lockfile}"
flock -n 9 || { echo "すでにバックアップが実行中のため終了します。"; exit 0; }

# 圧縮コマンドの解決
compress_cmd=("${COMPRESSOR}" -c)
if ! command -v "${COMPRESSOR}" >/dev/null 2>&1; then
  compress_cmd=(gzip -c)
fi

# 退避と圧縮
tar -C / -cf "${archive_path}" "${SOURCE_DIR#/}"
"${compress_cmd[@]}" "${archive_path}" > "${final_path}"
rm -f "${archive_path}"

# 古い世代の削除
find "${BACKUP_DIR}" -type f -name "webapp_backup_*.tar.gz" -mtime +"${RETENTION_DAYS}" -print -delete

echo "バックアップ完了: ${final_path}"

実行権限を付与したうえで、cron に登録します。PATH を明示し、ログを残すようにすると運用が安定します。

chmod +x /path/to/backup.sh
crontab -e
PATH=/usr/local/bin:/usr/bin:/bin
0 3 * * * /path/to/backup.sh >> /var/log/backup.log 2>&1

処理時間が長い場合は差分同期の rsync を採用する方法も有効です。高圧縮を狙うなら xz、速度重視なら pigz の利用を検討してください。

ログ監視と異常通知の自動化スクリプト

ログの全量検索を毎回行うとコストが高く、見落としの原因にもなります。前回のチェック位置を保持して差分のみを監視すると効率的です。

次の例は、ERROR や FATAL などのキーワードを検知したときにメールで通知します。ログローテーションで行数が減った場合は自動的に全件再スキャンへ切り替えます。通知コマンドがない環境では内容を標準出力に出します。

#!/usr/bin/env bash
# log_monitor.sh
set -euo pipefail
IFS=$'\n\t'
LC_ALL=C

# 設定
LOG_FILE="/var/log/app/error.log"
KEYWORDS='ERROR|FATAL|CRITICAL'
MAIL_TO="admin@example.com"
STATE_FILE="/var/tmp/log_monitor_state"
SUBJECT="Log Alert"

# 前回の行数
last_lines=0
if [[ -f "${STATE_FILE}" ]]; then
  last_lines="$(cat "${STATE_FILE}" 2>/dev/null || echo 0)"
fi

# 現在の行数
current_lines="$(wc -l < "${LOG_FILE}" || echo 0)"

# ログローテーション対策
new_lines=$(( current_lines - last_lines ))
start_line=1
if (( new_lines > 0 )); then
  start_line=$(( last_lines + 1 ))
else
  new_lines="${current_lines}"
fi

# 差分から検知
message="$(tail -n "${new_lines}" "${LOG_FILE}" | grep -E "${KEYWORDS}" || true)"

if [[ -n "${message}" ]]; then
  if command -v mail >/dev/null 2>&1; then
    printf '%s\n\n%s\n' "件名: [警告] ログに異常が検知されました" "${message}" \
      | mail -s "${SUBJECT}" "${MAIL_TO}"
  elif command -v sendmail >/dev/null 2>&1; then
    {
      echo "Subject: ${SUBJECT}"
      echo "To: ${MAIL_TO}"
      echo
      printf '%s\n' "${message}"
    } | sendmail -t
  else
    echo "[WARN] 通知コマンドが見つかりません。以下が検知内容です:"
    echo "${message}"
  fi
fi

# 状態更新
echo "${current_lines}" > "${STATE_FILE}"

以下のように 10 分おきに実行すれば、リアルタイムに近い間隔で検知できます。

PATH=/usr/local/bin:/usr/bin:/bin
*/10 * * * * /path/to/log_monitor.sh >> /var/log/log_monitor.log 2>&1

CSV の条件抽出と列選択

定例の集計やレポーティングでは、CSV から条件に合う行だけを抜き出し、必要な列のみを別ファイルに保存する作業が頻出します。

次の例は、三列目が Tokyo かつ四列目がしきい値を超える行から、日付と売上の二列だけを抽出します。ヘッダーは維持し、安全のために一時ファイルを経由して書き換えます。

#!/usr/bin/env bash
# process_csv.sh
set -euo pipefail
IFS=$'\n\t'

# 設定
INPUT_CSV="sales_data.csv"
OUTPUT_CSV="processed_data.csv"
CITY="Tokyo"
THRESHOLD=10000

if [[ ! -f "${INPUT_CSV}" ]]; then
  echo "エラー: 入力ファイルが見つかりません: ${INPUT_CSV}"
  exit 1
fi

umask 027
tmp_out="$(mktemp)"
trap 'rm -f "${tmp_out}"' EXIT

# ヘッダーをコピー
head -n 1 "${INPUT_CSV}" > "${tmp_out}"

# 2行目以降を抽出して 1列目と4列目を書き出し
# 注: カンマを含む厳密なCSVは csvkit の csvcut/csvgrep 等の利用をご検討ください。
awk -F',' -v city="${CITY}" -v th="${THRESHOLD}" '
  NR==1 { next }
  $3 == city && $4+0 > th { print $1 "," $4 }
' "${INPUT_CSV}" >> "${tmp_out}"

mv "${tmp_out}" "${OUTPUT_CSV}"
echo "処理が完了しました。結果: ${OUTPUT_CSV}"
# データにカンマ・改行が含まれる場合は csvcut/csvgrep 等の利用を検討してください。

必要に応じて、後段で sort と uniq を組み合わせて重複除去や並び替えを行うと、下流工程への引き渡しがスムーズになります。入力に欠損が混じる場合は awk の条件に NF>0 を加えると安定します。

導入時のポイント

運用サーバーでは PATH が最小限に設定されている場合があり、cron からの実行でコマンドが見つからないことがあります。crontab には PATH を明示し、標準出力・標準エラーをログファイルにリダイレクトするとトラブルシューティングが容易になります。

ファイル権限は umask で初期化し、バックアップ先や状態ファイルの格納先には十分な空き容量と適切なアクセス権を確保してください。通知先メールは実験的に自身宛に送信し、メールサーバーの到達性を確認してから本番運用に切り替えると安心です。

シェルスクリプトのエラー対策と安全な書き方

シェルスクリプトは手軽に書ける反面、予期せぬエラーによって意図しない動作を引き起こす危険性もはらんでいます。ここでは、安全性が格段に向上するオプションや、異常終了時に特定の処理を呼び出す方法、エラーハンドリングの基本となる終了ステータスの活用方法について解説します。

set -e / -u / -x によるデバッグと安全強化

スクリプトを安全に実行するには、setコマンドのオプションが効果的です。とくにset -e、set -u、set -xの3つは、多くの潜在的なバグを防ぎ、デバッグを容易にしてくれます。

set -eは、スクリプト内で実行されたコマンドがエラー(終了ステータスが0以外)になった場合、スクリプトを即座に終了させるオプションです。この設定をしないままスクリプトを実行すると、あるコマンドが失敗しても後続のコマンドが実行されてしまい、予期せぬ結果を招きかねません。

また、set -uを指定しておけば、未定義の変数を参照しようとした際に、エラーとしてスクリプトを終了させられます。変数名のタイポ(タイプミス)などが原因で、意図せず空の変数が使われてしまうミスを防ぎます。

set -xは、実行されるコマンドとその引数を、実行前に標準エラー出力に表示する設定です。スクリプトが今どのコマンドを実行しているのか、変数がどのように展開されているのかをリアルタイムで追跡できます。

trapで異常終了時の後処理を行う方法

スクリプトがエラーで中断した場合や、ユーザーによってCtrl+Cで強制終了させられた場合に、作成途中の一時ファイルを削除したり、ロックファイルを解除したりといった「後始末処理」が必要になる場合があります。

trapは、特定のシグナル(割り込み信号)を受け取った際に、あらかじめ定義しておいたコマンドや関数を実行するコマンドです。trapを活用すれば、クリーンアップ処理を確実に実行できます。

【基本構文】

trap '実行したいコマンド' シグナル名

よく使われるシグナル名には以下のようなものがあります。

  • EXIT:スクリプトが終了する時(正常・異常問わず)
  • ERR:コマンドがエラーで終了した時(set -e と似ていますが、trapは後処理を実行可能)
  • INT:Ctrl+Cで割り込まれた時
  • TERM:killコマンドで終了させられた時

【実行例:一時ファイルを必ず削除する】

#!/bin/bash
set -e

# 一時ファイルのパスを定義
TMP_FILE="/tmp/myscript.$$" # $$ はプロセスID

# クリーンアップ処理を関数として定義
cleanup() {
  echo "後処理を実行します..."
  rm -f "${TMP_FILE}"
  echo "一時ファイルを削除しました。"
}

# スクリプト終了時(EXIT)に cleanup 関数を呼び出すように設定
trap cleanup EXIT

このスクリプトを実行すると、終了する際にtrapで設定したcleanup関数が呼び出され、一時ファイルは確実に削除されます。

終了ステータス($?)の活用ポイント

終了ステータス($?)は、実行が完了した際に0から255までの数字が自動的に格納される場所です。この変数の値を確認すれば、直前のコマンドが成功したか失敗したかを判定できます。

【基本的な使い方】

# コマンドを実行
grep "some_pattern" /path/to/file

# 直後のコマンドの終了ステータスを確認
if [[ $? -ne 0 ]]; then
  echo "grepコマンドが失敗しました(パターンが見つからなかったか、ファイルが存在しません)。"
  # ここにリカバリー処理などを記述
fi

このように$?をチェックすれば、コマンドの実行結果に応じた、よりきめ細やかな制御フローが構築可能です。

安全な書き方(クォート・[[ ]]・${})の基本ルール

シェルスクリプトにおける予期せぬエラーの多くは、文字列の扱いや変数の展開方法に起因します。安全にシェルスクリプトを書くためには、以下のルールを意識すると良いでしょう。

変数は必ずダブルクォートで囲む

rm “$FILENAME” のように記述すれば、ファイル名にスペースや特殊文字が含まれていても、一つの文字列として安全に扱えます。

# 悪い例: FILENAME="My Document.txt" の場合、2つの引数と解釈される
rm $FILENAME

# 良い例: 一つの引数として正しく解釈される
rm "$FILENAME"

条件分岐には [[ ]] を使う

[ ] (testコマンド) よりも [[ ]] を使いましょう。[[ ]] は、変数が空の場合のエラーを防いだり、&& や || を直感的に使えたりと、多くの面で安全かつ高機能です。

# 悪い例: $VAR が空の場合にエラーになる可能性がある
if [ $VAR == "hoge" ]; then
  echo "一致しました"
fi

# 良い例: $VAR が空でも安全に比較できる
if [[ "$VAR" == "hoge" ]]; then
  echo "一致しました"
fi

変数の展開は${}で囲む

echo “$VAR” と echo “${VAR}” は多くの場合同じです。ただし、echo “${VAR}s” のように、変数の直後に文字列を続けたい場合に {} が必須となります。これを習慣にしておくと、意図しない変数名($VARs)として解釈されるミスを防げます。

SUFFIX="_backup"

# 悪い例: $FILENAME_backup という変数を参照しようとしてしまう
mv "$FILENAME" "$FILENAME$SUFFIX"

# 良い例: 正しく $FILENAME の後に "_backup" が連結される
mv "$FILENAME" "${FILENAME}${SUFFIX}"

上記のルールを参考に、安全な記述を常に心がければ、トラブルの少ない安定したスクリプト運用につながるでしょう。

フリーランスエンジニアの案件探しはエンジニアファクトリー

シェルスクリプトのスキルは、社内SEとして間違いなく活かせる力です。特にサーバーやネットワークの運用、ログの収集や定期処理の自動化など、日常業務の効率化に直結します。

社内システムを安定的に運用しつつ、業務改善を進めたい企業にとって、シェルスクリプトを使いこなせるエンジニアは頼りになる存在です。また、トラブル発生時にも迅速に対応できるため、現場からの信頼にもつながります。社内SE転職ナビでは、こうしたスキルを求める求人を10,000件以上保有しています。豊富な選択肢から、ご経験や希望に合う職場をご紹介できますので、シェルスクリプトを活かして社内SEとして活躍したい方は、ぜひお気軽にご相談ください。

まとめ

本記事では、Linux環境での業務自動化・効率化に役立つシェルスクリプトについて、基礎から応用までを解説しました。シェルとシェルスクリプトの役割やファイルの作成と実行といった基本的な知識から、cron連携によるバックアップ自動化やログ監視などの実務例まで網羅しています。

さらに、エラー対策や安全なスクリプト作成のためのルールにも触れているので、Linux環境で業務効率化や自動化を図りたいエンジニアの方は、ぜひ参考にしてください。

新着の案件一覧