本記事には広告(アフィリエイトリンク)が含まれます。

Bashスクリプト入門 LinuC102 第2回

広告

LinuC レベル1 102試験対策シリーズの第2回です。今回は シェルスクリプト の基本を、AlmaLinux 9.6 の実機で確かめながら学びます。前回(第1回)で覚えた環境変数・関数・~/.bashrc の知識を土台に、ファイル化して再利用できる「現場のスクリプト」が書けるところまで進みます。

環境前提

  • ディストロ:AlmaLinux 9.6(bash 5.1.8)
  • ユーザー:developer(sudo NOPASSWD)
  • 検証環境:Hyper-V on Windows 11
  • 関連VM:linuc-alma(External 192.168.1.132)
  • 本回は linuc-alma 単独で完結します
広告

今ここマップ

LinuC 102 試験対策シリーズ(全12回)

  第1回 シェル環境のカスタマイズ
▶ 第2回 Bashスクリプト入門    ← いまここ
  第3回 ネットワーク基礎
  第4回 ネットワークトラブルシューティングとDNS
  第5回 ユーザ・グループ管理と sudo 設定
  第6回 ジョブスケジューリングと時刻管理
  第7回 ログ管理実践
  第8回 メール配送エージェント(MTA)の基本
  第9回 ファイアウォールと SELinux 入門
  第10回 暗号化によるデータ保護
  第11回 クラウドセキュリティの基礎
  第12回 オープンソースの文化

この記事で身につくこと

  1. shebang(#!/bin/bash)と実行権限(chmod +x)でスクリプトを動かせる
  2. 変数のクォーティング("$VAR" / $VAR / '$VAR')の挙動差を説明できる
  3. if / case による条件分岐と、test / [ ] / [[ ]] を使い分けられる
  4. for / while ループと read による行処理が書ける
  5. 関数定義と return / exit / $? の終了ステータスを使い分けられる

第1章:「使い捨てワンライナー」と「再利用スクリプト」

第1回ではターミナルで直接コマンドを打って実行していました。これは ワンライナー と呼ばれ、その場限りの作業に向きます。一方、同じ作業を毎日・毎週繰り返すなら、ファイルに書き出して名前を付けて保存しておく方が安全で速くなります。これが シェルスクリプト です。

1.1 なぜスクリプトを書くのか(誰のために)

スクリプト化のメリットを「誰のため」で整理します。

  • 自分のため:同じ手順を3回やる前に、ファイルに保存して再利用する。覚えなくてよい。
  • チームのため:「README に手順 A→B→C を書く」より「deploy.sh を作って共有する」方が再現性が高い。
  • 運用のため:cron や systemd timer で自動実行できる。深夜のバックアップ、5分ごとのヘルスチェック、月初のレポート集計。
  • 後の自分のため:3か月後に「あの作業どうやったっけ?」と検索する代わりに、過去のスクリプトを開けば手順が再現できる。

シェルスクリプトは「コマンドを並べただけ」ではありません。条件分岐・ループ・関数を組み合わせれば、立派なプログラムになります。

第2章:最初のスクリプト ── shebang と実行権限

2.1 shebang は「このファイルを誰が解釈するか」を示す

シェルスクリプトの 1行目には次のような記号を書きます。

#!/bin/bash

先頭の #!shebang(シバン、シェバン) と呼びます。これは「このファイルは /bin/bash で実行してください」というカーネルへの指示です。

  • #!/bin/bash:bash で実行(本シリーズで使用)
  • #!/bin/sh:POSIX 互換シェルで実行(多くの環境で dash や bash の互換モード)
  • #!/usr/bin/env python3:PATH から python3 を探して実行(移植性が高い記法)

linuc-alma で実行 ──bash の場所と版を確認します。

実行コマンド:

$ bash --version | head -2
$ which bash

実行結果:

GNU bash, バージョン 5.1.8(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2020 Free Software Foundation, Inc.
/usr/bin/bash

AlmaLinux 9 では /bin/usr/bin へのシンボリックリンクなので、/bin/bash/usr/bin/bash も同一バイナリです。教材では伝統的な #!/bin/bash を採用します。

2.2 実行権限と4つの実行方法

スクリプトを ./hello.sh 形式で実行するには chmod +x で実行権限を付けます。実行方法は4つあり、挙動が違います。

方法実行されるシェル変数や関数の残り方
./hello.shshebang で指定したシェル(子プロセス)残らない
bash hello.sh明示的に bash の子プロセス残らない
source hello.sh現在のシェル残る
. hello.sh現在のシェル(source の短縮形)残る
source と ./script の実行のされ方の違いを示した図。左は ./script.sh の場合で、スクリプトは子プロセス(新しい bash)で実行され、その中で定義した変数 INSIDE は呼び出し元のシェルに戻らず、実行後は空になる。右は source script.sh の場合で、スクリプトは現在のシェルで読み込まれ、定義した変数 INSIDE が実行後も残る。bash script.sh は子プロセス側、. script.sh は source 側と同じ挙動になる。
図 02-01. source と ./script の実行のされ方の違い

「現在のシェルで読み込む」ということは、スクリプトの中で定義した変数や関数が、終わった後も使えるということです。これが第1回で source ~/.bashrc を使った理由でもあります。

📖 試験Tipsボックス1:shebang と実行方法

主題:1.06.2(重要度:高)
出題パターン:「シェルスクリプトの先頭に書く2文字は?」「現在のシェルでスクリプトを読み込むコマンドは?」「実行権限を付けるコマンドは?」

暗記ポイント

  • 先頭2文字 #! = shebang
  • chmod +x で実行権限
  • ./script.sh または bash script.sh子プロセス(変数残らない)
  • source script.sh または . script.sh現シェル(変数・関数が残る)

第3章:変数とクォーティング

3.1 変数の定義と参照

bash の変数は次のように定義します。

NAME=value           # 定義(イコールの前後にスペース禁止)
echo $NAME           # 参照
echo ${NAME}         # 参照(中括弧で範囲を明確化)
echo "${NAME}_log"   # 連結する場合は中括弧が必要

イコールの前後にスペースを入れてはいけませんNAME = value と書くと「NAME というコマンドに =value を引数として渡す」と解釈されてエラーになります。

3.2 クォーティングは3種類

クォート変数展開コマンド置換用途
"..." ダブルありあり通常はこれ(推奨)
'...' シングルなしなしそのまま文字列として扱う
クォートなしあり(ただしワード分割と glob 展開あり(同上)事故の元、避ける

linuc-alma で実行 ──クォーティングの違いを実演します。

実行コマンド:

$ SPACED="hello world from bash"
$ echo "double quote: [$SPACED]"
$ echo 'single quote: [$SPACED]'
$ echo "command sub : [$(date +%Y)]"
$ echo "arithmetic  : [$((3 + 4 * 2))]"

実行結果:

double quote: [hello world from bash]
single quote: [$SPACED]
command sub : [2026]
arithmetic  : [11]

シングルクォートの中では $SPACED がそのまま文字列として扱われます。コマンド置換 $(...) と算術評価 $(( )) はダブルクォート内で展開されます。

3.3 クォートしないと「ワード分割」が起きる

変数の値にスペースが含まれているとき、クォートしないと 空白で区切られて複数の引数に分割 されます。

実行コマンド:

$ LIST="a b c"
$ for x in $LIST; do echo " - [$x]"; done       # クォートなし
$ for x in "$LIST"; do echo " - [$x]"; done     # クォートあり

実行結果:

- [a]
 - [b]
 - [c]
 - [a b c]

クォートなしは a b c の3要素として扱われ、ループが3回まわります。クォートありは "a b c" という1要素なので1回だけです。意図しないワード分割は事故の温床です。第9章のヒヤリハットで実例を見ます。

📖 試験Tipsボックス2:クォーティングとコマンド置換

主題:1.06.2(重要度:高)
出題パターン:「変数を展開しないクォートは?」「コマンド置換の書き方は?」「変数を安全に展開するクォートは?」

暗記ポイント

  • "..." = 変数展開あり(推奨)
  • '...' = リテラル(展開なし)
  • $(cmd) = コマンド置換(推奨)
  • ` cmd ` = バッククォート(レガシー)
  • $(( )) = 算術評価
  • クォートなしは単語分割と glob 展開で危険

第4章:条件分岐 ── if と case と test

4.1 if の基本構文

if 条件; then
    処理
elif 別の条件; then
    処理
else
    処理
fi

「条件」の正体は コマンドの終了ステータスが 0 かどうか です。0 なら真、それ以外は偽。よく使う [ 式 ] はじつは test コマンドの別名で、式が真なら 0 を返します。

4.2 [ ] と [[ ]] と (( )) の違い

記法正体機能備考
[ 式 ]test コマンドPOSIX 互換多くのシェルで動く
[[ 式 ]]bash 拡張構文正規表現 =~&& ||、ワード分割なしbash 限定
(( 式 ))bash 算術評価数値演算と比較($ 不要)if (( a > b ))

4.3 主要な test 演算子

  • ファイル-f(通常ファイル)/ -d(ディレクトリ)/ -e(存在)/ -r-w-x(権限)/ -s(サイズ>0)
  • 文字列=(等値)/ !=(不等)/ -z(空文字)/ -n(非空)
  • 数値-eq -ne -lt -le -gt -ge

linuc-alma で実行 ──ファイルテストと文字列・数値テストを試します。

実行コマンド:

$ [ -f /etc/passwd ] && echo "/etc/passwd: regular file"
$ [ -d /etc ] && echo "/etc: directory"
$ [ -e /no-such ] || echo "/no-such: not exists"
$ [ -r /etc/shadow ] || echo "/etc/shadow: not readable by $(whoami)"
$ N=10
$ [ "$N" -eq 10 ] && echo "num eq"
$ [ "$N" -lt 20 ] && echo "num lt"

実行結果:

/etc/passwd: regular file
/etc: directory
/no-such: not exists
/etc/shadow: not readable by developer
num eq
num lt

4.4 [[ ]] の bash 拡張:正規表現とパターン

実行コマンド:

$ STR="abc123"
$ [[ "$STR" =~ ^[a-z]+[0-9]+$ ]] && echo "regex match: $STR"
$ [[ "$STR" == abc* ]] && echo "glob match: $STR starts with abc"

実行結果:

regex match: abc123
glob match: abc123 starts with abc

=~ は POSIX 拡張正規表現マッチ、== は glob マッチ。これらは [ ] では使えません。

4.5 case 文(パターンマッチ)

引数の値で分岐するときは if-elif より case が読みやすくなります。サービス管理スクリプトの定型です。

実行コマンド:

$ cat svc.sh
#!/bin/bash
case "$1" in
    start)   echo "starting service" ;;
    stop)    echo "stopping service" ;;
    restart) echo "restarting service" ;;
    *)       echo "usage: $0 {start|stop|restart}" ; exit 1 ;;
esac

$ ./svc.sh start
$ ./svc.sh foo
$ echo $?

実行結果:

starting service
usage: ./svc.sh {start|stop|restart}
1

*) は「どれにも当てはまらないとき」のフォールバック。引数バリデーションに必須です。

📖 試験Tipsボックス3:test 演算子と if/case

主題:1.06.2(重要度:高)
出題パターン:「ファイルが存在するかのテスト演算子は?」「文字列が空かのテストは?」「数値の等値比較演算子は?」「[ ][[ ]] の違いは?」

暗記ポイント

  • ファイル -f -d -e -r -w -x -s
  • 文字列 = != -z -n
  • 数値 -eq -ne -lt -le -gt -ge
  • [ ] = test(POSIX)
  • [[ ]] = bash 拡張(正規表現 =~ 可)
  • case はパターンマッチ、終端は ;;、デフォルトは *)

第5章:ループ ── for と while

5.1 for の3形式

  • リスト走査for var in 値1 値2 値3; do ... done
  • C 言語風for ((i=0; i<5; i++)); do ... done
  • 引数走査(暗黙の "$@"for arg; do ... done

linuc-alma で実行

実行コマンド:

$ for i in 1 2 3 4 5; do echo "i=$i"; done
$ for ((i=0; i<3; i++)); do echo "c-style i=$i"; done
$ for f in /etc/*.conf; do echo "$f"; done | head -3

実行結果:

i=1
i=2
i=3
i=4
i=5
c-style i=0
c-style i=1
c-style i=2
/etc/chrony.conf
/etc/dracut.conf
/etc/host.conf

3番目の for f in /etc/*.conf は glob 展開で実ファイル名のリストになります。これが「ls をパースするな、glob を使え」と言われる定石です。

5.2 while と while read(行処理の定石)

テキストファイルを 1行ずつ処理する場合は while read が定番です。

実行コマンド:

$ head -3 /etc/os-release | while read line; do
>     echo "[$line]"
> done

実行結果:

[NAME="AlmaLinux"]
[VERSION="9.6 (Sage Margay)"]
[ID="almalinux"]

5.3 until / break / continue

  • until 条件:条件が 偽の間 ループ(while の逆)
  • break:ループから抜ける
  • continue:次の反復へ進む

実行コマンド:

$ for i in 1 2 3 4 5; do
>     [ "$i" -eq 3 ] && continue
>     [ "$i" -eq 5 ] && break
>     echo "i=$i"
> done

実行結果:

i=1
i=2
i=4

i=3 はスキップ(continue)、i=5 で抜ける(break)。

第6章:関数と終了ステータス

6.1 関数の定義と呼び出し

関数は名前を付けてまとめた処理のかたまりです。第1回の myls と同じです。

実行コマンド:

$ greet() {
>     local name="${1:-world}"
>     echo "Hello, $name!"
> }
$ greet
$ greet "developer"

実行結果:

Hello, world!
Hello, developer!

ポイント:

  • local で関数内ローカル変数(漏れ防止)
  • ${1:-world} は「引数1がなければ world」のデフォルト値
  • 引数:$1$9(10以降は ${10})/ $# 個数 / $@ 全引数 / $0 スクリプト名(関数内でも変わらない)

6.2 終了ステータス($?)と return / exit

  • $?:直前のコマンドの終了ステータス
  • 0:成功
  • 1〜255:失敗(1 一般エラー、2 誤用、126 実行不可、127 command not found)
  • return N:関数の終了ステータスを N にして戻る
  • exit N:スクリプト全体を N で終了

実行コマンド:

$ true ; echo $?
$ false ; echo $?
$ ls /no-such 2>/dev/null ; echo $?
$ grep -q root /etc/passwd ; echo $?

実行結果:

0
1
2
0

ls /no-such の終了ステータスは 2(一般エラー)です。「失敗 = 1」と思い込んでいると、if [ $? -eq 1 ] のような判定で取りこぼします。「成功は 0、失敗は非ゼロ」と覚えるのが正解です。

6.3 短絡評価(&& と ||)

  • cmd1 && cmd2:cmd1 が 成功 したら cmd2 を実行
  • cmd1 || cmd2:cmd1 が 失敗 したら cmd2 を実行
  • cmd1 ; cmd2:順次実行(成否問わず)

実行コマンド:

$ true  && echo "T && runs"
$ false && echo "F && skipped"
$ true  || echo "T || skipped"
$ false || echo "F || runs"

実行結果:

T && runs
F || runs

6.4 set -euo pipefail(現場の三点セット)

本番運用するスクリプトには 必ず 入れる三点セットがあります。

  • set -e:コマンドが失敗(非ゼロ終了)したらスクリプトを即終了する
  • set -u:未定義変数を参照したら即終了する(タイプミス検出)
  • set -o pipefail:パイプライン途中の失敗を取りこぼさず終了ステータスに反映

linuc-alma で実行 ──pipefail の効果を実演します。

実行コマンド:

$ false | true ; echo "without pipefail: $?"
$ ( set -o pipefail; false | true; echo "with pipefail: $?" )

実行結果:

without pipefail: 0
with pipefail: 1

パイプライン全体の終了ステータスは「最後のコマンドのもの」になります。false | true は最後の true が成功なので 0 を返してしまい、途中の失敗を見逃します。pipefail を有効にすると、途中で失敗があれば全体を失敗(1)として扱います。

本番スクリプトの定型は次の1行を冒頭に書きます。

set -euo pipefail

📖 試験Tipsボックス4:終了ステータスと set オプション

主題:1.06.2(重要度:高)
出題パターン:「直前コマンドの終了ステータスを表す変数は?」「成功時の値は?」「set -e の意味は?」「関数を抜けるのは returnexit どちら?」

暗記ポイント

  • $? = 直前の終了ステータス
  • 0 成功 / 1〜255 失敗(127 = command not found)
  • && = 直前成功時のみ実行
  • || = 失敗時のみ
  • set -e = エラー即終了
  • set -u = 未定義変数で即終了
  • set -o pipefail = パイプ途中の失敗を反映
  • return は関数を抜ける(スクリプトは続行)、exit はスクリプト全体を終了

第7章:実用パターン ── 簡易ヘルスチェックスクリプト

ここまでの要素を組み合わせると、サービスの死活を1行で確認するスクリプト が書けます。

実行コマンド:

$ cat hc.sh
#!/bin/bash
set -u
check() {
    local svc=$1
    if systemctl is-active --quiet "$svc"; then
        echo "[OK] $svc"
        return 0
    else
        echo "[NG] $svc"
        return 1
    fi
}
check sshd
check chronyd

$ chmod +x hc.sh
$ ./hc.sh

実行結果:

[OK] sshd
[OK] chronyd

このスクリプトを cron に登録して 5分ごとに走らせれば、サービス停止に気付ける簡易監視になります。本格的な監視は次回以降で扱う予定ですが、原型はこれで作れます。

現場での使いどころ

  • 定型作業の自動化:毎朝のバックアップ確認、毎週のログ集計、月次レポート生成。3回やる作業はスクリプト化する
  • ヘルスチェックスクリプトsystemctl is-activeif で判定し、cron で5分ごとに実行 → メール通知(第8回 MTA で扱う)
  • デプロイスクリプトset -euo pipefail + 関数化で、途中失敗時に「処理途中で止まる」を防ぐ。失敗時のロールバックも関数で組む
  • 引数バリデーションif [ $# -lt 1 ]; then echo "usage: $0 ホスト名"; exit 2; fi で誤実行を防ぐ
  • 確認プロンプトで誤操作防止read -p "本当に削除しますか? (y/N): " ans で破壊的操作の前に確認させる
  • cron 用ログ出力logger コマンドで journald に出すか、>> /var/log/myapp.log 2>&1 でファイルに追記する(ログ管理は第7回で扱う)
  • ShellCheck で lint:書いたスクリプトは shellcheck script.sh で構文・落とし穴チェック。VS Code 拡張もある。チームで標準化すると事故が減る

第9章:ヒヤリハット ── 空変数 + rm -rf でルート消失

ヒヤリハット:bash スクリプトで $DIR が空のまま rm -rf $DIR/*

あるエンジニアが、本番サーバーの一時ファイル削除スクリプトを書きました。

#!/bin/bash
# 一時ディレクトリの中身をクリア
DIR=$(get_temp_dir)        # ← この関数が失敗して空文字を返した
rm -rf $DIR/*

結果、rm -rf $DIR/*rm -rf /* として展開され、ルートディレクトリ直下のすべてが削除される 事態に。リストア作業に半日。

原因は3つ重なっていました。

  • クォートしていない"$DIR" ではなく $DIR だったため、空文字が「無」として消える
  • 空チェックがない[ -z "$DIR" ] で空かどうか確認していない
  • set -u がない:未定義・空変数の参照を検知できなかった

教訓:守りのスクリプトを書く

  • 変数は必ずクォート"$DIR" と書く。$DIR 単体は禁止と心得る
  • 空チェックを入れる[ -z "$DIR" ] && { echo "DIR is empty"; exit 1; }
  • set -u を冒頭に:未定義変数で即終了させる
  • ドライランecho rm -rf "$DIR/"* を先に実行して、何が削除されるか目視確認
  • 絶対パスで書く:相対パスや変数依存の cd は事故の元
  • ShellCheckshellcheck がクォート漏れを警告してくれる(SC2086 など)

やってみよう

linuc-alma で順に試してください。作業用ディレクトリを作って、最後にクリーンアップします。

演習1:最初のスクリプト

$ mkdir -p ~/linuc-bash
$ cd ~/linuc-bash
$ cat > hello.sh <<'EOF'
#!/bin/bash
echo "Hello, $(whoami) from $(hostname)"
EOF
$ chmod +x hello.sh
$ ./hello.sh
$ bash hello.sh
$ source hello.sh

3つの実行方法でいずれも同じ出力が出ることを確認します。cat > hello.sh <<'EOF' はヒアドキュメント。シングルクォートの 'EOF' は変数展開を抑制します(中の $(whoami) をそのまま書き出すため)。

演習2:source と ./ で変数の残り方を観察

$ cat > setvar.sh <<'EOF'
#!/bin/bash
INSIDE=hello
echo "inside: INSIDE=$INSIDE"
EOF
$ chmod +x setvar.sh
$ unset INSIDE
$ ./setvar.sh ; echo "after exec: INSIDE=[$INSIDE]"
$ source setvar.sh ; echo "after source: INSIDE=[$INSIDE]"

./ 実行は子プロセスなので INSIDE は親に残らず空、source は現シェルなので残ります。

演習3:if と case で引数判定スクリプト

$ cat > if-test.sh <<'EOF'
#!/bin/bash
score=$1
if [ -z "$score" ]; then
    echo "usage: $0 数値"
    exit 2
fi
if [ "$score" -ge 80 ]; then
    echo "A"
elif [ "$score" -ge 60 ]; then
    echo "B"
else
    echo "C"
fi
EOF
$ chmod +x if-test.sh
$ ./if-test.sh 95
$ ./if-test.sh 70
$ ./if-test.sh 30
$ ./if-test.sh        # 引数なしのエラー処理を確認
$ echo $?

引数バリデーション([ -z "$score" ])と exit 2 の使い方を確認します。

演習4:ヘルスチェック関数 + set -euo pipefail

$ cat > hc.sh <<'EOF'
#!/bin/bash
set -euo pipefail

check() {
    local svc=$1
    if systemctl is-active --quiet "$svc"; then
        echo "[OK] $svc"
        return 0
    else
        echo "[NG] $svc"
        return 1
    fi
}

# 引数を全部チェック(引数走査)
if [ $# -lt 1 ]; then
    echo "usage: $0 サービス名 [サービス名...]"
    exit 2
fi
for svc in "$@"; do
    check "$svc" || true   # NG でもスクリプトを止めない
done
EOF
$ chmod +x hc.sh
$ ./hc.sh sshd chronyd
$ ./hc.sh nosuchservice
$ ./hc.sh                   # 引数なしの動作も見る
$ echo $?

関数の return、引数走査の "$@"set -euo pipefail|| true で続行制御──第6章までの全要素が入った実用例です。

クリーンアップ

$ cd ~
$ rm -rf ~/linuc-bash

必ず絶対パスで書く。これも本記事のヒヤリハットの教訓です。

理解度チェック(○×形式)

  1. shebang は #!/bin/bash のように記述し、スクリプトファイルの1行目に書く必要がある(◯ / ✕)
  2. ./script.shsource script.sh はどちらも子プロセスで実行されるため、変数は残らない(◯ / ✕)
  3. [ -z "$VAR" ]$VAR空文字 のときに真を返す(◯ / ✕)
  4. case 文ではパターンの終わりを ;;、文の終端を esac と書く(◯ / ✕)
  5. set -u は未定義変数を参照したときにスクリプトを即終了させるオプションである(◯ / ✕)

解答と解説

  1. 。先頭2文字 #! のあとに解釈シェルのパスを書く。shebang がないと、呼び出し元のシェルや実行方法によって動作が変わる。
  2. ./script.sh は子プロセス(変数残らず)、source script.sh は現シェル(変数残る)。決定的な違いがある。
  3. -z = zero length。逆は -n(non-empty)。引数バリデーションで頻出。
  4. case "$1" in パターン) 処理 ;; *) フォールバック ;; esac の構造。esaccase の逆綴り。
  5. 。三点セット set -euo pipefailuset -e(エラー即終了)、set -o pipefail(パイプ失敗反映)と合わせて使うのが現場の定石。

次回予告

第3回は ネットワーク基礎。TCP/IP の階層、IP アドレス、サブネット、ポート番号といった基礎概念から、ip コマンドと NetworkManager(nmcli)による永続的な設定までを扱います。今回学んだスクリプトの知識は、ヘルスチェックや疎通確認の自動化で再登場します。

参照リソース(自分で調べる癖をつける)

  • man bash ── bash の全機能(長いが、SHELL GRAMMAR と PARAMETERS の章は要点)
  • help if / help for / help while / help case / help set ── bash 組み込みコマンドのヘルプ
  • man test ── test 演算子一覧
  • ShellCheck(https://www.shellcheck.net/)── スクリプトの構文・落とし穴チェック。SC2086(クォート漏れ)の警告は必ず見る
  • LinuC 公式範囲:https://linuc.org/linuc1/range/102.html 主題 1.06.2

LinuC 102 試験対策シリーズ 全12回

広告
Linux
スポンサーリンク