第3部 管理と自動化
第8章 シェルとテキスト処理・シェルスクリプト入門
コマンドを1つずつ打つ段階から、「一連の作業をまとめて自動で走らせる」段階へ。ログを一瞬で集計し、面倒な手順をスクリプトにまとめる——Linuxが自動化に強いと言われる理由を、この最終章で体験します。
🎯 この章で学ぶこと
- シェル(bash)がコマンドを解釈する仕組み
- テキスト処理の三銃士
grep/sed/awk - 変数・環境変数・
$PATH・.bashrc - パイプの合わせ技、コマンド置換
$(...)、エイリアス - 初めてのシェルスクリプトと、
if/for/whileの制御構文 - 実用スクリプトの組み立て方と、自動化の注意点
8.1 シェルとは何か
これまで当たり前のようにコマンドを打ってきましたが、その入力を受け取り、意味を解釈し、実行し、結果を返してくれていた「窓口」があります。それがシェル(shell)です(コンピュータ大全コース第4章の復習と発展)。あなたとLinuxカーネルのあいだをつなぐOSの「殻(shell)」なので、この名がついています。
シェルにはいくつか種類がありますが、多くのLinuxで標準になっているのが bash(バッシュ)です。自分が今どのシェルを使っているかは $SHELL という変数で確認できます。
$ echo $SHELL
/bin/bash
シェルの重要な点は、単に1つのコマンドを実行するだけでなく、変数を覚えたり、条件で分岐したり、繰り返したりできる「小さなプログラミング言語」でもあるということです。この章の後半では、その力を使って作業を自動化します。
8.2 テキスト処理の三銃士 — grep・sed・awk
Linuxの運用は「大量のテキスト(ログ・設定・出力)から必要な情報を取り出す」作業の連続です。そこで主役になるのが grep・sed・awk の3つ。役割を一言でいうと、grepは「探す」、sedは「置き換える」、awkは「列を抜き出す」です。
grep — 探す
ファイルの中から、指定した文字を含む行だけを抜き出します。第4章でも登場しましたが、ログ調査の要です。例として、次のようなログファイルを使います。
$ cat app.log
2026-07-08 10:01 INFO start up
2026-07-08 10:02 ERROR db connection failed
2026-07-08 10:03 INFO retry
2026-07-08 10:05 ERROR db connection failed
2026-07-08 10:06 WARN slow response
$ grep ERROR app.log
2026-07-08 10:02 ERROR db connection failed
2026-07-08 10:05 ERROR db connection failed
ERROR を含む行だけが抜き出せました。行数を数える -c、大文字小文字を無視する -i、一致した行の前後も表示する -A/-B など、オプションも豊富です。
sed — 置き換える
sed は文字列の置換が代表的な使い道です。s/古い/新しい/ という書式で「置き換え(substitute)」を指定します。
$ echo "hello world" | sed 's/world/Linux/'
hello Linux
各行の world を Linux に置き換えました。1行に複数ある全部を置換したいときは末尾に g を付けて s/world/Linux/g とします。ファイル全体の文字を一括で書き換えるといった場面で活躍します。
awk — 列を抜き出す
awk は、空白などで区切られたテキストを「列」として扱い、特定の列だけ取り出すのが得意です。列は左から $1, $2, $3… と番号で指定します。先ほどのログから「時刻(2列目)とログレベル(3列目)」だけを取り出してみます。
$ awk '{print $2, $3}' app.log
10:01 INFO
10:02 ERROR
10:03 INFO
10:05 ERROR
10:06 WARN
必要な列だけがきれいに抜き出せました。ps aux や ls -l のような「列で整った出力」から、欲しい情報だけを取り出すのに絶大な威力を発揮します。この3つは、次のパイプと組み合わせることで真価を発揮します。
8.3 変数と環境変数
シェルでは値に名前を付けて変数として覚えさせられます。代入は 名前=値(イコールの前後に空白を入れないのがルール)、参照は名前の前に $ を付けます。
$ NAME=yuuki
$ echo $NAME
yuuki
$ echo "Hello, $NAME"
Hello, yuuki
この NAME は、今のシェルの中だけで有効な変数です。ここから起動する別のプログラムには引き継がれません。子プロセスにも引き継がせたいときは export を付けて環境変数にします。
$ export EDITOR=vim
$ echo $EDITOR
vim
環境変数は「システム全体で共有する設定値」として使われます。その代表格が $PATH です。
$PATH — コマンドの探し場所
ls と打つだけで、なぜLinuxは ls という実体(/usr/bin/ls)を見つけられるのでしょうか。その答えが $PATH です。これはコマンドを探しに行くディレクトリの一覧で、: 区切りで並んでいます。
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
コマンドを打つと、シェルはこの一覧を左から順に探し、最初に見つかった実行ファイルを動かします。「自分で作ったスクリプトをどこからでもコマンドとして呼びたい」ときは、置き場所を $PATH に追加します。
設定を永続化する .bashrc
変数や設定は、ターミナルを閉じると消えてしまいます。毎回自動で読み込ませたい設定は、ホームディレクトリの設定ファイルに書いておきます。
| ファイル | 読まれるタイミング | 主な用途 |
|---|---|---|
~/.bashrc | 対話的なシェルを開くたび | エイリアス、プロンプト、普段使いの設定 |
~/.bash_profile | ログイン時(1回) | 環境変数、ログイン時だけの設定 |
たとえば ~/.bashrc に export PATH=$HOME/bin:$PATH と書いておけば、次回以降、自分の ~/bin に置いたスクリプトをコマンド名だけで呼べるようになります。編集後にすぐ反映したいときは source ~/.bashrc を実行します。
8.4 便利な機能 — パイプの合わせ技・コマンド置換・エイリアス
パイプの合わせ技
第4章で学んだパイプ |(前のコマンドの出力を、次のコマンドの入力に渡す)は、いくつもつなげることで真価を発揮します。「ログの中で、どのエラーが何回出ているかを多い順に集計する」という実務そのものの処理を、1行で書いてみましょう。
$ cat app.log | grep ERROR | sort | uniq -c | sort -rn
2 2026-07-08 10:02 ERROR db connection failed
2 2026-07-08 10:05 ERROR db connection failed
処理を左から順に追うと、この一行の意味が見えてきます。
cat app.log… ログを出力するgrep ERROR… ERRORの行だけ抜き出すsort… 並べ替える(次のuniqは「隣り合う同じ行」しか数えないため、先に並べておく)uniq -c… 重複行をまとめ、各行の出現回数を先頭に付けるsort -rn… 数値(n)として逆順(r)に並べる → 多い順になる
小さな道具を | でつないで大きな仕事をする——これが第1章で触れたUNIXの設計思想「単機能の道具を組み合わせる」の実践です。
コマンド置換 $(...)
$(コマンド) と書くと、その中のコマンドの実行結果を、その場に埋め込むことができます。これをコマンド置換と呼びます。日付を使ったファイル名を作るのが定番の使い方です。
$ date +%Y%m%d
20260708
$ echo "backup-$(date +%Y%m%d).tar.gz"
backup-20260708.tar.gz
$(date +%Y%m%d) の部分が、実行結果 20260708 に置き換わっているのが分かります。「実行するたびに今日の日付が入ったファイル名」を自動生成できるので、バックアップのスクリプトなどで重宝します。
エイリアス
長いコマンドや、よく使うオプション付きのコマンドに、短い別名を付けられます。それが alias です。
$ alias ll='ls -alF'
$ ll
total 20
drwxr-xr-x 3 yuuki yuuki 4096 Jul 8 11:00 ./
...
これで ll と打つだけで ls -alF が実行されます。よく使うエイリアスは ~/.bashrc に書いておけば、毎回使えるようになります。
8.5 初めてのシェルスクリプト
ここまでのコマンドをファイルにまとめて、一気に実行できるようにしたものがシェルスクリプトです。まずは定番の「Hello」から。エディタで hello.sh という名前のファイルを作り、次の内容を書きます。
hello.sh#!/bin/bash echo "こんにちは、$USER さん" echo "今日は $(date +%Y-%m-%d) です"
1行目の #!/bin/bash はシバン(shebang)と呼ばれ、「このファイルは bash で実行してください」という指定です。スクリプトの先頭に必ず書きます。2行目以降は、これまで打ってきたコマンドをそのまま並べただけです。
作っただけでは、まだ実行できません。第5章で学んだとおり、実行権(x)を付ける必要があります。
$ chmod +x hello.sh
$ ./hello.sh
こんにちは、yuuki さん
今日は 2026-07-08 です
実行するときの ./ は「今いるディレクトリの」という意味です(このディレクトリは通常 $PATH に入っていないため、場所を明示しています)。これで、複数のコマンドをまとめた自分だけの道具ができました。
# から行末まではコメントとして無視されます(シバンの #! だけは特別な意味を持ちます)。「なぜこの処理をするのか」を書き残しておくと、後で読み返したときや、他の人が読むときに助かります。自動化スクリプトは長く使われるほど、書いた本人ですら忘れます。コメントは未来の自分への手紙です。
8.6 制御構文 — if・for・while
スクリプトが「ただコマンドを並べるだけ」から一歩進むのが、条件分岐と繰り返しです。
if — 条件で分岐する
条件を判定して処理を分けます。bashでは、条件を [ ... ] で囲んで書きます。角カッコの内側は前後に空白が必要な点に注意してください。
check.sh#!/bin/bash if [ -f /etc/passwd ]; then echo "ファイルは存在します" else echo "ファイルがありません" fi
[ -f /etc/passwd ] は「このファイルが存在すれば真」というテストです。このように、bashでは比較や存在確認に専用の記号を使います。数値の比較には次の演算子を使います(記号ではなく英字の略なのが特徴です)。
| 演算子 | 意味 | 由来 |
|---|---|---|
-eq | 等しい | equal |
-ne | 等しくない | not equal |
-gt | より大きい | greater than |
-lt | より小さい | less than |
-ge / -le | 以上 / 以下 | greater/less or equal |
-f ファイル | ファイルが存在する | file |
-d ディレクトリ | ディレクトリが存在する | directory |
age.sh#!/bin/bash COUNT=12 if [ $COUNT -gt 10 ]; then echo "10より多い(現在 $COUNT 件)" fi
for — 繰り返す
リストの要素を1つずつ取り出して、同じ処理を繰り返します。
loop.sh#!/bin/bash for name in apple banana cherry; do echo "果物: $name" done
$ ./loop.sh
果物: apple
果物: banana
果物: cherry
ファイルの一覧に対して繰り返すこともでき、「フォルダ内の全ファイルに同じ処理をする」といった自動化の中心になります。while は「条件が真である間ずっと繰り返す」構文で、回数が決まっていない繰り返しに使います。
同じ「もし〜なら」でも、Pythonでは if count > 10:、PowerShellでは if ($count -gt 10) のように書きます(入門コース〈Python〉第3章、コンピュータ大全コース第6章)。bashが -gt という英字の演算子を使い、条件を [ ] で囲むのは独特です。言語ごとに「書き方の作法」は違いますが、「条件で分岐する」「繰り返す」という考え方そのものは共通です。1つ身につければ、他の言語の制御構文も理解しやすくなります。
8.7 実用スクリプト例
学んだ要素を組み合わせて、実務でありそうなスクリプトを1つ通しで作ってみましょう。「指定したディレクトリを、日付入りの名前で圧縮してバックアップする」スクリプトです。
backup.sh#!/bin/bash # 使い方: ./backup.sh バックアップ対象ディレクトリ # バックアップ元(1つ目の引数)と保存先を決める SRC_DIR="$1" DEST_DIR="/home/yuuki/backups" DATE=$(date +%Y%m%d) ARCHIVE="$DEST_DIR/backup-$DATE.tar.gz" # 引数が指定されているか確認する if [ -z "$SRC_DIR" ]; then echo "エラー: バックアップ対象を指定してください" echo "使い方: ./backup.sh 対象ディレクトリ" exit 1 fi # 対象が本当に存在するディレクトリか確認する if [ ! -d "$SRC_DIR" ]; then echo "エラー: $SRC_DIR はディレクトリではありません" exit 1 fi # 保存先がなければ作る mkdir -p "$DEST_DIR" # 圧縮してバックアップ tar czf "$ARCHIVE" "$SRC_DIR" echo "完了: $ARCHIVE を作成しました"
$ ./backup.sh /home/yuuki/documents
完了: /home/yuuki/backups/backup-20260708.tar.gz を作成しました
このスクリプトには、これまでの学びが詰まっています。$1 でスクリプトへの引数を受け取り、$(date ...) のコマンド置換で日付入りの名前を作り、if で「引数が空でないか(-z)」「ディレクトリが存在するか(-d)」を確認してから処理に進んでいます。いきなり本処理を始めるのではなく、条件を確認してから動く——これが安全なスクリプトの基本の形です。
「古いファイルを消す」といった削除を含む処理は、取り返しがつきません。特に変数を使った rm -rf "$DIR"/* のような処理は、もし $DIR が空だったりタイプミスがあったりすると、意図しない場所を丸ごと消しかねません。削除スクリプトを作るときは、まず rm の代わりに echo rm ... と書いて「何が消される予定か」を表示させて確認し、正しいと確信してから本物の rm に変えるのが鉄則です。上の例のように、処理の前に条件チェックを入れる習慣も、事故を防ぎます。
シェルスクリプトは強力な自動化の道具ですが、強力さは危うさと表裏一体です。人間なら「なんか変だぞ」と気づいて手を止めるところを、スクリプトは何も考えずに最後までやり切ってしまいます。だからこそ、いくつかの原則を守る必要があります。
- 入力を検証する — 上の
backup.shのように、引数やファイルの存在を確認してから処理に進みます。外部からの入力(引数・ファイル名・ユーザー入力)をそのまま信用して危険な操作に渡すと、意図しない動作や攻撃の糸口になります。この「入力を疑う」考え方は、入門コース〈Python〉第10章のセキュアコーディングとまったく同じ発想です。 - まずテスト環境で、小さく試す — いきなり本番サーバーで走らせず、影響のない場所・少数のファイルで動作を確かめます。削除や上書きを含む処理は特に慎重に。
- cronと組み合わせるときは二重に慎重に — 第7章の
cronにスクリプトを登録すれば、無人で定期実行できます。便利な一方、間違ったスクリプトを仕込めば、そのミスも毎晩自動で繰り返されます。「手で何度も問題なく動くこと」を確認してから、自動運用に乗せるのが順序です。
自動化の理想は「人間が寝ている間に、正しい作業を確実にこなしてくれる」ことです。そこに至るには、「検証する → テストする → 本番に乗せる」という手順を省かないこと。急いで本番に投入したくなる気持ちをぐっとこらえるのが、結局いちばんの近道です。
まとめ
- シェル(標準はbash)はコマンドを解釈する窓口であり、変数・分岐・繰り返しを持つ小さな言語でもある
- テキスト処理の三銃士
grep(探す)・sed(置き換える)・awk(列を抜く)はログ処理の定番 - 変数は
NAME=値と$NAME、子プロセスに渡すならexport。$PATHはコマンドの探し場所、設定は.bashrcに永続化 - パイプの連結、コマンド置換
$(...)、エイリアスで作業を効率化できる - スクリプトは先頭に
#!/bin/bash、chmod +xで実行権を付け./script.shで実行 if [ ... ]、for、whileで分岐・繰り返し。比較は-eq-gt、テストは-f-d- 自動化は「入力を検証し、テストしてから本番へ」。破壊的操作は必ず確認してから
練習問題
Webサーバーのアクセスログ access.log から、「どのIPアドレス(各行の1列目)からのアクセスが多いか」を、多い順に集計したいです。awk・sort・uniq をパイプでつないだ1行のコマンドを書いてください。
解答を見る
次のように書けます。awk '{print $1}' access.log | sort | uniq -c | sort -rn
まず awk '{print $1}' で各行の1列目(IPアドレス)だけを取り出し、sort で並べ替え(uniq は隣り合う同じ行しか数えないため必須)、uniq -c で重複をまとめて回数を付け、最後に sort -rn で回数の多い順に並べます。頻出IPの特定は、アクセス集中や不審なアクセスの調査の基本です。
次のシェルスクリプトを実行すると、どう表示されますか。また、シバン(1行目)と -gt は、それぞれ何のためのものか説明してください。
#!/bin/bash
FILES=15
if [ $FILES -gt 10 ]; then
echo "ファイルが多いです: $FILES 件"
else
echo "ファイルは少なめです: $FILES 件"
fi
解答を見る
FILES は 15 で、[ $FILES -gt 10 ] は「15 は 10 より大きいか?」を判定します。真なので then 側が実行され、「ファイルが多いです: 15 件」と表示されます。
・シバン #!/bin/bash は、このスクリプトを bash で実行するよう指定する行です。
・-gt は「greater than(より大きい)」の意味で、bashにおける数値比較の演算子です(Pythonの > にあたります)。
「今日の日付を名前に含んだログファイル(例: log-20260708.txt)を作りたい」とき、コマンド置換 $(...) と date を使って、そのファイル名を echo で表示するコマンドを書いてください。日付部分は date +%Y%m%d で 20260708 の形式になります。
解答を見る
次のように書けます。echo "log-$(date +%Y%m%d).txt"$(date +%Y%m%d) の部分が、実行時にコマンドの結果(例: 20260708)へ置き換わるため、log-20260708.txt と表示されます。実際にファイルとして作りたい場合は、リダイレクトを使って touch "log-$(date +%Y%m%d).txt" のようにします。コマンド置換は、実行のたびに変わる値(日付・時刻・ホスト名など)をファイル名やメッセージに埋め込むときの定番テクニックです。