# Penguin Gym Linux > ブラウザ完結の仮想ターミナルで Linux コマンドを実践学習できる無料 Web アプリ。86 件の体系的なハンズオン課題、167 問のコマンドクイズ、LPIC-1 試験対策コンテンツ(35 件の専用記事 / 本番形式の模擬試験 60 問 / 12 件のハンズオン演習)、コマンドタイピングゲーム、学習ダッシュボード、50 種のバッジによるゲーミフィケーションを提供する。日本語・英語・ポルトガル語対応。インストール不要で、ブラウザだけで Linux 操作を学べる。 主要対象: Linux コマンド初学者 / LPIC-1 受験予定者 / Linux 操作の実務復習をしたいエンジニア / 学校・スクールの IT 教育現場。 このサイトでできること: 1) ブラウザ内の仮想ターミナルで `ls` / `cd` / `grep` / `chmod` 等の Linux コマンドを実行して練習、2) 課題を解いてバッジを獲得しながら段階的に学習、3) タイピングゲームでコマンドを打鍵し反復で定着させる、4) LPIC-1 101/102 範囲を網羅した過去問形式クイズと模擬試験で試験対策、5) 記事で概念・トラブルシューティング・実務 Tips を体系的に学ぶ。 このサイトではないもの: 本物の Linux VM や SSH 環境ではない(ファイル変更は仮想実行で永続化されない)、クラウド実行プラットフォームではない、有償アカウント登録は不要。実機環境でのみ動く操作(systemd unit ファイル編集の永続化、本物の sudo 等)は対象外。 # 絶対パス(absolute path)と相対パス(relative path)入門 - . と .. と ~ を理解する Source: https://penguin-gym-linux.com/articles/guides/absolute-relative-paths ## パスの読み方でつまずいていませんか? {#intro} 「`cd ../logs` の `..` って何?」「`~/Documents` の `~` ってどこ?」コマンドを学び始めると、こういう記号付きの「住所」に必ず出会う。これが **パス(path)** だ。 この記事では、絶対パス(absolute path)と相対パス(relative path)の違いを説明する。`.` `..` `~` という3つの記号の意味も、リナとライニー先輩の会話で整理していく。読み終わるころには、どんなパスを見ても「今どこを指しているか」が分かるようになる。 ## この記事でわかること {#toc} - 「パス」がファイルやフォルダの住所であること - 絶対パス(`/` から始まる)と相対パス(現在地から辿る)の違い - `.`(今いる場所)/ `..`(1つ上)/ `~`(ホーム)の意味 - 同じ場所を2通りの書き方で指せること - 迷子になったときに `pwd` で現在地を確認するコツ - ミニ課題で理解を確認すること ## 1. そもそもパスって何? {#what-is-path} > **結論**: パスはファイルやフォルダの「住所」。どのフォルダをたどればそこに着くかを、`/` で区切って書いたもの。 ::: dialogue @lina: ライニー先輩、コマンドの説明に出てくる `/home/lina/memo.txt` みたいなやつ、いつも何を表してるのか分からなくて… @linny: それは「パス(path)」だよ。日本語では「{経路|けいろ}」と言う。ファイルやフォルダの場所を示す住所だと思えばいい。 @lina: 住所、ですか? @linny: そう。`/home/lina/memo.txt` は「`home` フォルダの中の `lina` フォルダの中にある `memo.txt`」という意味だよ。`/`(スラッシュ)はフォルダの区切り記号だね。 @lina: あの、説明でよく見る「ディレクトリ」という言葉は、フォルダとは別物ですか? @linny: 同じものだよ。Windows や Mac で言う「フォルダ」を、Linux では「ディレクトリ」と呼ぶんだ。この記事では「フォルダ」と書く。ただし「カレントディレクトリ」のように用語として定着している言葉は、そのまま使うね。 @lina: なるほど。郵便の住所が「東京都→渋谷区→…」って絞り込んでいくのと似てますね。フォルダも1段ずつ降りていくイメージですか? @linny: その通り。ルート(`/`)から1段ずつフォルダを降りていくのがパスの基本ルールだよ。 ::: ::: tip **パス=ファイルへの道順** Linux では、すべてのファイルとフォルダが `/`(ルート)を頂点にした1本の{木構造|もくこうぞう}に並んでいる。パスは、その木のどの枝をたどれば目的のファイルに着くかを示す「道順」だ。木構造そのものは、[ディレクトリ構造の全体像](/articles/guides/linux-directory-structure) で詳しく解説している。 ::: ## 2. 絶対パスとは? {#absolute} > **結論**: 絶対パスは必ず `/`(ルート)から始まる。どこにいても同じ場所を指す住所だ。「東京都から書いた完全な住所」と同じだと考えるといい。 ::: dialogue @lina: パスにも種類があるって聞いたんですけど、本当ですか? @linny: 本当だよ。パスには大きく2種類ある。まずは「絶対パス(absolute path)」から説明するね。 @lina: 最初がスラッシュ、ですね。 @linny: そう。`/` は木のてっぺん「ルートディレクトリ」を表す。絶対パスは、そこから目的地までを{省略|しょうりゃく}せずに書くんだ。 @lina: だから、どこにいても同じ場所を指せるんですか? @linny: その通り。たとえば `/etc/hosts` は、リナがどのフォルダにいても `/etc/hosts` のまま。郵便で言えば、都道府県から書いた完全な住所と同じだね。 ::: ```bash cd /etc pwd ``` ```output /etc ``` `/etc` のように `/` で始まるパスを指定すると、現在地に関係なく必ずその場所へ移動できる。 ::: highlight **絶対パスの目印は先頭の `/`** - `/`(ルート)から書き始める - 現在地がどこでも結果は同じ - 設定ファイルやスクリプトなど「確実にこの場所」と指定したいときに向く ::: ## 3. 相対パスとは? {#relative} > **結論**: 相対パスは「今いる場所」を起点にした住所だ。専門用語では「カレントディレクトリ」(作業ディレクトリとも呼ぶ)という。`/` では始まらず、現在地が変わると指す先も変わる。 ::: dialogue @lina: もう1種類は何ていうんですか? @linny: 「相対パス(relative path)」だよ。これは `/` から始まらない。「今いる場所」を起点にして道順を書くんだ。 @lina: 今いる場所が基準…? @linny: そう。リナが `/home/lina` にいるとき、`cd Documents` と打つと `/home/lina/Documents` に移動する。「ここから見て Documents の中へ」という意味だね。 @lina: じゃあ、別の場所で同じ `cd Documents` を打ったら…? @linny: 行き先が変わるよ。`/var` にいるなら `/var/Documents` を探しにいく。自分の立ち位置によって指す先が変わるから「相対」と呼ぶんだ。 ::: ```bash pwd ``` ```output /home/lina ``` ```bash cd Documents pwd ``` ```output /home/lina/Documents ``` ::: warning **相対パスは「今どこにいるか」で結果が変わる** 同じ `cd Documents` でも、現在地(カレントディレクトリ)が違えば移動先が変わる。コマンドが思った通りに動かないときは、まず `pwd` で現在地を確認しよう。 ::: ## 4. . と .. と ~ の意味は? {#dots-tilde} > **結論**: `.` は「今いる場所」を表す記号だ。`..` は「1つ上の{階層|かいそう}」を表す。`~` は「自分のホームディレクトリ」を表す。 ::: dialogue @lina: 本題なんですけど、`.` とか `..` とか `~` って結局なんなんですか? ずっと謎で… @linny: いい質問。相対パスを書くときに使う特別な記号だよ。3つまとめて覚えよう。 @lina: お願いします! @linny: `.`(ドット1つ)は「今いる場所」を表す。`..`(ドット2つ)は「1つ上の階層」を表す。`~`(チルダ)は「自分のホームディレクトリ(作業を始めたときの初期位置、home directoryとも呼ぶ)」を表す近道だよ。 ::: | 記号 | 読み方 | 意味 | 例 | | ---- | ----------------- | ------------------------ | --------------------------------- | | `.` | ドット / カレント | 今いる場所(カレント) | `./script.sh`(ここのスクリプト) | | `..` | ドットドット | 1つ上の階層 | `cd ..`(親フォルダへ) | | `~` | チルダ | 自分のホームディレクトリ | `cd ~`(ホームへ一発移動) | ### `..` で1つ上に戻る ::: dialogue @lina: `..` がいちばん見かける気がします。 @linny: よく使うよ。`/home/lina/Documents` にいるとき `cd ..` すると、1つ上の `/home/lina` に戻る。さらに `cd ..` すれば `/home` に行けるよ。 ::: ```bash pwd ``` ```output /home/lina/Documents ``` ```bash cd .. pwd ``` ```output /home/lina ``` `..` は組み合わせも可能だ。`cd ../../etc` なら「2つ上に戻ってから `etc` に入る」という意味になる。 ### `~` でホームに一発で帰る ::: dialogue @lina: `~` はどんなときに便利なんですか? @linny: どこにいても `cd ~` だけでホームディレクトリ(`/home/lina` など)に一発で戻れる。深いフォルダで迷子になったときの「帰宅ボタン」みたいなものだね。`cd` を引数なしで打っても同じ効果だよ。 ::: ```bash cd ~ pwd ``` ```output /home/lina ``` `~/Documents` のように書けば「ホームの中の Documents」を、今いる場所に関係なく指せる。 ::: tip **`.` はいつ使うの?** `.` は普段あまり打たない。スクリプトを実行するとき、`./script.sh` という形でよく登場する。これは「今いるフォルダにある `script.sh`」という意味だ。 `.` を付けないと、シェル(打ったコマンドを受け取って実行するプログラム。ターミナルの中で動いている)は、決められたコマンド置き場だけを探す。今いるフォルダは探さない。だから見つからず失敗することがある。自作スクリプトの実行では `./` を付けるのが定番だ。 ::: ## 5. 絶対パスと相対パス、どう使い分ける? {#which-to-use} > **結論**: 確実に指定したいなら絶対パスを使う。近くの場所へすぐ移動したいなら相対パスが向く。同じ場所を両方の書き方で指せることも多い。 ::: dialogue @lina: 結局、絶対パスと相対パスはどう使い分ければいいんですか? @linny: 迷わず確実に指したいときは絶対パス。今いる場所のすぐ近くを指したいときは相対パスが楽だよ。 @lina: 両方で同じ場所を指せることもあるんですか? @linny: あるよ。リナが `/home/lina` にいるとき、`/home/lina/Documents`(絶対)も `Documents`(相対)も行き先は同じだよ。短く書けるのが相対パスの強みだね。 ::: 同じ移動を2通りで書いた例を見てみよう(現在地が `/home/lina` の場合)。 ```bash cd /home/lina/Documents ``` ```bash cd Documents ``` どちらも結果は同じ `/home/lina/Documents` への移動だ。 ::: highlight **使い分けのめやす** - **絶対パス**: 設定ファイル / スクリプト / 別の遠い場所を「確実に」指したいとき - **相対パス**: すぐ隣・1つ上など「近場」を素早く指したいとき - 迷ったら絶対パスを使う。現在地に左右されないため事故りにくい ::: ## 6. 迷子になったらどうする? {#lost} > **結論**: `pwd` で現在地を確認するのが基本。相対パスがうまく動かないときは、まず今どこにいるかを疑う。 ::: dialogue @lina: `cd Documents` と打ったら、英語のメッセージが出て移動できませんでした。さっきは同じコマンドで移動できたのに...! なんで今回だけ失敗するんですか? ::: ```bash cd Documents ``` ```output bash: cd: Documents: No such file or directory ``` ::: dialogue @linny: `No such file or directory` は「そんな名前のファイルやフォルダは見つかりません」という意味だよ。相対パスは今いる場所が基準になる。だから、思っていた場所と違う場所にいると失敗するんだ。 @lina: どうすれば直せますか? @linny: {合言葉|あいことば}は `pwd`。"print working directory" の略で、今いる場所を表示してくれる。エラーが出たら、まず `pwd` で現在地を確認しよう。 ::: ```bash pwd ``` ```output /var/log ``` ::: dialogue @lina: あっ、`/var/log` にいました! さっきは `/home/lina` にいたから動けたんですね。今回は場所が違ったから失敗したんだ...! すっきりしました。 ::: 「`Documents` がない」と言われたら、`pwd` の結果を見て「あ、今 `/var/log` にいるのか。じゃあ `cd ~/Documents` にしよう」と修正できる。`ls` で今いる場所の中身を一覧して、次に進めるフォルダを確認するのも有効だ。 ::: tip ブラウザ上で実際にパス移動を練習できる。[Penguin Gym Linux のターミナル](/terminal) で `cd` と `pwd` を何度も打って、現在地が変わる感覚を体で覚えよう。手を動かすのが上達の近道だ。 ::: ## ミニ課題 {#exercises} > **結論**: 3つの課題で、絶対パス・相対パス・`.` `..` `~` の理解を確認する。 ::: dialogue @linny: ここまでの内容を、3つの課題で確認してみよう。 ::: **課題1**: リナは `/home/lina/Documents` にいる。ここから `/home/lina` に戻るには、どう入力すればいいか。 :::details ヒント1(方向づけ)を見る 今いる場所から見て、目的地は1つ上の階層にある。表で見た記号の中に、この動きを表すものがあった。 ::: :::details ヒント2(コマンド名)を見る `cd ..` を使う。 ::: :::details 答えを見る ```bash pwd ``` ```output /home/lina/Documents ``` ```bash cd .. pwd ``` ```output /home/lina ``` ::: **課題2**: リナは `/var/log` にいる。ここから自分のホームディレクトリ(`/home/lina`)に一発で戻るには、どう入力すればいいか。 :::details ヒント1(方向づけ)を見る 絶対パスをフルで書かなくても、ホームディレクトリに一発で戻れる特別な記号があった。 ::: :::details ヒント2(コマンド名)を見る `cd ~` を使う。引数なしの `cd` でも同じ結果になる。 ::: :::details 答えを見る ```bash pwd ``` ```output /var/log ``` ```bash cd ~ pwd ``` ```output /home/lina ``` ::: **課題3**: リナは `/var` にいる。相対パスで `cd Documents` としたら `bash: cd: Documents: No such file or directory` と表示された。ホームディレクトリの中の `Documents` へ確実に移動するには、どう入力すればいいか。 :::details ヒント1(方向づけ)を見る 相対パスは今いる場所が基準になる。今いる場所に関係なく確実に目的地を指せるパスの種類を思い出そう。 ::: :::details ヒント2(コマンド名)を見る `cd` に絶対パス、または `~` を組み合わせる。 ::: :::details 答えを見る ```bash pwd ``` ```output /var ``` ```bash cd /home/lina/Documents pwd ``` ```output /home/lina/Documents ``` `cd ~/Documents` でも同じ場所に移動できる。 ::: ## まとめ {#summary} - パスはファイル・フォルダの「住所」。`/` で区切って道順を書く - **絶対パス** は `/`(ルート)から始まり、現在地に関係なく同じ場所を指す - **相対パス** は `/` で始まらず、今いる場所(カレントディレクトリ)が基準になる - `.` は今いる場所、`..` は1つ上、`~` は自分のホームディレクトリ - 相対パスでつまずいたら、まず `pwd` で現在地を確認する ## 次に読む {#next} - [ディレクトリ構造の全体像 - Linuxファイルシステムを理解する](/articles/guides/linux-directory-structure) - [ターミナルの使い方 - コマンドライン入門](/articles/guides/terminal-basics) - [基本コマンドを覚えよう](/articles/tutorials/basic-commands) # bash から乗り換える? zsh / fish の選び方 Source: https://penguin-gym-linux.com/articles/guides/choosing-a-shell-zsh-fish ## bash から zsh / fish に乗り換えるべきか? {#conclusion} > **結論**: 普段使いの操作を快適にしたいだけなら乗り換える価値はある。ただしサーバー運用とスクリプトは bash のまま残すのが安全。 判断は「何を最適化したいか」で決まる。 - **対話操作(補完・履歴・ハイライト)を快適にしたい** → zsh か fish に乗り換える価値あり - **スクリプト・サーバー管理・移植性が主目的** → bash のまま。乗り換え不要 - **設定に時間をかけたくない** → fish(インストール直後から快適) - **bash 互換を保ちつつ強化したい** → zsh(既存スクリプトの大半がそのまま動く) ::: tip **重要な前提** 乗り換えるのは「自分が対話的に使うログインシェル」だけ。`#!/bin/bash` で始まるスクリプトの実行シェルは乗り換えても変わらない。両者は別物として切り分ける。 ::: bash・zsh・fish それぞれの機能比較そのものは [shell 比較(bash/zsh/fish)](/articles/guides/shell-comparison) で詳しく扱っている。本記事は「実際に乗り換えるかどうかの判断」と「乗り換え作業・トラブル対応」に絞る。 ## 乗り換えると具体的に何が変わるのか? {#what-changes} > **結論**: 変わるのは対話操作の快適さ(補完・提案・色)と設定ファイル。スクリプトの実行環境は変わらない。 乗り換えで実際に体感が変わるのは次の点。 | 体感する場面 | bash | zsh | fish | | -------------------- | ----------- | ---------------- | ---------------------------- | | Tab 補完 | 基本のみ | 強力(候補選択) | 最も賢い | | 履歴からの先読み提案 | なし | プラグイン要 | 標準で薄字表示 | | 入力中の色付け | なし | プラグイン要 | 標準 | | 設定ファイル | `~/.bashrc` | `~/.zshrc` | `~/.config/fish/config.fish` | | 既存 bash スクリプト | 動く | ほぼ動く | **動かない** | ::: warning fish は対話の快適さが最大の魅力だが、`VAR=value`・`export VAR=...`・`if [ ... ]` といった POSIX 構文がそのままでは動かない。`.bashrc` をコピーしても fish では読み込めない。この非互換が乗り換えで最も詰まるポイント。 ::: ## どのシェルに乗り換えるかの判断フロー {#decision} > **結論**: 既存の bash 設定を活かしたいなら zsh、ゼロから快適さを取りたいなら fish。迷うならまず zsh が無難。 次の順で自問すると決まりやすい。 1. **`.bashrc` に多くの alias / 関数 / PATH 設定を貯めている?** - はい → zsh(構文がほぼ同じで移植が楽) - いいえ → 次へ 2. **設定を書くより、入れた瞬間から快適に使いたい?** - はい → fish - いいえ → 次へ 3. **Oh My Zsh などのテーマ・プラグイン文化を使いたい?** - はい → zsh - どちらでもよい → zsh(情報量が多く詰まりにくい) ::: tip 「インストールして `chsh` する前に、まず一時的に起動して試す」のが鉄則。`zsh` または `fish` とそのまま打てば、ログインシェルを変えずにその場で体験できる。`exit` で元の bash に戻る。 ::: ## zsh への乗り換え手順 {#migrate-zsh} > **結論**: インストール → 試用 → `chsh` で既定化 → `.bashrc` の必要分を `.zshrc` へ移す、の 4 ステップ。 ### 1. インストールして試す ```bash # Ubuntu / Debian sudo apt install zsh # まずログインシェルを変えずに起動して試す zsh ``` 初回起動時に設定ウィザード(`zsh-newuser-install`)が出る。とりあえず `q` で抜けて後から設定してよい。 ### 2. デフォルトシェルに設定 ```bash chsh -s "$(which zsh)" ``` ::: warning `chsh` の変更は次回ログイン(端末の開き直し)から反映される。現在のセッションには効かない。反映後は `echo $SHELL` で `/usr/bin/zsh` 等になっているか確認する。 ::: ### 3. bash の設定を引き継ぐ zsh は bash と構文が近いため、`~/.bashrc` の alias・関数・PATH 追加の大半はそのまま `~/.zshrc` に貼って動く。ただし bash 専用の補完設定(`bash-completion`)や `PROMPT_COMMAND` は zsh 用の書き方に置き換える必要がある。 ```bash # .zshrc に貼ってそのまま動く例 alias ll='ls -alF' export PATH="$HOME/.local/bin:$PATH" ``` 補完やテーマを一括で整えたいなら Oh My Zsh を導入する選択肢もあるが、起動が重くなることがある点は [shell 比較記事](/articles/guides/shell-comparison) のとおり。 ## fish への乗り換え手順 {#migrate-fish} > **結論**: fish は POSIX 非互換のため `.bashrc` は移植できない。設定は fish の文法で書き直す前提で乗り換える。 ### 1. インストールして試す ```bash # Ubuntu / Debian sudo apt install fish # ログインシェルを変えずに起動して試す fish ``` ### 2. デフォルトシェルに設定 ```bash # fish のパスが /etc/shells に登録されているか確認 grep fish /etc/shells chsh -s "$(which fish)" ``` ### 3. 設定は fish の文法で書く bash の `export` や `VAR=value` は fish では使えない。代表的な書き換えは次のとおり。 ```fish # bash: export PATH="$HOME/.local/bin:$PATH" # fish: set -gx PATH $HOME/.local/bin $PATH # bash: alias ll='ls -alF' # fish(alias は使えるが関数推奨): alias ll='ls -alF' ``` ::: tip fish の対話補完は man ページから自動生成されるため、設定ゼロでも多くのコマンドのオプション補完が効く。これが「設定せずに快適」と言われる理由。 ::: ## 乗り換えで詰まる定番トラブル {#pitfalls} > **結論**: 詰まりの大半は「スクリプトを fish 構文で実行した」「`chsh` が即時反映だと誤解」「設定ファイルを取り違えた」の 3 つ。 ### `~/.bashrc` を編集したのに反映されない 乗り換え後に読み込まれるのは zsh なら `~/.zshrc`、fish なら `~/.config/fish/config.fish`。bash の設定ファイルは対話シェルとしては読まれなくなる。編集先を取り違えていないか確認する。 ### fish でシェルスクリプトが動かない ```fish > export FOO=bar fish: Unsupported use of '='. ... ``` fish は POSIX 非互換。手元の `.sh` スクリプトを動かすときは fish のままにせず、明示的に bash で実行する。 ```bash bash ./deploy.sh ``` スクリプトの先頭に `#!/bin/bash` を付け、実行権限を与えて `./deploy.sh` とすれば、ログインシェルが fish でも bash で実行される。 ### chsh が反映されない `chsh` は次回ログインから有効。SSH なら再接続、デスクトップ端末なら開き直しが必要。すぐ試したいだけなら `chsh` せずコマンド名(`zsh` / `fish`)で起動する。 ## 元に戻すには(ロールバック) {#rollback} > **結論**: bash の実体は消えないので、`chsh -s /bin/bash` でいつでも戻せる。乗り換えは可逆な操作。 zsh / fish を試して合わなければ、デフォルトを bash に戻すだけでよい。 ```bash chsh -s /bin/bash ``` ::: warning 万一ログインシェルが壊れてターミナルが開けなくなった場合でも、別ユーザーや復旧コンソールから `/etc/passwd` の該当行の末尾シェルパスを `/bin/bash` に直せば復旧できる。`chsh` 自体は `/etc/passwd` を書き換えているだけ。 ::: インストールした zsh / fish のパッケージを残しておけば、対話用は fish、スクリプト確認用は bash、と使い分けることもできる。乗り換えは「どれか 1 つを捨てる」決断ではない。 ## 次に読む {#next} - [shell 比較(bash/zsh/fish)- 違いと選び方](/articles/guides/shell-comparison) - [シェル(shell)とは何か](/articles/guides/what-is-a-shell) - [ターミナルの使い方 - コマンドライン入門](/articles/guides/terminal-basics) # Readline ショートカット入門 - Ctrl+R / Ctrl+A でターミナル操作高速化 Source: https://penguin-gym-linux.com/articles/guides/cli-shortcuts-readline ## ターミナル操作、もっと速くできる {#intro} コマンドを打ち間違えることがある。そのたびに矢印キーを何度も押す。カーソルを行き来させるだけで時間がかかる。さっき打った長いコマンドを、もう一度最初から入力し直すのも同じくらい面倒だ。 こうした「地味に遅い操作」は、ショートカット(キーバインド。特定のキーの組み合わせで操作を呼び出す仕組み)を覚えるだけで、一気に解決する。 この記事では、ターミナル入力を高速化する Readline ショートカットを、リナとライニー先輩の会話でやさしく整理していく。bash でも zsh でも、ほぼ同じキーが使える(bash・zsh は「シェル」の種類だ。シェルとは、打ったコマンドを受け取って実行するプログラムのこと)。 ## この記事でわかること {#toc} - ショートカットの正体が「Readline」というライブラリであること - カーソルを一瞬で行頭・行末へ飛ばす方法(Ctrl+A / Ctrl+E) - 入力ミスをまとめて消す行編集(Ctrl+U / Ctrl+K / Ctrl+W) - 過去のコマンドを検索して再利用する Ctrl+R - 今日から使える厳選ショートカット早見表 - 覚えたショートカットを実際に試すミニ課題 ## 1. そもそもショートカットの正体は? {#what} > **結論**: Ctrl+A などのショートカットは、bash 固有の機能ではない。「Readline」という行編集ライブラリの機能だ。だから多くのシェルで共通して使える。 ::: dialogue @lina: ライニー先輩、コマンドの途中でカーソルを動かしたいとき、いつも矢印キーを連打しています。遅いんです… @linny: それ、ショートカットを使えば一瞬だよ。しかも、そのショートカットは bash だけの機能じゃないんだ。 @lina: え、bash の機能じゃないんですか? @linny: うん。コマンドラインで「文字を打つ・消す・カーソルを動かす」部分は、Readline(リードライン)という共通の部品が担当している。「行編集ライブラリ」とも呼ばれる、入力1行の面倒をみる部品だよ。bash も、たいていの zsh も、このキー操作をそのまま受け継いでいるんだ。 @lina: だから一度覚えれば、いろんな場所で使えるってことですね。 @linny: その通り。今日覚えるキーは、SSH(別のコンピュータへ遠くからログインする仕組み)でつないだサーバーの上でも、そのまま効くよ。一度覚えれば、ずっと使えるんだ。 ::: ::: tip **Readline は「行を編集する係」** Readline は、ターミナルに表示される1行のコマンドの面倒をみている。入力・修正・履歴呼び出しまで、すべて担当する。標準では Emacs というエディタ風のキー割り当てになっている。だから Ctrl 系のキーが多い。 ::: ## 2. カーソルを一瞬で行頭・行末へ {#move} > **結論**: `Ctrl+A` で行頭へ、`Ctrl+E` で行末へ、一瞬で移動できる。長いコマンドの先頭を直すとき、矢印キーの連打が不要になる。 ::: dialogue @lina: まず何から覚えるのがおすすめですか? @linny: 一番効くのは「カーソル移動」だね。長いコマンドの先頭に `sudo` を付け忘れた。そんなとき、Ctrl+A で一瞬で行頭に飛べる。 @lina: A は何の意味ですか? @linny: A は行の **A**head(先頭)、E は **E**nd(末尾)。そう覚えるといいよ。試しに長めのコマンドを打って、まだ Enter は押さずにやってみよう。 ::: ```bash echo これはとても長いコマンドの例です ``` | キー | 動作 | 覚え方 | | -------- | ---------- | ----------------- | | `Ctrl+A` | 行頭へ移動 | **A**head(先頭) | | `Ctrl+E` | 行末へ移動 | **E**nd(末尾) | | `Ctrl+F` | 1文字右へ | **F**orward | | `Ctrl+B` | 1文字左へ | **B**ackward | | `Alt+F` | 1単語右へ | **F**orward word | | `Alt+B` | 1単語左へ | **B**ackward word | ::: tip 1文字ずつなら矢印キーでもいい。けれど、**単語単位**で飛べる `Alt+F` / `Alt+B` を覚えると、パスやオプションの修正にかかる手数が減る。 macOS の標準ターミナルでは、Alt(Option)キーがそのままでは効かないことがある。その場合は `Esc` を押してから `F` を押すと、同じ動きになる。 ::: ## 3. 入力ミスをまとめて消すには? {#delete} > **結論**: `Ctrl+U` はカーソルより前を全削除する。`Ctrl+K` は後ろを全削除する。`Ctrl+W` は直前の単語を削除する。Backspace の連打から卒業できる。 ::: dialogue @lina: コマンドを打ち間違えたとき、Backspace を押しっぱなしにして、全部消しています… @linny: それも卒業しよう。まずは Ctrl+U を覚えよう。カーソルより前を、まとめて消せるキーだよ。 @lina: あの、長いコマンドを打っている途中で、単語を1つだけ消したかったのに、間違えて Ctrl+U を押してしまったことがあります。そうしたら、打っていた文字が全部消えて、焦りました… @linny: それ、よくある失敗だね。カーソルが行の末尾にあると、Ctrl+U は「そこまで打った内容ぜんぶ」を消してしまうんだ。 @lina: そうだったんですね…! 単語1つだけ消したいときは、別のキーを使えばいいんですか? @linny: そう。直前の単語1つだけなら Ctrl+W だよ。ここでいう「単語」は、空白で区切られたひとかたまりのことなんだ。 @lina: `/home/lina/Documents` みたいなパスは、どうなりますか? @linny: パスには空白がない。だからひとかたまりと{判定|はんてい}され、まるごと消えるよ。「`/` ごとに1階層ずつ消える」わけではないから、そこは注意してね。 @lina: 全部消したいときは、どうすればいいですか? @linny: 行頭に Ctrl+A で戻ってから Ctrl+K を押せば、確実に全部消せるよ。逆に Ctrl+U を行頭で使っても、何も消えない。カーソルより前に文字がないからね。 ::: | キー | 動作 | | -------- | ------------------------------------------------------------ | | `Ctrl+U` | カーソルより**前**をすべて削除 | | `Ctrl+K` | カーソルより**後ろ**をすべて削除 | | `Ctrl+W` | カーソル直前の**単語**(空白で区切られたひとかたまり)を削除 | | `Ctrl+Y` | 直前に削除した内容を貼り付け | ::: warning **`Ctrl+U` の消える範囲はシェルによって違う** bash では `Ctrl+U` はカーソルより前だけを消す。zsh では初期設定のまま使うと、カーソル位置に関係なく**行全体**が消える。まずは短い入力で1回試して、どこまで消えるか自分の目で確かめよう。 ::: ::: highlight **消す → 貼る はセット** `Ctrl+U` / `Ctrl+K` / `Ctrl+W` で消した内容は「キルリング」という場所に一時保存される。`Ctrl+Y`(**Y**ank=引き出す)でそのまま貼り付け直せる。間違えて消しても慌てなくて大丈夫。 ::: ## 4. さっき打ったコマンドをもう一度使うには? {#history} > **結論**: `Ctrl+R` で{履歴|りれき}を{逆順|ぎゃくじゅん}に検索できる。一部のキーワードを打つだけで、過去の長いコマンドを呼び戻せる。 ::: dialogue @lina: 昨日打った長い `docker` コマンドを、もう一度使いたいです。また全部打つしかないですか? @linny: そこで Ctrl+R だよ。履歴検索といって、過去に打ったコマンドをキーワードで探し出せるんだ。`Ctrl+R` を押して、コマンドの一部、例えば `docker` と打ってみて。 @lina: あ! 昨日のコマンドが出てきました! @linny: でしょう。そのまま Enter を押せば実行できる。もう一度 Ctrl+R を押せば、もっと古い候補が出るよ。左右の矢印キーや Ctrl+E を押せば、編集してから実行できる。やめたいときは Ctrl+G か Ctrl+C で抜けられるよ。 ::: ```output (reverse-i-search)`docker': docker compose up -d --build ``` ::: tip **Ctrl+R の操作の流れ** 1. `Ctrl+R` を押す(`(reverse-i-search)` 表示になる) 2. コマンドの一部を入力する(部分一致で候補が出る) 3. もう一度 `Ctrl+R`:さらに古い候補へ 4. `Enter`:そのまま実行 / `Ctrl+G`:キャンセル ::: 上下の矢印キーや `Ctrl+P`(**P**revious)/ `Ctrl+N`(**N**ext)でも履歴を1つずつたどれる。直前のコマンドをちょっと直したいだけなら、こちらが手早い。 ## 5. 画面と操作をリセットしたいときは? {#control} > **結論**: `Ctrl+L` で画面クリア、`Ctrl+C` で実行中コマンドの中断、`Ctrl+D` で入力終了ができる。混乱したときに使える「逃げ道」キーだ。 ::: dialogue @lina: 画面が出力でいっぱいになって、見づらいときはどうすればいいですか? @linny: `Ctrl+L` を押すと、画面がすぐきれいになるよ。`clear` コマンドと同じ効果だけど、打つより速い。コマンドが終わらず、固まったように見えるときは `Ctrl+C` で中断できる。 @lina: 実は前に、コマンドが固まったと思って `Ctrl+D` を押したことがあります。そうしたらターミナルごと閉じてしまって、すごく焦りました… @linny: それ、あるある失敗だね。`Ctrl+D` は「もう入力しない」と伝えるキーで、動いているコマンドを止める効果はないんだ。 @lina: てっきり中断のキーだと思っていました…! @linny: 覚え方はシンプルだよ。止めたいなら `Ctrl+C`、もう何も入力しないなら `Ctrl+D`。プロンプトで何も入力せずに `Ctrl+D` を押すと、ログアウト(シェル終了)になる。`cat` コマンドで入力待ちのとき、入力を終わらせるためにも使うよ。 ::: | キー | 動作 | | -------- | ----------------------------------------- | | `Ctrl+L` | 画面をクリア(`clear` 相当) | | `Ctrl+C` | 実行中のコマンドを中断 | | `Ctrl+D` | 入力の終わり / 空のプロンプトでシェル終了 | ::: warning `Ctrl+C` と `Ctrl+D` は役割が違う。`Ctrl+C` は「今動いているものを止める」、`Ctrl+D` は「もう入力しないと伝える」。固まったように見えたら、まず `Ctrl+C` を試すのが基本だ。 ::: ## 6. まず覚える厳選ショートカット {#cheatsheet} > **結論**: いきなり全部は不要だ。「行頭 / 行末移動・行削除・履歴検索」の5つだけ、先に体に入れよう。それだけで、1回の入力にかかる時間が短くなる。 ::: dialogue @lina: 数が多くて、どれから覚えればいいか迷います… @linny: 最初は5つでいい。下の早見表を上から順に、今日のターミナル作業で意識して使ってみて。1週間で、指が勝手に動くようになるよ。 ::: | 優先 | キー | 動作 | | ---- | -------- | ---------------------- | | 1 | `Ctrl+A` | 行頭へ移動 | | 2 | `Ctrl+E` | 行末へ移動 | | 3 | `Ctrl+U` | カーソルより前を全削除 | | 4 | `Ctrl+R` | コマンド履歴を逆順検索 | | 5 | `Ctrl+L` | 画面をクリア | ::: highlight 覚えたショートカットは、実際にコマンドを打つ場で使ってこそ定着する。[ターミナルの使い方](/articles/guides/terminal-basics) のページで、手を動かしながら試してみよう。 ::: ## 7. ミニ課題 - 手を動かして確認しよう {#practice} ::: dialogue @lina: 覚えたことを、実際に試してみたいです! @linny: いいね。次の3つを、実際のターミナルで試してみよう。答えを見る前に、まず自分でやってみて。 ::: **課題1**: 長めのコマンドを入力し(Enter はまだ押さない)、カーソルを行の先頭と末尾へすばやく移動させてみよう。 :::details ヒント1(方向づけ)を見る 矢印キーを何度も押さなくても、1回のキー操作で行の端まで移動できるキーがある。 ::: :::details ヒント2(キー名)を見る 行頭へは `Ctrl+A`、行末へは `Ctrl+E` を使う。 ::: :::details 答えを見る `echo` などで長めのコマンドを入力する(まだ Enter は押さない)。`Ctrl+A` を押すとカーソルが行頭に、`Ctrl+E` を押すと行末に、一瞬で移動する。 ::: **課題2**: 入力したコマンドの途中にカーソルを置き、「前だけ」「後ろだけ」を、それぞれ削除してみよう。 :::details ヒント1(方向づけ)を見る Backspace を連打しなくても、カーソルを基準に「前」と「後ろ」をまとめて消せるキーが、それぞれ用意されている。 ::: :::details ヒント2(キー名)を見る カーソルより前を消すのは `Ctrl+U`、後ろを消すのは `Ctrl+K`。 ::: :::details 答えを見る コマンドの途中にカーソルを置いた状態で `Ctrl+U` を押すと、カーソルより前が消える。同じ位置で `Ctrl+K` を押すと、カーソルより後ろが消える。消えた内容は `Ctrl+Y` で貼り戻せる。 ::: **課題3**: さっき自分が打ったコマンドを、履歴検索で呼び出してみよう。 :::details ヒント1(方向づけ)を見る コマンド全体を覚えていなくても、一部のキーワードだけで過去の入力を探し出せるキーがある。 ::: :::details ヒント2(キー名)を見る `Ctrl+R` を押してから、コマンドの一部を入力する。 ::: :::details 答えを見る `Ctrl+R` を押すと `(reverse-i-search)` と表示される。そこにコマンドの一部(例: `echo`)を入力すると、一致する過去のコマンドが表示される。`Enter` で実行、`Ctrl+G` でキャンセルできる。 ::: ## まとめ {#summary} - Ctrl 系ショートカットの正体は Readline ライブラリ。bash でも zsh でも共通して使える - `Ctrl+A` / `Ctrl+E` で行頭・行末へ一瞬移動できる - `Ctrl+U` / `Ctrl+K` / `Ctrl+W` で入力をまとめて消し、`Ctrl+Y` で貼り戻せる - `Ctrl+U` はカーソルより前を全部消す。カーソル位置によっては、思ったより多く消えるので注意 - `Ctrl+R` で過去のコマンドをキーワード検索して再利用できる - `Ctrl+L`(画面クリア)・`Ctrl+C`(中断)・`Ctrl+D`(入力終了)は覚えておくと安心 - まずは5つだけ。使って慣れるのが上達の近道 ## 次に読む {#next} - [ターミナルの使い方 - コマンドライン入門](/articles/guides/terminal-basics) - [シェル(shell)とは何か](/articles/guides/what-is-a-shell) - [bash/zsh/fish の違いと選び方](/articles/guides/shell-comparison) # コンテナ(containers)と仮想マシン(VMs)の違い - Docker入門の前に知る基礎 Source: https://penguin-gym-linux.com/articles/guides/containers-vs-vms ## この記事で解決できること {#intro} - コンテナと仮想マシン(VM)が **何を仮想化しているのか** を仕組みから区別できる - 「なぜコンテナは軽くて速いのか」「なぜ VM は隔離が強いのか」を **理屈で** 説明できる - Docker に触る前に、**どちらを使うべきか** の判断軸が身につく ::: tip **結論(先に要点)** - VM は **ハードウェアごと** 仮想化し、各 VM が独自の **ゲスト OS カーネル** を持つ - コンテナは **ホストのカーネルを共有** し、`namespace` と `cgroups` でプロセスを隔離する - 軽さ・速さはコンテナ、隔離の強さは VM。**用途で使い分ける**(併用も一般的) ::: ::: warning **前提(対象環境)** - Linux ホスト(Ubuntu / RHEL 系いずれも考え方は同じ) - コンテナは Linux コンテナ(Docker / Podman 等)を想定 - VM は KVM / VirtualBox / VMware 等のハイパーバイザ型を想定 ::: ## コンテナと仮想マシンは何が違うのか? {#difference} > **結論**: VM は OS まるごとを仮想化し、コンテナはアプリのプロセスだけを隔離する。仮想化する「層」が違う。 両者は「1 台のマシンで複数の環境を動かす」点では同じだが、**仮想化する対象のレイヤーが違う**。 | 観点 | 仮想マシン(VM) | コンテナ | | -------------- | ------------------------------ | ------------------------------- | | 仮想化の対象 | ハードウェア(CPU/メモリ/NIC) | プロセス(OS リソースの見え方) | | カーネル | VM ごとに独自のゲストカーネル | ホストカーネルを共有 | | 起動時間 | 数十秒〜数分 | ミリ秒〜数秒 | | イメージサイズ | 数 GB | 数 MB〜数百 MB | | オーバーヘッド | ハイパーバイザ層の分だけ重い | ほぼネイティブ | | 隔離の強さ | 強い(カーネルが別) | 中(カーネルを共有) | | 異種 OS | 可能(Linux 上で Windows 等) | 不可(ホストと同じカーネル) | VM は「物理マシンをまるごとソフトウェアで再現する」発想。コンテナは「同じ OS の上で、各アプリに専用の世界を見せる」発想だと捉えると整理しやすい。 ## なぜコンテナは軽量で速いのか? {#why-light} > **結論**: コンテナはホストのカーネルをそのまま使うため、OS を起動し直さない。だから速く、メモリも食わない。 コンテナの軽さの正体は **カーネル共有** にある。VM が起動のたびにゲスト OS(カーネル + 初期化プロセス群)をブートするのに対し、コンテナは **すでに動いているホストカーネルの上で、ただプロセスを 1 つ起動するだけ**。 その「プロセスを隔離して、あたかも独立した OS に見せる」仕組みが Linux カーネルの 2 つの機能だ。 ### namespace(分離する) `namespace` は、プロセスから見える OS リソースを **区切る** 仕組み。種類ごとに見える範囲を分離する。 - **PID**: プロセス ID 空間(コンテナ内では自分が PID 1 に見える) - **mount**: ファイルシステムのマウント状態 - **network**: ネットワークインターフェース・ルーティング - **UTS**: ホスト名 - **IPC**: プロセス間通信 - **user**: UID/GID のマッピング 現在のホストの namespace は `lsns` で確認できる。 ```bash $ lsns ``` ### cgroups(制限する) `cgroups`(control groups)は、プロセスが使える **リソース量を制限・計測** する仕組み。CPU・メモリ・I/O などに上限を設けられる。 ```bash $ systemd-cgls ``` ::: tip **namespace は「見える範囲」、cgroups は「使える量」** この 2 つを組み合わせて、1 つのプロセスを「独立した OS のように」隔離したものがコンテナの実体。 ::: ## 仮想マシンはどんな仕組みなのか? {#vm} > **結論**: VM はハイパーバイザがハードウェアを仮想化し、その上でゲスト OS をまるごと起動する。 VM は **ハイパーバイザ**(hypervisor)と呼ばれる層が、CPU・メモリ・ディスク・NIC といった **ハードウェアを仮想化** する。ゲスト OS はそれが本物のハードウェアだと信じてブートする。 ハイパーバイザには大きく 2 種類ある。 - **Type 1(ベアメタル型)**: ハードウェア直上で動く。KVM / Xen / VMware ESXi。サーバ・クラウドの基盤。 - **Type 2(ホスト型)**: ホスト OS の上で動く。VirtualBox / VMware Workstation。手元の検証用。 ゲストごとに独立したカーネルが走るため、**Linux ホスト上で Windows を、その隣で別バージョンの Linux を** といった異種 OS の同居ができる。これはカーネルを共有するコンテナにはできない芸当だ。 ## 隔離(セキュリティ)はどちらが強いのか? {#isolation} > **結論**: 隔離の強さは VM が上。コンテナはカーネルを共有するぶん、カーネルの脆弱性が全コンテナに波及しうる。 VM はゲストごとにカーネルが分かれているため、1 つの VM が侵害されても他の VM・ホストへ直接は波及しにくい。境界がハードウェア仮想化のレベルにある。 一方コンテナは **ホストカーネルを共有** する。利点(軽さ)の裏返しで、カーネルに脆弱性があれば、コンテナからホストへ抜ける「コンテナブレイクアウト」のリスクが理屈上は残る。 ::: warning コンテナは「軽量な VM」ではない。隔離レベルが異なるため、強い分離が要件なら VM、あるいは VM とコンテナの併用を検討する。 ::: 実務では、クラウド上で **VM の中でコンテナを動かす** 構成が一般的。VM で強い境界を確保しつつ、その内側でコンテナの軽さ・可搬性を活かす、いいとこ取りの形だ。 ## どちらを使うべきか? {#which} > **結論**: アプリのパッケージング・配布・高密度ならコンテナ、異種 OS・強い隔離・カーネル検証なら VM。 用途別の目安は次のとおり。 | やりたいこと | 向いている方 | | --------------------------------------- | ------------ | | アプリを環境ごとパッケージして配布 | コンテナ | | マイクロサービス・CI/CD | コンテナ | | 1 台に多数の環境を高密度に詰める | コンテナ | | Linux 上で Windows など異種 OS を動かす | VM | | 強い隔離・マルチテナント境界 | VM | | カーネルモジュールや OS そのものの検証 | VM | 判断に迷ったら、こう問うと早い。 1. **ホストと同じカーネルで足りるか?** → 足りるならコンテナが第一候補 2. **異種 OS や独自カーネルが要るか?** → 要るなら VM 3. **隔離はどこまで厳しく要求されるか?** → 厳しいほど VM(または VM 内コンテナ) ## Docker入門の前に知っておくべきこと {#before-docker} > **結論**: Docker は namespace と cgroups を使いやすく包んだツール。仕組みを知っていれば「魔法」ではなく理屈で読める。 Docker を触り始めると **イメージ** と **コンテナ** という言葉が出てくる。ここまでの仕組みと対応づけておくと混乱しない。 - **イメージ**: コンテナの「設計図」。アプリと依存をまとめた読み取り専用のテンプレート - **コンテナ**: イメージから起動した「実行中のプロセス」。その正体は namespace と cgroups で隔離された Linux プロセス つまり Docker は、ここで見た **カーネル機能を人間が扱いやすい形に包んだ道具**にすぎない。「コンテナ = 超軽量 VM」という誤解さえ手放せば、Docker のコマンドは素直に読めるようになる。 ::: tip **ここまでの理解で十分** 「カーネルを共有し、namespace で分離、cgroups で制限」——この一文が頭に入っていれば、Docker 入門はスムーズに進む。 ::: ## 次に読む {#next} - [LinuxユーザーのためのDocker入門概論](/articles/guides/docker-for-linux-users) - [ディレクトリ構造の全体像](/articles/guides/linux-directory-structure) - [signal(シグナル)とは](/articles/guides/signals-concept) - [rootとsudoの考え方](/articles/guides/root-vs-sudo-concept) - [DevOps学習ハブ(Docker・Git・AWSのコース比較)](/devops) # Linux 開発環境構築ガイド(dev environment setup)- 最初に入れる道具一式 Source: https://penguin-gym-linux.com/articles/guides/dev-environment-setup ## この記事で分かること {#intro} - 新しい Linux マシンで **最初に入れるべき道具一式** が分かる - パッケージマネージャ・エディタ・シェル・Git・SSH 鍵を **正しい順番** で整えられる - 環境を **再現可能(reproducible)** にして、次のマシンでも迷わなくなる ::: tip **結論(実務の型)** - まず **パッケージマネージャ** を起点にする(依存解決を一元化) - 次に **エディタ・シェル・Git・SSH 鍵** の 4 点を揃える - 最後に **手順をスクリプト / dotfiles 化** して再現性を確保する ::: ::: warning **前提(対象環境)** - Debian / Ubuntu 系(`apt`)を例に説明する - 他ディストリのコマンド対応は [パッケージマネージャ概観](/articles/guides/package-manager-overview) を参照 - ターミナル操作と sudo が使えること ::: ## 開発環境(dev environment)に最初に入れる道具は? {#tools} > **結論**: 最初に入れるのはパッケージマネージャ・エディタ・シェル・Git・SSH 鍵の 5 点。これだけで大半の開発作業が回る。 優先度の高い順に並べると次のとおり。1 つずつ後続セクションで掘り下げる。 | 順番 | 道具 | 役割 | | ---- | -------------------- | -------------------------------- | | 1 | パッケージマネージャ | すべての道具を入れる土台 | | 2 | エディタ | コードを書く・読む | | 3 | シェル | コマンドを実行する対話環境 | | 4 | Git | バージョン管理・コード共有 | | 5 | SSH 鍵 | リモート / GitHub への安全な認証 | ## なぜパッケージマネージャから始めるのか? {#package} > **結論**: OS 標準のパッケージマネージャを起点にすれば、依存解決と更新を一元管理でき、環境構築の再現性が上がる。 個別のインストーラを寄せ集めると、更新もアンインストールも管理できなくなる。まずパッケージ一覧を更新し、開発に最低限必要な土台を入れる。 ```bash sudo apt update sudo apt install -y build-essential git curl wget ca-certificates ``` `build-essential` は `gcc` / `make` などコンパイルに必要な道具をまとめて導入するメタパッケージ。多くの言語ランタイムやツールがビルド時にこれらを要求する。 導入後はバージョンを確認しておくと、後でトラブルを切り分けやすい。 ```bash gcc --version git --version ``` ```output gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 git version 2.34.1 ``` ## エディタとシェルはどう選ぶか? {#editor-shell} > **結論**: エディタは普段使いの 1 つに絞り、シェルは bash か zsh を既定にすると学習コストを抑えられる。 エディタは好みで構わないが、最初は **1 つに集中** したほうが習熟が早い。ターミナル内で完結させたいなら `vim` / `nano`、GUI なら VS Code が定番。 ```bash sudo apt install -y vim ``` シェルは Debian / Ubuntu の既定が `bash`。補完や履歴を強化したいなら `zsh` も選択肢になる。違いと選び方は [shell 比較(bash/zsh/fish)](/articles/guides/shell-comparison) を参照。 ::: tip 迷ったら **bash のまま** で十分。シェルを変えるのは基本操作に慣れてからでよい。 ::: ## Git と SSH 鍵はどう設定するか? {#git-ssh} > **結論**: Git の user 設定と SSH 鍵生成・登録を最初に済ませれば、認証で詰まらず push まで通せる。 まず Git のコミット作者情報を設定する。これを忘れると最初のコミットで止まる。 ```bash git config --global user.name "Your Name" git config --global user.email "you@example.com" ``` 次に SSH 鍵を生成する。現在は `ed25519` が推奨。 ```bash ssh-keygen -t ed25519 -C "you@example.com" ``` 生成した **公開鍵** を表示し、GitHub などのサービスに登録する(秘密鍵は絶対に共有しない)。 ```bash cat ~/.ssh/id_ed25519.pub ``` 登録後、接続確認すると成功メッセージが返る。 ```output Hi your-name! You've successfully authenticated, but GitHub does not provide shell access. ``` ::: danger 秘密鍵(`id_ed25519`、拡張子なしのほう)は他人に渡さない。漏れたら鍵を作り直してサービス側を更新する。 ::: ## 言語ランタイムはグローバル導入でいいか? {#runtime} > **結論**: 言語ランタイムはグローバル導入を避け、バージョン管理ツールでプロジェクト単位に切り替えるのが安全。 `apt` で言語ランタイムを直接入れると、プロジェクトごとにバージョンが異なる場面で詰む。**バージョン管理ツール** を使うと、プロジェクトごとに切り替えられる。 | 言語 | 代表的なバージョン管理ツール | | ------- | ---------------------------- | | Node.js | nvm / fnm | | Python | pyenv | | Ruby | rbenv | | Rust | rustup | 例として Rust は公式インストーラ `rustup` が標準。 ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` ::: warning インストール用の `curl | sh` は **URL が公式か** を必ず確認してから実行する。出所不明のスクリプトをそのまま流さない。 ::: ## 環境を再現可能にするには? {#reproducible} > **結論**: インストール手順をスクリプトや dotfiles にまとめておけば、新しいマシンでも同じ環境を再現できる。 ここまでの手順を毎回手作業でやると、マシンを移るたびに差異が生まれる。**手順をコード化** しておくと再現性が上がる。 簡単なのは、導入コマンドを 1 枚のシェルスクリプトにまとめておく方法。 ```bash #!/usr/bin/env bash set -euo pipefail sudo apt update sudo apt install -y build-essential git curl vim git config --global user.name "Your Name" git config --global user.email "you@example.com" ``` 設定ファイル(`.bashrc` / `.gitconfig` / `.vimrc` など、いわゆる **dotfiles**)は Git リポジトリで管理し、新しいマシンで clone するのが定番。 ```bash git clone git@github.com:your-name/dotfiles.git ~/dotfiles ``` ::: tip 最初から完璧を目指さなくてよい。**使うたびに追記** していくと、自分用の構築スクリプトが自然に育つ。 ::: ## よくあるつまずきと対処 {#pitfalls} > **結論**: つまずきの多くは権限・PATH・鍵の登録漏れが原因。sudo の乱用を避け、エラーメッセージを読む習慣が近道。 - **Permission denied**: 書き込み先の権限不足。むやみに `sudo` を付けず、まず対象の所有者と権限を `ls -l` で確認する - **command not found**: インストール済みでも PATH が通っていない可能性。`which <コマンド>` で実体を確認する - **Git push で認証エラー**: SSH 公開鍵をサービス側に登録できているか、`ssh -T git@github.com` で確認する ::: warning **やってはいけないこと** - エラーを読まずに `sudo` を付けて再実行する - 出所不明のスクリプトを確認せず実行する - 秘密鍵をリポジトリにコミットする ::: ## 次に読む {#next} - [パッケージマネージャ概観 - apt / dnf / pacman / zypper](/articles/guides/package-manager-overview) - [shell 比較(bash/zsh/fish)- 違いと選び方](/articles/guides/shell-comparison) - [ファイル転送の基本(scp / rsync)](/articles/tutorials/scp-rsync-basics) # LinuxユーザーのためのDocker入門概論 - コマンド知識を活かして学ぶ Source: https://penguin-gym-linux.com/articles/guides/docker-for-linux-users ## この記事で解決できること {#intro} - Docker が **Linux の何を使って動いているのか** を、プロセス・namespace・cgroups の知識から理解できる - イメージ・コンテナ・Dockerfile という基本用語を、**丸暗記ではなく仕組みで** 捉えられる - 手元の Linux コマンド知識が **Docker 学習でどう活きるか** の見取り図が持てる ::: tip **結論(先に要点)** - Docker は新しい魔法ではなく、**Linux カーネルの機能(namespace / cgroups)を扱いやすく包んだ道具** - コンテナの正体は「隔離された 1 つの Linux プロセス」。VM のような別 OS ではない - ファイルシステム・ネットワーク・プロセス管理の知識が **そのまま Docker 理解の土台になる** ::: ::: warning **この記事の位置づけ** - 本サイトは Linux 学習サイト。Docker の**操作手順(`docker run` の全オプション等)は扱わない** - 目的は「Linux の知識から Docker へ橋を架ける」こと。**概念を掴んだら実践は専用教材で**深めるのが最短 ::: ## Docker とは何か {#what-is-docker} > **結論**: Docker は、アプリを「動く環境ごと」パッケージして持ち運べるようにするツール。その中身は Linux カーネルの隔離機能。 Docker を一言でいえば「アプリケーションを、依存関係を含めた 1 つのまとまりとして梱包し、どこでも同じように動かす」ための道具だ。「自分の環境では動くのに本番では動かない」という食い違いを、**環境ごと配る**ことで解消する。 ここで多くの初学者が「コンテナ = 軽い仮想マシン」と誤解する。だが仕組みは根本的に違う。VM がハードウェアごと仮想化して別の OS を起動するのに対し、**コンテナはホストの Linux カーネルをそのまま共有し、プロセスを隔離しているだけ**だ。この違いは [コンテナと仮想マシンの違い](/articles/guides/containers-vs-vms) で仕組みから解説している。 コンテナを支える Linux カーネルの機能は、大きく 2 つ。 - **namespace**: プロセスから見える OS リソース(プロセス ID・ネットワーク・マウント等)を **区切る** - **cgroups**: プロセスが使える CPU・メモリ・I/O の **量を制限する** 現在のホストで動いている namespace は `lsns` で確認できる。 ```bash $ lsns NS TYPE NPROCS PID USER COMMAND 4026531834 time 120 1 root /sbin/init 4026531835 cgroup 120 1 root /sbin/init 4026531836 pid 118 1 root /sbin/init ``` つまり Docker は、この `namespace` と `cgroups` を人間が扱いやすいコマンド体系(`docker run` など)に包んだラッパーだと捉えると腑に落ちる。 ## Docker の基本概念 {#concepts} > **結論**: 覚えるべき用語は 3 つ。イメージ(設計図)・コンテナ(実行中プロセス)・Dockerfile(設計図の作り方)。 操作を覚える前に、まず 3 つの言葉の関係を掴む。 | 用語 | 正体 | Linux に例えると | | -------------- | ------------------------------------------ | ------------------------------ | | **イメージ** | アプリと依存をまとめた読み取り専用の雛形 | 実行ファイル + 必要な一式 | | **コンテナ** | イメージから起動した実行中のプロセス | `namespace` で隔離したプロセス | | **Dockerfile** | イメージの作り方を記述したテキストの手順書 | セットアップ用シェルスクリプト | 流れはシンプルだ。**Dockerfile からイメージをビルドし、イメージからコンテナを起動する**。イメージは「クラス」、コンテナは「そのインスタンス」と捉えると、複数のコンテナが同じイメージから生まれる関係が理解しやすい。 ```bash $ docker images REPOSITORY TAG IMAGE ID SIZE nginx latest a1b2c3d4e5f6 187MB $ docker ps CONTAINER ID IMAGE STATUS NAMES 9f8e7d6c5b4a nginx Up 3 seconds web ``` ::: tip **イメージは不変、コンテナは使い捨て** イメージは読み取り専用で変わらない。コンテナはそこに書き込み可能な層を重ねた実行体で、壊れたら捨てて作り直せる。この「使い捨て前提」の発想が、Docker 運用の勘所になる。 ::: ## なぜ Linux ユーザーが Docker を学ぶのか {#why-learn} > **結論**: Docker は現代のインフラ・開発現場の共通語。Linux の素養がある人ほど、学習コストが低く優位に立てる。 Docker はいまや Web アプリのデプロイ、CI/CD、マイクロサービス、クラウド基盤まで、実務のあらゆる場面で使われている。求人票で「Docker 経験」を目にする機会も多い。 そして重要なのは、**Linux の基礎ができている人にとって Docker の学習コストは低い**という点だ。コンテナの正体が Linux プロセスである以上、プロセス管理・ファイルシステム・ネットワークの知識はそのまま応用が効く。ゼロから学ぶ人が「namespace とは」「cgroups とは」でつまずくところを、素通りできる。 Linux 学習の次の一歩として Docker が有力なのは、**新しい世界に移るのではなく、いまの知識を一段拡張する**からだ。 ## Linux コマンド知識がどう活きるか {#linux-skills} > **結論**: ファイルシステム・ネットワーク・プロセス管理の 3 領域が、Docker のトラブルシュートでそのまま武器になる。 具体的に、既存の Linux 知識が Docker のどこで活きるかを見る。 ### ファイルシステム コンテナ内は独立したファイルシステムに見えるが、その実体はホスト上のディレクトリ(レイヤー)だ。`df` / `du` でディスクを追う感覚は、コンテナのディスク肥大化調査にそのまま使える。イメージ・コンテナ・ボリュームがどれだけ容量を食っているかの切り分けは [Dockerディスク使用量の特定](/articles/troubleshooting/docker-disk-usage) で扱っている。 ### ネットワーク コンテナは network namespace で独自の IP・インターフェースを持つ。`ip addr` / `ss` / `ping` でネットワークを診断する知識は、コンテナ間通信やポート公開(ポートフォワーディング)の理解に直結する。「なぜコンテナ内から外部に繋がらないのか」を切り分ける力は、通常の Linux ネットワーク診断の延長線上にある。 ### プロセス管理 コンテナ内では起動したアプリが PID 1 として振る舞う。`ps` / `top` でプロセスを見る、`kill` でシグナルを送る——この操作感は Docker でも変わらない。コンテナが終了する挙動は、[signal(シグナル)とは](/articles/guides/signals-concept) で説明した `SIGTERM` / `SIGKILL` の理解がそのまま効く。 ::: tip **Docker 特有の知識は「薄い上澄み」** 土台の 8 割は Linux の一般知識で、Docker 固有の知識はその上に乗る薄い層にすぎない。だからこそ Linux をやってきた人の伸びが速い。 ::: ## 学習の次のステップ {#next-steps} > **結論**: 概念を掴んだら、手を動かす実践に進む。体系立った動画教材が最短ルート。 ここまでで「Docker が Linux の何を使っているか」「イメージ・コンテナ・Dockerfile の関係」「既存知識がどう活きるか」の見取り図が得られたはずだ。次は実際に `docker run` でコンテナを起動し、Dockerfile を書いてイメージをビルドする**手を動かす段階**になる。 操作手順は本サイトの範囲を超えるため、体系立った教材で学ぶのが効率的だ。概念を理解した状態から入れば、コマンドの一つ一つが「なぜそうなるか」とともに頭に入る。 学ぶ順番の目安は次のとおり。 1. `docker run` でイメージからコンテナを起動する基本 2. Dockerfile を書いて自分のイメージをビルドする 3. `docker compose` で複数コンテナをまとめて扱う 4. ボリューム・ネットワークで永続化と通信を設計する ## 次に読む {#next} - [Linuxの次に学ぶべき技術ロードマップ](/articles/guides/next-steps-after-linux) - [コンテナと仮想マシンの違い](/articles/guides/containers-vs-vms) - [Dockerディスク使用量の特定](/articles/troubleshooting/docker-disk-usage) - [signal(シグナル)とは](/articles/guides/signals-concept) - [DevOps学習ハブ(Docker・Git・AWSのコース比較)](/devops) # エディタの選び方 - vim / nano / emacs / VS Code の使い分け Source: https://penguin-gym-linux.com/articles/guides/editor-comparison-vim-nano-emacs ## エディタって、どれを使えばいいの? {#intro} ファイルを編集しようとして `vim` と打ったら、画面から抜け出せなくなった——。Linux を学び始めた人の多くが一度は通る道だ。世の中には vim・nano・emacs・VS Code と、テキストエディタがたくさんある。でも「結局どれを使えばいいのか」は、なかなか教えてもらえない。 この記事では、代表的な 4 つのエディタ(vim / nano / emacs / VS Code)の違いを、リナとライニー先輩の会話でやさしく整理していく。初心者がどれから始めればいいかも、あわせて示す。なお「エディタ」「テキストエディタ」「editor」は、どれも同じものを指す呼び方だ。 ## この記事でわかること {#toc} - なぜエディタの種類がこんなに多いのか - nano / vim / emacs / VS Code それぞれの得意な場面 - 4 つのエディタの学習コストと使いどころの違い - 初心者がまず選ぶべきエディタと、乗り換えの目安 - 「vim から抜け出せない」を一発で解決する方法 ## 1. なぜエディタの種類が多いの? {#why} > **結論**: エディタは「サーバ上で 1 行だけ直す」用途と「腰を据えて開発する」用途で求められるものが違うため、目的別に複数の選択肢が育ってきた。 ::: dialogue @lina: ライニー先輩、テキストエディタってどうしてこんなに種類があるんですか? 1 個でいいのに… @linny: いい疑問だね。実は「どこで・何を編集するか」で、ちょうどいいエディタが変わるからなんだ。 @lina: 場面によって違う、ということですか? @linny: そう。たとえば SSH(手元のパソコンから、ネットワーク越しに遠くのサーバへログインする仕組み)で入ったサーバの設定を 1 行だけ直したいとき。重たい開発ツールを{起動|きどう}するより、ターミナルの中で開いてすぐ直せるエディタが向いている。 @lina: 反対に、じっくり開発するときは? @linny: 何百ファイルもあるアプリを毎日書くなら、{補完|ほかん}(入力途中の候補を自動で出してくれる機能)や検索が強い高機能なエディタのほうが快適だよね。 @lina: なるほど。用途ごとに「ちょうどいい道具」が育ってきたんですね。 @linny: その通り。だから「どれが一番か」じゃなくて「いつ・どれを使うか」で考えるのが正解なんだ。 ::: ::: tip **CUI エディタと GUI エディタ** - **CUI(ターミナル内)**: nano / vim / emacs。SSH 越しのサーバでもそのまま動く - **GUI(画面アプリ)**: VS Code。マウスやメニューが使え、大規模開発に強い どんな環境でも動くのは CUI 系。手元の開発で快適なのは GUI 系、と覚えておくと整理しやすい。 ::: ## 2. nano はどんなエディタ? {#nano} > **結論**: nano は画面下に操作キーが常に表示される、もっとも初心者にやさしい CUI エディタ。「とりあえず直す」に最適。 ::: dialogue @lina: まずは一番やさしいのから知りたいです! @linny: それなら nano だね。nano の良いところは、画面の下にいつも「次に押すキー」が出ていること。これがあるから、操作を覚えていなくても迷わない。 @lina: カンニングペーパーが最初から付いてるみたいですね。 @linny: まさに。たとえば `^X` と書いてあったら「Ctrl を押しながら X」で終了、という意味だよ。{記号|きごう}の `^` は Ctrl のことなんだ。 ::: ```bash nano memo.txt ``` nano を起動すると、文字をそのまま打ち込んで編集できる。終了したいときは次のキーを押す。 ```output ^X 終了 ^O 保存 ^W 検索 ^K 行の切り取り (^ は Ctrl キーのこと) ``` ::: highlight **nano の基本操作** - **保存**: `Ctrl+O`(Write Out)→ Enter で確定 - **終了**: `Ctrl+X` - **検索**: `Ctrl+W` 矢印キーでカーソルを動かせて、特別な「モード」もない。Windows のメモ帳に一番近い感覚で使える。 ::: ::: tip 迷ったらまず nano。多くの Linux に最初から入っており、サーバで設定ファイルを少し直すだけなら nano で十分こなせる。 ::: ## 3. vim はどんなエディタ? {#vim} > **結論**: vim はキーボードだけで高速に編集できる強力な CUI エディタ。「モード」の概念があり、慣れると最速だが学習コストは高め。 ::: dialogue @lina: 私、前に vim を開いたら抜け出せなくなって焦りました… @linny: あるあるだね。vim には「モード」という考え方があって、これを知らないとパニックになるんだ。 @lina: モード、ですか? @linny: そう。vim は起動直後は「ノーマルモード」で、キーは編集ではなく操作の命令として働く。文字を打ち込むには `i` を押して「挿入モード」に入る必要があるんだ。 @lina: だから普通に打っても変な動きをしたんですね…! @linny: その通り。そして抜け出すには、まず `Esc` でノーマルモードに戻ってから、`:q` で終了する。これさえ知っていれば、もう怖くないよ。 ::: ```bash vim config.txt ``` vim から無事に抜け出すための「最重要 3 コマンド」はこれだ。 ```output i 挿入モードに入る(文字を打てるようになる) Esc ノーマルモードに戻る :wq 保存して終了 :q! 保存せずに強制終了 ``` ::: warning **「抜け出せない!」を解決する手順** 1. まず `Esc` を押す(どのモードにいても、ひとまずノーマルモードに戻る) 2. 保存して終わるなら `:wq` と打って Enter 3. 編集を捨てて終わるなら `:q!` と打って Enter `:` を打つと画面下にコマンドを入力できる。慌てず `Esc` から始めるのがコツ。 ::: ::: warning **`:q!` は編集内容が戻らない** 1. **GUI との差分**: GUI のエディタなら閉じるときに「保存しますか?」と聞かれる。`:q!` は確認なしで編集を捨てる 2. **安全な使い分け**: 残したいときは `:wq`、捨てていいと決めたときだけ `:q!` を使う 3. **安心してほしいこと**: 練習用のファイルで試せば、失っても困らない。まずは `memo.txt` のような使い捨てのファイルで練習しよう ::: ::: tip vim は最初こそ難しいが、ホームポジションから手を離さず高速に編集できるのが最大の魅力。サーバ管理では vim が前提のことも多く、最低限の操作(開く・直す・保存して終わる)は身につけておくと安心。 ::: ## 4. emacs はどんなエディタ? {#emacs} > **結論**: emacs は{拡張|かくちょう}機能で「何でもできる」ことを目指した CUI エディタ。カスタマイズ性は{随一|ずいいち}だが、その分覚えることも多い。 ::: dialogue @lina: emacs は vim とどう違うんですか? @linny: emacs は「vim のような編集モードの切り替えがなく、最初から文字を打てる」のが大きな違い。その代わり、操作は `Ctrl` や `Alt` を組み合わせたキーで指示するんだ。 @lina: vim みたいに「抜けられない」ことはないんですか? @linny: 編集モードの切り替えがない分、文字入力では迷いにくいよ。終了は `Ctrl+X` のあと `Ctrl+C`。保存は `Ctrl+X` のあと `Ctrl+S` だね。 @lina: キーを 2 段階で押すんですね。 @linny: そう、これが emacs 流。そして emacs の本当のすごさは「拡張性」にある。設定を書き換えれば、メール送信やファイル管理まで取り込めるんだ。エディタというより「作業環境」に近いね。 ::: ```bash emacs notes.txt ``` emacs の最低限の操作は次の通り。`C-x` は「Ctrl を押しながら x」を表す。 ```output C-x C-s 保存 C-x C-c 終了 C-g 操作のキャンセル(困ったらこれ) ``` ::: highlight **emacs の立ち位置** - vim のような編集モードの切り替えがなく、起動直後から入力できる - `Ctrl` / `Alt` の組み合わせキーで操作する - 設定言語(Lisp)で機能をいくらでも追加できる 「自分専用の道具に育てたい」人に向く。一方で、少し直すだけなら nano、高速編集なら vim のほうが手軽なことも多い。 ::: ## 5. VS Code はどんなエディタ? {#vscode} > **結論**: VS Code はマウスとメニューで直感的に使える GUI エディタ。補完・検索・拡張機能が豊富で、本格的な開発に最も向く。 ::: dialogue @lina: VS Code はよく名前を聞きます。これは何が違うんですか? @linny: VS Code はこれまでの 3 つと違って、ウィンドウで開く GUI のエディタなんだ。マウスでクリックできるし、見た目もカラフルで分かりやすい。 @lina: じゃあ一番やさしいのは VS Code なんじゃ…? @linny: 手元のパソコンで開発するなら、その通り。補完も賢いし、間違いに色を付けて教えてくれる。拡張機能を入れれば言語ごとの支援も増える。 @lina: いいことだらけに聞こえます! @linny: ただし弱点もある。VS Code は基本的に GUI が必要だから、SSH で入っただけのサーバではそのままでは動かない(リモート拡張という仕組みで補えるけど、ひと手間かかる)。だからこそ nano や vim も知っておく価値があるんだ。 ::: ターミナルから VS Code を開くこともできる。現在のフォルダをまるごと開くには次のコマンドを使う。 ```bash code . ``` ::: tip **VS Code が向く場面** - 手元の PC で、複数ファイルにまたがるアプリ開発をする - 補完・デバッグ・Git 連携などを 1 画面で済ませたい - まだコマンド操作に慣れておらず、マウスで安心して触りたい 開発環境の整え方は [Linux 開発環境構築ガイド](/articles/guides/dev-environment-setup) でも解説している。 ::: ## 6. 結局どれを選べばいい? {#choose} > **結論**: まず nano で「直せる」体験を、次に vim の最低限を、本格開発には VS Code を。emacs は興味が出たら触れば十分。 ::: dialogue @lina: 種類は分かりました。でも、私はまず何から使えばいいですか? @linny: 初心者なら nano から始めるのがおすすめ。操作が画面に出ているから、まず「ファイルを編集して保存する」体験を安心して積める。 @lina: vim や emacs は後回しでいいんですか? @linny: vim は「開く・直す・保存して終わる」の最低限だけ先に覚えておくと安心。サーバで突然 vim が立ち上がっても困らないからね。本格的にコードを書き始めたら VS Code、と段階を踏めば十分だよ。 ::: | エディタ | 種類 | 学習コスト | 向いている場面 | | -------- | ---- | ---------- | ------------------------------------ | | nano | CUI | 低 | サーバで設定を少し直す・初心者の最初 | | vim | CUI | 高 | キーボードだけで高速編集・サーバ管理 | | emacs | CUI | 高 | 自分好みに育てる・統合作業環境 | | VS Code | GUI | 中 | 手元での本格的なアプリ開発 | ::: highlight **迷ったときの選び方** - **とりあえず直したい** → nano - **サーバで vim が開いた** → `Esc` → `:wq` を思い出す - **手元で開発する** → VS Code - **道具を育てたい** → emacs / vim を深掘り エディタは「乗り換え可能」。最初の 1 つにこだわりすぎず、必要になったら次を覚えればいい。 ::: ## リナ、vim から抜け出せない {#first-try} > **結論**: 編集したまま `:q` を打つと vim は終了を断る。保存して終わるなら `:wq`、編集を捨てるなら `:q!`。 ::: dialogue @lina: 教わったとおり `vim` を開いて、`i` で文字を打ってみました。`Esc` を押してから `:q` で終わろうとしたら、赤い文字が出て終われません。 ::: ```output E37: No write since last change (add ! to override) ``` ::: dialogue @lina: 何か壊してしまったんでしょうか…? @linny: 大丈夫、壊れていないよ。これは「まだ保存していないよ」という vim からの確認なんだ。編集した内容をどうするかを、まだ決めていないからね。 @lina: 保存するか捨てるかを、私が選ぶんですね。 @linny: そのとおり。保存して終わるなら `:wq`、編集を捨てて終わるなら `:q!` だよ。`!` は「わかっているから強行して」の合図なんだ。 ::: ```output :wq 保存して終了する :q! 編集を捨てて終了する ``` ::: dialogue @lina: `:q!` で抜けられました。エラーじゃなくて、聞かれていただけなんですね。 @linny: そう。`Esc` を押してから `:` で始める、この順番だけ覚えておけば、もう二度と閉じ込められないよ。 ::: ## ミニ課題 {#exercises} > **結論**: nano で保存して終わる、vim から抜ける、入っているエディタを確かめる。この3つを手で試そう。 ::: dialogue @linny: 学んだことを自分の手で確かめてみよう。3 つの課題を用意したよ。 ::: ### 課題1: nano でファイルを保存して終了しよう {#exercise-1} **やること**: `memo.txt` を nano で開き、1 行書いて保存し、エディタを終了しよう。 :::details ヒント1(方向づけ)を見る 画面の下に出ているキーの一覧を読もう。保存は「書き出す」、終了は「出る」を表すキーだよ。 ::: :::details ヒント2(コマンド名)を見る `nano memo.txt` で開き、`Ctrl+O` → Enter で保存、`Ctrl+X` で終了するよ。 ::: :::details 答えを見る ```bash nano memo.txt ``` ```output ^O 保存(Enter で確定) ^X 終了 (^ は Ctrl キーのこと) ``` ::: ### 課題2: vim を保存せずに終了しよう {#exercise-2} **やること**: `config.txt` を vim で開き、文字を打ってから、その編集を保存せずに終了しよう。 :::details ヒント1(方向づけ)を見る まず今のモードから抜ける。そのあと「終了」に「強行」の記号を足すよ。 ::: :::details ヒント2(コマンド名)を見る `Esc` を押してから `:q!` と打って Enter だよ。 ::: :::details 答えを見る ```bash vim config.txt ``` ```output Esc ノーマルモードに戻る :q! 保存せずに終了する ``` ::: ### 課題3: 入っているエディタを確かめよう {#exercise-3} **やること**: nano と vim が自分の環境に入っているかを調べよう。 :::details ヒント1(方向づけ)を見る コマンドの置き場所を教えてくれるコマンドがある。「どれ?」を英語にした名前だよ。 ::: :::details ヒント2(コマンド名)を見る `which`(コマンドの置き場所を表示するコマンド)に、調べたい名前を並べて渡すよ。 ::: :::details 答えを見る ```bash which nano vim ``` ```output /usr/bin/nano /usr/bin/vim ``` パスが表示されれば、そのエディタは使える。何も出なければ入っていない。 ::: ## 振り返り {#review} > **結論**: nano は画面のヒントで迷わない、vim は `Esc` → `:q!` で抜けられる、VS Code は手元の開発向き。この3点で選べる。 ::: dialogue @lina: つまり「まず nano で編集に慣れて、vim は抜け方だけ先に覚える」でいいんですね。 @linny: そのとおり。サーバで急に vim が開いても、`Esc` → `:q!` を思い出せれば困らないよ。 @lina: 手元でコードを書くようになったら VS Code に進む、という順番も分かりました。 @linny: いいね。エディタは乗り換えできる道具だから、最初の1つで悩みすぎないことが大事だよ。 ::: ## 今日の3行まとめ {#summary} > **結論**: 用途で選ぶ・nano から始める・vim の抜け方を覚える。この3点でエディタ選びは終わる。 1. **エディタは用途別に育った道具** - 「どれが一番か」ではなく「いつ・どれを使うか」で選ぶ 2. **最初は nano、次に vim の最低限** - nano は操作が画面に出ていて迷わない。vim は `Esc` → `:wq` / `:q!` だけ先に覚える 3. **本格的な開発は VS Code** - ただしサーバでは GUI が使えないので、CUI の nano や vim も知っておく ## 次に読む {#next} - [ターミナルの使い方 - コマンドライン入門](/articles/guides/terminal-basics) - [Readline ショートカット入門](/articles/guides/cli-shortcuts-readline) - [Linux 開発環境構築ガイド](/articles/guides/dev-environment-setup) # ファイルパーミッション(file permissions)の考え方 - rwxとowner/group/otherを直感で掴む Source: https://penguin-gym-linux.com/articles/guides/file-permissions-mental-model ## パーミッションの文字列が呪文に見えていませんか? {#intro} `ls -l` を打ったら、行頭に `-rw-r--r--` みたいな文字列が並んでいて、「これ何の{暗号|あんごう}…?」と固まった経験はないだろうか。これが **パーミッション(permission)** ——ファイルへのアクセス権を表す表示だ。「パーミッション」「アクセス権」「{権限|けんげん}」は、どれも同じものを指す別の呼び方になる。 一見すると{呪文|じゅもん}だが、仕組みはシンプルだ。「**誰が**」「**何を**できるか」という2つの軸を並べただけである。この記事では、`owner` / `group` / `other` という3者と、`rwx`(読み・書き・実行)の組み合わせを、リナとライニー先輩の会話で整理していく。読み終わるころには、`-rwxr-xr--` を見て「ああ、こういう権限か」と一目で読めるようになる。 ## この記事でわかること {#toc} - パーミッションが「誰が」「何を」できるかを決める仕組みであること - `owner`(所有者)/ `group`(グループ)/ `other`(その他)の3者の意味 - `r`(読む)/ `w`(書く)/ `x`(実行)の意味 - `ls -l` の先頭10文字の読み方 - ファイルとディレクトリで `rwx` の意味が変わること - `755` `644` のような数字表記との対応 ## 1. そもそもパーミッションは何のため? {#why} > **結論**: パーミッションは「誰がこのファイルに何をしてよいか」を決めるルール。勝手に読まれたり壊されたりするのを防ぐための仕組み。 ::: dialogue @lina: ライニー先輩、`ls -l` したときの行の最初にある `-rw-r--r--` って何ですか? ずっと見ないふりしてました… @linny: それは「パーミッション」だよ。日本語だと「アクセス権」。そのファイルに対して「誰が」「何をしていいか」を決めているんだ。 @lina: 誰が何を、ですか? @linny: そう。Linux は一台のマシンを複数の人が共有して使うことを前提に作られている。だから細かい設定が必要になるんだ。「自分のファイルは他人に勝手に書き換えられたくない」「でも読むだけなら許す」——こうした指定ができる仕組みがパーミッションだよ。 @lina: なるほど。鍵のかかり具合みたいなものですね。 @linny: いいたとえだね。家の玄関は鍵をかけるけれど、郵便受けは誰でも入れられる。ファイルも同じで「どこまで開けておくか」を1つずつ決められるんだ。 ::: ::: tip **パーミッション=「誰が」×「何を」の表** パーミッションは難しそうに見えるが、たった2つの軸でできている。 - **誰が**: `owner` / `group` / `other` の3者 - **何を**: `r`(読む)/ `w`(書く)/ `x`(実行)の3つ この「3 × 3」の{格子|こうし}さえ掴めば、どんな表示も読み解ける。 ::: ## 2. owner / group / other の3者とは? {#who} > **結論**: パーミッションは3者に分けて設定する。`owner`(持ち主本人)/ `group`(同じグループの仲間)/ `other`(それ以外の全員)。 ::: dialogue @lina: まず「誰が」のほうから教えてください。 @linny: 「誰が」は3つのグループに分かれる。`owner`(オーナー)、`group`(グループ)、`other`(その他)だよ。 @lina: 3人の登場人物がいる感じですか? @linny: いいたとえだね。`owner` はそのファイルを作った本人、つまり持ち主。`group` はそのファイルに割り当てられた「グループ」に属する人たち。`other` はそのどちらでもない、残り全員だよ。 @lina: 会社の書類でいうと…? @linny: そう、まさにそんな感じ。`owner` は書類を作った自分、`group` は同じチームのメンバー、`other` は社外の人。同じ書類でも「自分は編集可」「チームは閲覧可」「社外は触れない」と分けたいでしょ。それと同じだよ。 ::: | 区分 | 読み方 | 誰のこと | | ------- | ------------ | ---------------------------------- | | `owner` | オーナー / u | ファイルの持ち主本人 | | `group` | グループ / g | ファイルのグループに属するメンバー | | `other` | アザー / o | 上のどちらでもない残り全員 | ::: highlight **3者は「u / g / o」と略される** `chmod` などのコマンドでは `owner` を `u`(user)、`group` を `g`、`other` を `o` と略す。`a`(all)と書けば3者まとめての意味になる。この略し方は後で `chmod u+x` のように使うので、頭の片隅に置いておこう。 ::: ## 3. rwx(読む・書く・実行する)の意味は? {#rwx} > **結論**: `r` は読む(read)、`w` は書く・変更する(write)、`x` は実行する(execute)。この3つの許可を3者それぞれに与える。 ::: dialogue @lina: 次は「何を」のほうですね。`rwx` って書いてあるやつ。 @linny: そう。3文字それぞれが1つの操作を表している。`r` は read(読む)、`w` は write(書く・変更する)、`x` は execute(実行する)だよ。 @lina: 実行する、っていうのは? @linny: プログラムやスクリプトを「動かす」権限のことだね。たとえば `script.sh` を実行したいなら、そのファイルに `x` が付いていないと動かせない。逆に、ただのメモ帳ファイルに `x` は普通いらない。 @lina: 読む・書く・動かす。3つだけなら覚えられそう。 ::: | 記号 | 英語 | 意味 | | ---- | ------- | --------------------------- | | `r` | read | 中身を読む / 一覧を見る | | `w` | write | 中身を変更する / 追加・削除 | | `x` | execute | 実行する / 中に入る | ::: tip **権限が無いところは `-` で埋める** `rwx` のうち許可されていないものは、その位置が `-`(ハイフン)で表示される。たとえば `r--` なら「読むだけOK、書く・実行はNG」。`rw-` なら「読み書きOK、実行はNG」。`-` は「ここの権限は無い」という空席のしるしだと思えばいい。 ::: ## 4. ls -l の先頭10文字はどう読む? {#ls-l} > **結論**: 先頭の10文字は「1文字(種類)+ owner の rwx + group の rwx + other の rwx」。3文字ずつ区切って読む。 ::: dialogue @lina: いよいよ本題です。`-rw-r--r--` を読めるようになりたい! @linny: よし、分解してみよう。ちなみに `ls -l` の `-l` は「オプション」——コマンドの動きを変える追加の指定だよ。ハイフンで始まるのが目印で、`-l` は long(くわしく)の頭文字なんだ。 @lina: なるほど、詳しく出してほしいときに付けるんですね。 @linny: そう。この10文字は、最初の1文字とそのあとの「3文字 × 3ブロック」でできているんだ。 @lina: 3文字ずつ区切るんですね。 @linny: そう。最初の1文字はファイルの種類(`-` なら普通のファイル、`d` ならディレクトリ)。残りの9文字を3つに割ると、左から `owner` の権限・`group` の権限・`other` の権限になる。さっきの「誰が × 何を」の格子そのものだよ。 ::: `-rw-r--r--` を区切って読むと、こうなる。 ```output - rw- r-- r-- ↑ ↑ ↑ ↑ 種類 owner group other ``` - 先頭 `-`: 普通のファイル(`d` ならディレクトリ) - `rw-`: **owner** は 読み`r` 書き`w` 実行なし`-` - `r--`: **group** は 読み`r` のみ - `r--`: **other** も 読み`r` のみ ![-rw-r--r-- を種類・owner・group・other に区切り、3者と rwx の格子に置き換えた図。owner は r と w が許可、group と other は r のみ許可で、それ以外は不許可](/images/diagrams/rwx-matrix.svg){.article-diagram} {.diagram-figure} 図1: 同じ `-rw-r--r--` を「誰が × 何を」の格子に置き換えたもの。緑の文字が許可されている操作、`-` は許可されていない操作だ。図中の `type` は本文の「種類」にあたる。 {.diagram-caption} つまり「持ち主は読み書きできるが、それ以外の人は読むだけ」という、設定ファイルなどでよく見る権限だ。 実際に確認してみよう。 ```bash ls -l memo.txt ``` ```output -rw-r--r-- 1 lina lina 42 Jun 6 10:00 memo.txt ``` ::: highlight **10文字の読み方テンプレート** 1. **1文字目**: `-`(ファイル)か `d`(ディレクトリ)か 2. **2〜4文字目**: owner(持ち主)の rwx 3. **5〜7文字目**: group(グループ)の rwx 4. **8〜10文字目**: other(その他)の rwx 迷ったら「種類・自分・仲間・他人」と唱えながら3文字ずつ区切ろう。 ::: ::: warning **`ls -l` の3〜4列目もセットで見る** 権限の文字列のあとに出てくる `lina lina` の部分が、その owner 名と group 名だ。`-rw-r--r--` が「誰にとっての権限か」は、この{所有者|しょゆうしゃ}・グループ表示と合わせて初めて意味を持つ。権限文字列だけでなく、隣の名前も一緒に見るクセをつけよう。 ::: ## 5. ファイルとディレクトリで rwx の意味は変わる? {#dir} > **結論**: 変わる。ディレクトリでは `r`=中身を一覧、`w`=ファイルの追加削除、`x`=中に入る(cd できる)を意味する。 ::: dialogue @lina: ディレクトリにも `rwx` が付いてますけど、ファイルと同じ意味ですか? @linny: いいところに気づいたね。実はすこし意味が変わるんだ。ディレクトリは「ファイルを入れる箱」だから、操作の対象が「箱そのもの」になる。 @lina: 箱に対する読む・書く・実行…? @linny: 順番に言うと、`r` は「箱の中のファイル名を一覧できる」、`w` は「箱にファイルを足したり消したりできる」、`x` は「箱の中に入れる(`cd` できる)」だよ。 @lina: `x` が「実行」じゃなくて「中に入る」になるんですね。 @linny: そこがつまずきポイント。ディレクトリに `x` が無いと、たとえ中身があっても `cd` で入れないし、その中のファイルにもアクセスできない。だからディレクトリには `x` が付いていることが多いんだ。 ::: | 権限 | ファイルでは | ディレクトリでは | | ---- | -------------------- | -------------------------------- | | `r` | 中身を読む | 中のファイル名を一覧する | | `w` | 中身を書き換える | ファイルを追加・削除する | | `x` | プログラムを実行する | 中に入る(`cd`)・中身へアクセス | ::: warning **ディレクトリの `x` を消すと「中身があるのに入れない」事故になる** ディレクトリの `w` は `x` とセットで初めて働く。`w` だけ付けても、中に入れないのでファイルは作れない。 ディレクトリから `x` を外すと、`Permission denied` で `cd` できなくなる。「ファイルはあるはずなのに開けない」というトラブルの原因は、ディレクトリ側の `x` 不足であることが多い。詳しくは [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) を参照。 ::: ::: warning **`chmod 777` は「全員に何でも許す」設定なので避ける** エラーを早く消したくて `chmod 777` を打つ例をネットでよく見かける。これは危ない近道だ。 1. **何が起きるか**: owner / group / other の全員に読み・書き・実行のすべてを許す。他人に書き換えられても Linux は止めてくれない 2. **安全な選び方**: ファイルは `644`、スクリプトやディレクトリは `755` から始める。足りなければ `chmod u+x` のように、要る分だけ足す 3. **安心してほしいこと**: 本サイトの仮想ターミナルは学習用だ。あなたのパソコンは壊れない。`777` を打ってしまう失敗も、ここで先に試しておけばいい ::: ## 6. 755 や 644 みたいな数字は何? {#numbers} > **結論**: `rwx` を数字に置き換えた表記。`r=4` `w=2` `x=1` を足して、owner・group・other の順に3桁で書いたもの。 ::: dialogue @lina: `chmod 755` とか `644` っていう数字も見るんですけど、これは何ですか? @linny: それは `rwx` を数字で表したものだよ。同じ権限を、文字じゃなく数字で短く書いているだけ。 @lina: どう変換するんですか? @linny: それぞれの権限に点数を付けるんだ。`r` は4点、`w` は2点、`x` は1点。許可されている権限の点数を足すと、その桁の数字になる。 @lina: たとえば `rwx` なら…? @linny: 4 + 2 + 1 = 7。`rw-` なら 4 + 2 = 6。`r--` なら 4 だね。これを owner・group・other の順に3つ並べると `644` みたいな3桁になる。 ::: | rwx | 計算 | 数字 | | ----- | --------- | ---- | | `rwx` | 4 + 2 + 1 | `7` | | `rw-` | 4 + 2 | `6` | | `r-x` | 4 + 1 | `5` | | `r--` | 4 | `4` | | `---` | 0 | `0` | だから `644` は `rw-r--r--`(owner=6・group=4・other=4)、`755` は `rwxr-xr-x`(owner=7・group=5・other=5)を意味する。 ```bash chmod 644 memo.txt ls -l memo.txt ``` ```output -rw-r--r-- 1 lina lina 42 Jun 6 10:00 memo.txt ``` ::: tip **よく使う2つだけ先に覚えればいい** - `644`(`rw-r--r--`): 普通のファイル。持ち主は読み書き、他は読むだけ - `755`(`rwxr-xr-x`): スクリプトやディレクトリ。持ち主は全部、他は読む+実行(=入る) この2つで日常の大半をカバーできる。数字と記号の細かい使い分けは [chmod の数字表記と記号表記](/articles/tutorials/chmod-numeric-symbolic) で深掘りできる。 ::: ## 7. 手を動かして確かめるには? {#practice} > **結論**: ブラウザ上のターミナルで `ls -l` と `chmod` を実際に打つのが、いちばん速い定着方法。 ::: dialogue @lina: 頭では分かった気がするんですけど、まだ自信がないです… @linny: パーミッションは「見て覚える」より「変えて確かめる」のが早い。`chmod` で権限を変えて、`ls -l` で表示がどう変わるか目で追うと、一度で理解できるよ。 @lina: 自分の環境で壊したら怖くないですか? @linny: 練習用のファイルを作ってやれば大丈夫。それも不安なら、ブラウザで試せる練習場を使うといい。 ::: ```bash ls -l script.sh chmod u+x script.sh ls -l script.sh ``` ```output -rw-r--r-- 1 lina lina 18 Jun 6 10:05 script.sh -rwxr--r-- 1 lina lina 18 Jun 6 10:05 script.sh ``` 1行目と2行目を見比べると、`u+x`(owner に実行権を追加)で `rw-` が `rwx` に変わったのが分かる。 ::: tip [Penguin Gym Linux のターミナル](/terminal) で `ls -l` と `chmod` を何度も打って、表示がどう変化するかを目で確かめよう。「数字を変える → 文字列が変わる」を体感すると、パーミッションは一気に怖くなくなる。 ::: ## リナ、スクリプトが動かない {#first-try} > **結論**: `x` が付いていないファイルは実行できない。`Permission denied` は `ls -l` で `x` があるかを確かめるサイン。 ::: dialogue @linny: 試す前に、練習用のスクリプトを1つ作っておこう。 ::: ```bash echo 'echo "Hello, Penguin Gym"' > hello.sh ``` ::: dialogue @linny: 動かすときは `./hello.sh` と書く。先頭の `./` は「今いる場所のこのファイル」という意味だよ。 @lina: では動かしてみます。`./hello.sh` っと…あれ、断られました。 ::: ```bash ./hello.sh ``` ```output bash: ./hello.sh: Permission denied ``` ::: dialogue @lina: 中身は正しく書いたはずなのに、どうしてでしょう? @linny: 書いた中身ではなく、権限のほうが足りないんだ。`ls -l` で今の権限を見てみよう。 ::: ```bash ls -l hello.sh ``` ```output -rw-r--r-- 1 lina lina 32 Jun 6 10:20 hello.sh ``` ::: dialogue @lina: `rw-` です。あっ、`x` がありません。 @linny: そこが原因。実行するには owner に `x` が要る。`chmod u+x` で足してみよう。 ::: ```bash chmod u+x hello.sh ./hello.sh ``` ```output Hello, Penguin Gym ``` ::: dialogue @lina: 動きました。`Permission denied` を見たら、まず `ls -l` で `x` を確認すればいいんですね。 @linny: そのとおり。エラーは「権限が足りない」というお知らせなんだ。読み方が分かれば、もう怖くないよ。 ::: ## ミニ課題 {#exercises} > **結論**: この記事で出てきた `ls -l` と `chmod` を実際に打って、表示の変化を目で確かめよう。 ::: dialogue @linny: 学んだことを自分の手で確かめてみよう。3 つの課題を用意したよ。まず練習用のファイルを 2 つ作っておこう。 ::: ```bash touch memo.txt echo 'echo "Hello, Penguin Gym"' > script.sh ``` ### 課題1: ファイルの権限を表示しよう {#exercise-1} **やること**: `memo.txt` の権限を、先頭の10文字が見える形で出そう。 :::details ヒント1(方向づけ)を見る 一覧を出すコマンドに、くわしく出すオプションを付ける。long(長い)の頭文字だよ。 ::: :::details ヒント2(コマンド名)を見る `ls -l` を使うよ。 ::: :::details 答えを見る ```bash ls -l memo.txt ``` ```output -rw-r--r-- 1 lina lina 42 Jun 6 10:00 memo.txt ``` ::: ### 課題2: owner に実行の許可を足そう {#exercise-2} **やること**: `script.sh` の owner に `x` を追加して、表示が変わったか確かめよう。 :::details ヒント1(方向づけ)を見る 権限を変えるコマンドに「誰に・何を・足す」を渡す。owner の略記は `u` だったね。 ::: :::details ヒント2(コマンド名)を見る `chmod u+x` を使い、そのあと `ls -l` で確認するよ。 ::: :::details 答えを見る ```bash chmod u+x script.sh ls -l script.sh ``` ```output -rwxr--r-- 1 lina lina 18 Jun 6 10:05 script.sh ``` ::: ### 課題3: 数字表記で 644 に戻そう {#exercise-3} **やること**: `script.sh` を「持ち主は読み書き、他は読むだけ」に戻そう。 :::details ヒント1(方向づけ)を見る `r=4` `w=2` `x=1` を足して3桁で書く。持ち主は 4+2、他の2者は 4 だけだよ。 ::: :::details ヒント2(コマンド名)を見る `chmod 644` を使うよ。 ::: :::details 答えを見る ```bash chmod 644 script.sh ls -l script.sh ``` ```output -rw-r--r-- 1 lina lina 18 Jun 6 10:06 script.sh ``` ::: ## 振り返り {#review} > **結論**: パーミッションは「誰が × 何を」の3×3の格子。`ls -l` の10文字を3文字ずつ区切れば読める。 ::: dialogue @lina: つまり `ls -l` の10文字は、種類・自分・仲間・他人の順に区切って読めばいいんですね。 @linny: そのとおり。そして `x` は、ファイルなら「実行」、ディレクトリなら「中に入る」に変わるところだけ気をつけよう。 @lina: 数字の `644` と `755` も、`r=4` `w=2` `x=1` の足し算だと分かりました。 @linny: それだけ押さえれば、ふだんの大半はカバーできる。あとは `chmod` で変えて `ls -l` で確かめる、を繰り返すだけだよ。 ::: ## 今日の3行まとめ {#summary} > **結論**: 「誰が × 何を」の格子・10文字の区切り方・数字表記の3点で、パーミッションは読み書きできる。 1. **パーミッションは「誰が」×「何を」の3×3の格子** - `owner` / `group` / `other` に `r` / `w` / `x` を当てはめる 2. **`ls -l` の先頭10文字は「種類+owner+group+other」** - 3文字ずつ区切って読む。ディレクトリの `x` は「中に入る」 3. **数字表記は `r=4` `w=2` `x=1` の足し算** - まず `644`(普通のファイル)と `755`(スクリプト・ディレクトリ)を覚える ## 次に読む {#next} - [パーミッションの基本 - chmod / chown 入門](/articles/tutorials/permissions-basics) - [chmod の数字表記と記号表記](/articles/tutorials/chmod-numeric-symbolic) - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) # はじめての方へ - Penguin Gym Linux完全ガイド Source: https://penguin-gym-linux.com/articles/guides/getting-started ## この記事でわかること {#what-you-will-learn} - インストールなしで、ブラウザだけでLinuxコマンドを安全に練習する方法がわかる - 実践ターミナル・ダッシュボード・理解度テスト・学習記事の4つの使い分けがわかる - バッジとレベルを使って、学習を続けるコツが身につく ## はじめに {#intro} > **結論**: インストールなしで使える仮想環境がある。だからLinuxコマンドを安全に練習できる。 :::dialogue @lina: ライニー先輩、Linuxコマンドを勉強したいんですけど、どこから始めればいいかわからなくて... @linny: 大丈夫だよ、リナ。このサイト「Penguin Gym Linux」を使えば、ブラウザだけでLinuxコマンドの練習ができるんだ。 @lina: えっ、何もインストールしなくていいんですか? @linny: その通り。安全な仮想環境だから、失敗してもパソコンが壊れる心配もないよ。一緒にサイトの使い方を見ていこう。 ::: ## 1. このサイトについて {#what-is-this} > **結論**: ブラウザだけで始められて、失敗してもパソコンは壊れない。それが5つの特徴の中心にある。 :::dialogue @linny: まず、Penguin Gym Linuxがどんなサイトか説明するね。ポイントは5つだよ。 ::: :::highlight ### Penguin Gym Linuxの特徴 {#features-list} 1. **インストール不要** - ブラウザだけで学習開始 2. **安全な環境** - 仮想環境だからシステムを壊す心配なし 3. **段階的学習** - 初級から上級まで体系的に学べる 4. **進捗管理** - 学習の成果を可視化 5. **ゲーミフィケーション** - バッジ獲得でモチベーション維持 ::: :::dialogue @lina: バッジがもらえるんですか!それは楽しそうですね。 @linny: そうだよ。学習を続けるとバッジがどんどん増えていくから、やる気が続くんだ。 ::: ## 2. 4つの学習機能 {#features} > **結論**: 練習は実践ターミナル、進み具合はダッシュボード、確認は理解度テスト、理屈は学習記事で学ぶ。 :::dialogue @linny: このサイトには4つの主要機能があるよ。それぞれの役割を整理しよう。 ::: ### 実践ターミナル {#terminal} 課題を順番にこなしながら学べる**メイン機能**。基礎から上級まで、ひとつずつ進められる。 [実践ターミナルで学習開始](/terminal) ### ダッシュボード {#dashboard} 進み具合を絵やグラフで見られる。もらったバッジや、分野ごとの終わった割合も一目でわかる。 [ダッシュボードを見る](/dashboard) ### 理解度テスト {#quiz} **5段階の難易度**でLinuxコマンドの知識をためす。苦手な部分が見つかり、覚えたことも定着する。 [理解度テストに挑戦](/quiz) ### 学習記事 {#articles} 「なぜそうなるのか」から実際の使い方まで、じっくり読んで学べる。 [学習ガイドを読む](/articles/guides/) :::dialogue @lina: 機能がたくさんありますね。どれから始めればいいんですか? @linny: まずは**実践ターミナル**から始めるのがおすすめだよ。課題を順番にクリアしていくと、自然とスキルが身につくんだ。 ::: ## 3. 学習の始め方 {#how-to-start} > **結論**: 実践ターミナルを開く、課題を選ぶ、コマンドを打つ。この3ステップで学習を始められる。 :::dialogue @linny: 実際に学習を始める手順を説明するね。3つのステップで進めていこう。 ::: ### ステップ1: 実践ターミナルにアクセス {#step1} :::dialogue @linny: まず[実践ターミナル](/terminal)を開こう。画面は大きく2つのエリアに分かれているよ。 ::: - **左側(サイドバー)**: 課題の一覧。カテゴリごとに整理されている - **右側(ターミナル)**: コマンドを入力する黒い画面 :::dialogue @lina: スマホでも使えますか? @linny: もちろん。モバイル版では下部のタブで画面を切り替える形式になるよ。どのデバイスでも快適に学習できるんだ。 ::: ### ステップ2: 課題を選択 {#step2} :::dialogue @linny: サイドバーから課題を選ぼう。最初は**「基本コマンド」カテゴリの最初の課題**から始めるといいよ。 ::: 課題を選ぶと、次の3つが表示される。 - **課題の説明**: 何をすればいいか - **ヒント**: 困ったときのヒント - **期待される動作**: 正解の条件 ### ステップ3: コマンドを入力して実行 {#step3} :::dialogue @linny: ターミナルにコマンドを入力してEnterキーを押そう。例えば、最初の課題で`pwd`と入力するとこうなるよ。 ::: ```bash $ pwd /home/user ``` :::dialogue @lina: あ、今いる場所が表示されました! @linny: 正解だね。課題をクリアすると「完了」マークがついて、次の課題に進めるようになるよ。 ::: ### 最初に覚える3つのコマンド {#three-commands} :::dialogue @linny: どの課題を始める前にも、この3つのコマンドは覚えておこう。 ::: #### `pwd` - 現在のディレクトリを表示 「Print Working Directory」の略。今いる場所を確認する。 #### `ls` - ファイル一覧を表示 「List」の略。今いる場所にあるファイルやフォルダを表示。 #### `cd ディレクトリ名` - ディレクトリを移動 「Change Directory」の略。指定した場所に移動する。 :::dialogue @lina: `pwd`で今どこにいるか確認して、`ls`で中身を見て、`cd`で移動するんですね。 @linny: その通り!この3つはLinuxの基本中の基本だよ。 ::: ## 4. ゲーミフィケーション {#gamification} > **結論**: バッジ・レベル・学習の記録という3つのしかけで、やる気が続くようになっている。 :::dialogue @lina: さっき「バッジがもらえる」って言ってましたけど、詳しく教えてください。 @linny: いいね。ゲーミフィケーション機能は3つあるよ。 ::: ### バッジシステム {#badge-system} 学習の成果に応じてバッジがもらえる。 - **カテゴリマスター**: 特定カテゴリの全課題完了 - **連続学習**: 7日連続でレッスン完了 - **高速完了**: 課題を素早く完了 ### レベルシステム {#level-system} 終わらせた課題の数に応じてレベルが上がる。 - **初級**: 5課題完了 - **中級**: 15課題完了 - **上級**: 30課題完了 ### 学習統計 {#learning-stats} [ダッシュボード](/dashboard)で確認できる項目: - 学習開始からの経過日数 - カテゴリ別完了率 - 最長連続学習日数 :::dialogue @lina: バッジを集めるのが楽しみになってきました。 @linny: 小さな達成を積み重ねることで、自然と学習が続くようになるよ。 ::: ## 5. 効果的な学習のコツ {#tips} > **結論**: 毎日15分続ける、メモを取る、繰り返し打つ、エラーを怖がらない。この4つで上達が速くなる。 :::dialogue @lina: 上手に学習を進めるコツはありますか? @linny: もちろん。4つのコツを教えるね。 ::: ### 毎日少しずつ {#daily-practice} 1日15分でもいい。毎日さわることが大切。続けた分だけ力になる。 ### メモを取る {#take-notes} よく使うコマンドや便利なオプションは、その場でメモしよう。自分だけのコマンド集を作るのもおすすめ。 ### 繰り返し練習 {#repeat-practice} 同じコマンドを何度も打とう。手が自然に動くようになるまで繰り返すのがコツ。 ### エラーを恐れない {#dont-fear-errors} エラーは学ぶチャンス。仮想環境だから、何度失敗しても大丈夫。 ### よくあるつまずきポイント {#common-issues} :::dialogue @linny: 初心者がよくつまずくポイントも知っておこう。 ::: :::warning **「command not found」と表示される** - **原因**: タイプミスやスペルミス - **対処**: コマンド名を1文字ずつ見直す。Linuxは大文字と小文字を別のものとして扱う。 ::: :::warning **「No such file or directory」と表示される** - **原因**: 指定したファイルやディレクトリが存在しない - **対処**: まず `ls` で名前があるか確かめてから操作する。 ::: :::tip **パスの指定方法がわからない** ポイント: - `.` は現在のディレクトリ - `..` は親ディレクトリ - `/` で始まるのは絶対パス ::: :::dialogue @lina: エラーが出ても、原因がわかれば対処できそうですね。 @linny: その通り。エラーメッセージをよく読むことが大切だよ。 ::: ## リナ、大文字で打ってしまう {#first-try} > **結論**: Linuxは大文字と小文字を別の文字として扱う。`PWD` は `pwd` とは違うコマンド名になる。 :::dialogue @lina: さっそく打ってみます。`PWD` っと…あれ、「command not found」って出ました。 ::: ```bash PWD ``` ```output bash: PWD: command not found ``` :::dialogue @lina: つづりは合っているはずなのに、どうしてでしょう? @linny: 惜しい! Linuxは大文字と小文字を別のものとして扱うんだ。コマンド名は小文字の `pwd` だよ。 ::: ```bash pwd ``` ```output /home/user ``` :::dialogue @lina: 小文字にしたら出ました。1文字違うだけで別のコマンド扱いなんですね。 @linny: そのとおり。「command not found」を見たら、まず大文字小文字とつづりを見直そう。仮想環境だから、何度打ち間違えても大丈夫だよ。 ::: ## ミニ課題 {#exercises} > **結論**: 実践ターミナルで `pwd` と `ls` を打ち、ダッシュボードで進み具合を確かめよう。 :::dialogue @linny: では、実際にサイトを使って練習してみよう。3つの課題を用意したよ。 ::: ### 課題1: 今いる場所を表示しよう {#exercise-1} **やること**: [実践ターミナル](/terminal)を開いて、今いる場所を表示しよう。 :::details ヒント1(方向づけ)を見る 「今、自分がどこにいるか」を教えてくれるコマンドを思い出そう。 ::: :::details ヒント2(コマンド名)を見る `pwd` を使うよ。Print Working Directory の頭文字だね。 ::: :::details 答えを見る ```bash pwd ``` ```output /home/user ``` ::: ### 課題2: 今いる場所の中身を見よう {#exercise-2} **やること**: 今いる場所にあるファイルやフォルダを一覧で表示しよう。 :::details ヒント1(方向づけ)を見る 「ここに何があるか」を並べて見せてくれるコマンドを探そう。2文字だよ。 ::: :::details ヒント2(コマンド名)を見る `ls` を使うよ。list(一覧)の略だね。 ::: :::details 答えを見る ```bash ls ``` ```output Documents Downloads Pictures ``` ::: ### 課題3: 自分の進み具合を確かめよう {#exercise-3} **やること**: [ダッシュボード](/dashboard)を開いて、今の進み具合を見よう。 :::details ヒント1(方向づけ)を見る コマンドではなくページを開く課題だよ。学習の記録がまとまっている場所はどこだったかな。 ::: :::details ヒント2(ページ名)を見る [ダッシュボード](/dashboard)を開くよ。もらったバッジと分野ごとの割合が見られる。 ::: :::details 答えを見る [ダッシュボード](/dashboard)を開くと、次の3つが確認できる。 - 学習を始めてからの日数 - 分野ごとの終わった割合 - 続けて学習できた最長の日数 ::: ## 振り返り {#review} > **結論**: 打って動かした経験がいちばん残る。その積み上がりが次のやる気になる。 :::dialogue @lina: つまり、このサイトでは「実践ターミナルで練習 → ダッシュボードで進捗確認 → 理解度テストで復習」という流れで学習すればいいんですね。 @linny: その通り!記事を読んで概念を理解してから実践するのも効果的だよ。自分に合った方法で進めていこう。 @lina: バッジを集めながら頑張ります! @linny: いいね。わからないことがあったら、いつでも聞いてね。 ::: ## 今日の3行まとめ {#summary} > **結論**: 実践ターミナル・`pwd`/`ls`/`cd`・バッジの3点を押さえれば、迷わず始められる。 1. **実践ターミナル**で課題を順番にクリアしていくのが学習の基本 2. **pwd・ls・cd**の3つがLinuxの最初に覚えるコマンド 3. **バッジとレベル**のゲーミフィケーションで楽しく継続 ## 次のステップ {#next-steps} > **結論**: 次は実践ターミナルの `pwd` レッスンから、すぐに手を動かせる。 - [実践ターミナルで学習開始](/terminal?lesson=basic-pwd) - [ターミナル操作の基本を学ぶ](/articles/guides/terminal-basics) # Linuxの起動の流れ入門 - BIOS/UEFIからログインまで Source: https://penguin-gym-linux.com/articles/guides/how-linux-boots ## この記事で解決できること {#intro} - 電源投入から login プロンプトまで、**何がどの順で動くか** を 1 本の線で追える - 起動が止まったとき「**どの段階で**詰まったか」を切り分ける軸が手に入る - `systemd-analyze` / `journalctl -b` で **自分のマシンの起動** を観察できる ::: tip **結論(起動の 5 段階)** 1. **ファームウェア(BIOS / UEFI)** … ハードを初期化し、起動デバイスを探す 2. **ブートローダ(GRUB)** … カーネルと initramfs をメモリに読み込む 3. **カーネル** … ハードを掌握し initramfs を一時的な root として展開 4. **init(systemd, PID 1)** … 本物の root に切替え、サービスを並行起動 5. **ログイン** … getty / display manager がプロンプトを出す ::: ::: warning **前提(対象環境)** - 一般的な x86_64 Linux(UEFI + GRUB 2 + systemd 構成) - BIOS 専用機・他のブートローダ(systemd-boot 等)・他の init(OpenRC 等)では一部の名称が変わる ::: ## Linux の起動は何段階あるのか? {#overview} > **結論**: 大きく「ファームウェア → ブートローダ → カーネル → init → ログイン」の 5 段階。各段階は次の 1 段だけを起動する責務の連鎖であり、止まった位置を特定すれば原因の範囲が一気に狭まる。 起動(boot)は魔法ではなく、**バトンを渡すリレー** だ。電源が入った瞬間は CPU しか信用できる状態がなく、そこから少しずつ「信用できる範囲」を広げていく。各段階は次の段階をメモリに載せて制御を渡し、自分の役目を終える。 ```output 電源ON │ ▼ ① ファームウェア(BIOS / UEFI) ハード初期化・起動デバイス選択 ▼ ② ブートローダ(GRUB) kernel + initramfs をロード ▼ ③ カーネル デバイス掌握・initramfs 展開 ▼ ④ init(systemd / PID 1) サービス起動・ターゲット到達 ▼ ⑤ ログイン(getty / DM) プロンプト表示 ユーザー操作可能 ``` この「どこまで進んだか」が、トラブル切り分けの最重要情報になる。画面に GRUB メニューが出れば①②は成功、カーネルのメッセージが流れれば③に到達、と **見える症状で段階を逆算** できる。 ## ① BIOS と UEFI は何をするのか? {#firmware} > **結論**: ファームウェア(BIOS / UEFI)はハードウェアを初期化し、起動可能なデバイスを探して最初のプログラムをメモリに読み込む。UEFI は EFI システムパーティション上の `.efi` を直接起動できる点が BIOS と異なる。 電源投入後、最初に動くのはマザーボード上の **ファームウェア** だ。役割は 2 つ。 - **POST(Power-On Self-Test)**: CPU・メモリ・ストレージなど基本ハードの存在と健全性を確認する - **起動デバイスの選択**: 設定された順序でディスク・USB・ネットワークを調べ、起動可能なものを見つける ここで **BIOS** と **UEFI** の違いが出る。 | 観点 | BIOS(レガシー) | UEFI(現行) | | ------------ | ------------------------- | ---------------------------------- | | 起動方式 | MBR 先頭 446 バイトを実行 | ESP 上の `.efi` 実行ファイルを起動 | | ディスク上限 | 2 TB(MBR) | 9.4 ZB(GPT) | | セキュア起動 | なし | Secure Boot に対応 | | 設定保持 | CMOS | NVRAM(`efibootmgr` で操作) | UEFI 機では **ESP(EFI System Partition)** という FAT32 の小さな領域に、ブートローダの実行ファイル(例: `/EFI/ubuntu/grubx64.efi`)が置かれる。UEFI はそれを直接起動するため、BIOS 時代の「MBR にコードを埋め込む」手品が不要になった。 ```bash # UEFI で起動したかの確認(このディレクトリが存在すれば UEFI ブート) $ ls /sys/firmware/efi ``` ::: tip `/sys/firmware/efi` が **存在すれば UEFI**、無ければ BIOS(レガシー)ブート。デュアルブートやインストールトラブルでは、まずこの一点で土俵を確定させると無駄な切り分けが減る。 ::: ## ② ブートローダ(GRUB)の役割は? {#bootloader} > **結論**: ブートローダはカーネル本体と initramfs をディスクからメモリに読み込み、起動パラメータを付けてカーネルへ制御を渡す。GRUB はメニュー表示・カーネル選択・パラメータ編集もできる。 ファームウェアが起動するのが **ブートローダ** で、Linux では **GRUB 2** が最も一般的だ。GRUB の仕事は次の通り。 - 複数のカーネル / OS から **どれを起動するか選ぶ**(あの黒い選択メニュー) - 選ばれた **カーネル本体(`vmlinuz`)と initramfs(`initrd.img`)をメモリにロード** - **カーネルパラメータ(cmdline)** を付与してカーネルへジャンプ 設定は `/boot/grub/grub.cfg` に生成されるが、これは自動生成ファイルなので **直接編集しない**。元になるのは `/etc/default/grub` とテンプレート群だ。 ```bash # 起動に使われたカーネルとパラメータを確認 $ cat /proc/cmdline ``` ```output BOOT_IMAGE=/boot/vmlinuz-6.8.0-31-generic root=UUID=... ro quiet splash ``` `root=UUID=...` が「本物の root ファイルシステムはどれか」、`ro` が「最初は読み取り専用でマウント」を意味する。トラブル時に GRUB メニューで `e` を押すと、この行を **その場で編集**(例: `quiet splash` を消して `single` を足す)してレスキュー起動できる。 ::: warning GRUB メニューが出る=①②は成功。ここから先に進まない(カーネルメッセージが出ない)場合は、カーネルや initramfs の破損、`root=` の指定ミスを疑う。逆に GRUB メニューすら出なければ、原因はファームウェア設定や ESP / ブートローダ自体にある。 ::: ## ③ カーネルと initramfs は何をしているのか? {#kernel} > **結論**: カーネルはメモリ・CPU・デバイスを掌握し、まず initramfs を一時的な root として展開する。initramfs には本物の root をマウントするための最小限のドライバが入っており、準備ができたら本物の root へ切り替える。 GRUB から制御を受け取った **カーネル(kernel)** は、自身を展開してハードウェアの管理を開始する。メモリ管理・プロセススケジューラを立ち上げ、CPU の全コアを有効化し、ドライバを初期化していく。 ここで鍵になるのが **initramfs(initial RAM filesystem)** だ。なぜ必要か——本物の root ファイルシステムが、暗号化(LUKS)・LVM・RAID・特殊なディスクドライバの上にある場合、カーネルは「それをマウントするためのドライバ」を先に必要とする。卵が先か鶏が先かの問題だ。 initramfs はこれを解く **小さな一時 root**。GRUB がカーネルと一緒にメモリへ載せたこのアーカイブを、カーネルは RAM 上に展開し、その中の `init` スクリプトを実行する。その役目は概ね次の通り。 1. 必要なカーネルモジュール(ストレージ・暗号化等)をロード 2. LVM の有効化・LUKS の復号など、**本物の root を見えるようにする** 準備 3. 本物の root をマウントし、`switch_root` でそちらへ **根を切り替える** ```bash # カーネルが認識・初期化した過程はリングバッファに残る $ dmesg | head ``` initramfs の役目はここで終わり、以降は本物の root 上の `/sbin/init`(= systemd)が主役になる。この「一時 root → 本物の root への switch」が起動中盤の山場であり、暗号化ディスクのパスフレーズ入力もこの段階で発生する。 ## ④ init(systemd / PID 1)は何を起動するのか? {#init} > **結論**: 本物の root 上で最初に起動するプロセスが init で、現在の主流は systemd(PID 1)。systemd は依存関係を解決しながらサービス(ユニット)を並行起動し、目標状態(ターゲット)への到達を目指す。 `switch_root` の後、カーネルが本物の root から起動する最初のユーザープロセスが **PID 1 の init** だ。現代の多くのディストリでは **systemd** がこれを担う。systemd の特徴は、サービスを **依存関係グラフ** として捉え、依存のないものを **並行起動** する点にある(旧来の SysVinit が起動スクリプトを直列実行していたのと対照的)。 systemd が管理する起動単位を **ユニット(unit)** と呼ぶ。 - `*.service` … デーモン / プログラム(例: `sshd.service`) - `*.mount` … ファイルシステムのマウント - `*.socket` … ソケットの待ち受け - `*.target` … 複数ユニットをまとめた **到達目標**(旧ランレベルに相当) 起動のゴールは **既定ターゲット** への到達だ。サーバなら `multi-user.target`(CUI)、デスクトップなら `graphical.target`(GUI)が一般的。 ```bash # 既定ターゲットの確認(旧ランレベルの後継) $ systemctl get-default ``` ```output graphical.target ``` | 旧ランレベル | systemd ターゲット | 意味 | | ------------ | ------------------- | ------------------------ | | 0 | `poweroff.target` | シャットダウン | | 1 | `rescue.target` | シングルユーザー(復旧) | | 3 | `multi-user.target` | CUI マルチユーザー | | 5 | `graphical.target` | GUI | | 6 | `reboot.target` | 再起動 | systemd は `default.target` から依存を辿り、必要なユニットを起動していく。失敗したユニットがあっても他は進むため、「**起動はしたが特定サービスだけ落ちている**」という切り分けが可能になる。 ::: tip systemd は各サービスを signal で制御する。停止要求は `SIGTERM`、応答しなければ `SIGKILL` という流れは、graceful shutdown の理屈そのもの。詳しくは [signal(シグナル)とは](/articles/guides/signals-concept) を参照。 ::: ## ⑤ ログインプロンプトはどこから出るのか? {#login} > **結論**: ターゲット到達の最終段で、CUI なら getty が仮想端末に login プロンプトを、GUI なら display manager(gdm 等)がログイン画面を表示する。ここまで来れば起動は完了。 ターゲットの到達に伴い、ユーザーが操作する入口が用意される。 - **CUI(`multi-user.target`)**: `getty`(`agetty`)が各仮想端末(tty1 等)で `login:` プロンプトを表示する。`Ctrl+Alt+F2` などで切り替わる素のテキスト端末がこれ。 - **GUI(`graphical.target`)**: **display manager**(`gdm` / `sddm` / `lightdm` 等)がグラフィカルなログイン画面を出し、認証後にデスクトップセッションを起動する。 認証は **PAM(Pluggable Authentication Modules)** が担い、成功するとシェル(CUI)またはデスクトップ環境(GUI)が起動して、ようやく「使える状態」になる。ここが起動リレーのゴールテープだ。 ## 自分のマシンの起動を観察するには? {#observe} > **結論**: `systemd-analyze` で総所要時間と遅いユニットを、`journalctl -b` で今回ブートの全ログを確認できる。「遅い」「特定サービスが起動しない」は、この 2 つでほぼ切り分けられる。 理屈を自分の環境で確かめる。systemd は起動の各段階を計測・記録しているので、可視化は簡単だ。 ```bash # 起動全体の所要時間(ファームウェア → カーネル → ユーザー空間) $ systemd-analyze ``` ```output Startup finished in 4.123s (firmware) + 2.001s (loader) + 1.892s (kernel) + 6.540s (userspace) = 14.557s ``` `firmware` / `loader` / `kernel` / `userspace` の内訳が、まさに本記事の①〜④に対応している。どこが遅いかが一目で分かる。 ```bash # 起動を遅くしているユニットを上から順に $ systemd-analyze blame ``` ```bash # 今回のブートのログだけを読む(-b = current boot) $ journalctl -b ``` ::: warning `systemd-analyze blame` の数字は **並行起動** のため単純な合計にはならない(同時に動いた分は重複計上される)。順序依存の遅延は `systemd-analyze critical-chain` の方が実態に近い。 ::: ## まとめ:起動段階と切り分けの早見表 {#summary} > **結論**: 「画面に何が出たか」で到達段階が分かる。GRUB メニュー=①②成功、カーネルログ=③到達、emergency プロンプト=④で root マウント失敗、login=完走。症状を段階に翻訳できれば原因の範囲が定まる。 | 段階 | 担当 | 成功の見え方 | 詰まったときの主因 | | ---------------- | ------------------ | ---------------------- | ------------------------------- | | ① ファームウェア | BIOS / UEFI | メーカーロゴ | 起動順序・ESP・ハード故障 | | ② ブートローダ | GRUB | GRUB メニュー | grub.cfg・カーネル/initrd 破損 | | ③ カーネル | kernel + initramfs | カーネルログが流れる | ドライバ欠如・`root=` 誤り | | ④ init | systemd (PID 1) | サービス起動メッセージ | root マウント失敗・ユニット異常 | | ⑤ ログイン | getty / DM | login プロンプト | DM 設定・PAM・GPU ドライバ | 起動の流れを 5 段階の責務として持っておくと、「起動しない」という漠然とした事象を **どの段階の問題か** に翻訳できる。あとは該当段階のログ(`dmesg` / `journalctl -b`)を読めば、原因にまっすぐ近づける。 ## 次に読む {#next} - [systemctl の基本 - サービスの起動・停止・自動起動](/articles/tutorials/systemctl-basics) - [journalctl でログを読む](/articles/tutorials/journalctl-basics) - [signal(シグナル)とは - SIGTERM / SIGKILL / SIGHUP の違い](/articles/guides/signals-concept) - [カーネルパニックで起動しないときの対処](/articles/troubleshooting/kernel-panic-boot-failure) # ディレクトリ構造の全体像 - Linuxファイルシステムを理解する Source: https://penguin-gym-linux.com/articles/guides/linux-directory-structure 「`cd /etc` とか `/var/log` とか、いろんな場所が出てくる。これ全部、何なの?」Linuxを触り始めると必ずぶつかる疑問です。この記事では、ライニー先輩とリナの対話を通じて、**Linuxのディレクトリ構造の地図**を一緒に頭に入れていきましょう。 ## この記事でわかること {#what-you-will-learn} - Linux のファイルシステムが `/`(ルート)から枝分かれする1本の木構造であること - `/etc` `/var` `/home` `/bin` など主要ディレクトリの役割 - FHS(ファイルシステム階層標準)という世界共通の決まり - `/proc` `/dev` `/sys` のような仮想ディレクトリの正体 - 迷子になったときの現在地確認のコツ ## 1. 導入:ファイルシステムって何? {#intro} :::dialogue @lina: ライニー先輩、Linuxには `Documents` フォルダが見当たりません。`/etc` や `/usr` など、知らない場所ばかりで迷子になります... @linny: それはよく分かるよ。Windowsの「Cドライブ」みたいな感覚で探すと混乱するんだ。Linuxには**1本の大きな木**のような構造がある。まずその全体像をつかもう。 ::: :::tip **ファイルシステムとは** Linuxでは、すべてのファイルとフォルダ(ディレクトリ)が**たった1つの出発点**から{枝分かれ|えだわかれ}する「木」のような構造になっています。この出発点を**ルートディレクトリ**と呼び、`/`(スラッシュ1文字)で表します。 ::: :::dialogue @lina: Windowsだと「Cドライブ」「Dドライブ」に分かれていました。Linuxは違うんですか? @linny: そう、そこが大きな違い。Linuxにはドライブレターがないんだ。USBメモリも外付けディスクも、ぜんぶ `/` から始まる1本の木の「どこかの枝」としてつながる。これを覚えておくと、あとで楽になるよ。 ::: ### なぜ構造を知ると得なのか - **設定ファイルの場所**が予想できる(だいたい `/etc` の中) - **ログの場所**が分かる(だいたい `/var/log`) - エラーメッセージのパスを見て「これはシステム領域だな」と**当たりがつく** - 「どこに何を置けばいいか」で迷わなくなる ## 2. すべての出発点「/」(ルート) {#root} > **結論**: `/`(ルート)はすべてのファイルの先祖にあたる最上位ディレクトリ。ここから全体が枝分かれする。 :::dialogue @lina: さっき出てきた `/` って、それ自体がフォルダなんですか? @linny: その通り。`/` は**いちばん上のディレクトリ**で、すべてのファイルの先祖にあたる場所だよ。試しに中身を見てみよう。 ::: ```bash $ ls / bin dev home lib mnt proc run srv tmp var boot etc lib64 media opt root sbin sys usr ``` :::dialogue @lina: わー、たくさんありますね。これ全部覚えるんですか...? @linny: 安心して。全部を暗記する必要はないよ。最初に押さえるのは**5〜6個だけ**。残りは「そういう箱があるんだ」くらいでOK。この並び順には実はルールがあってね、**FHS(Filesystem Hierarchy Standard)**っていう世界共通の決まりに従っているんだ。 ::: :::tip **FHS(ファイルシステム{階層|かいそう}標準)** どのディレクトリに何を置くかを決めた標準仕様です。これがあるおかげで、UbuntuでもCentOSでも「設定は `/etc`」「ログは `/var/log`」と**ほぼ同じ場所**にあります。 なお、UbuntuやCentOSのような「Linuxの配布セット」を**ディストリビューション**(略して「ディストリ」)と呼びます。ディストリが違っても迷わないのは、この標準のおかげです。 ::: ## 3. 主要ディレクトリの地図 {#map} > **結論**: 主要ディレクトリは「使う人のもの」「システムのもの」「動いて変わるもの」の3グループで覚えると整理しやすい。 :::dialogue @linny: まずは全体マップを見てみよう。これが頭に入れば、もう迷子にはならないよ。 ::: ``` / ← すべての出発点(ルート) ├── bin ← 基本コマンド(ls, cp など) ├── boot ← 起動に必要なファイル ├── dev ← デバイス(ディスク、USBなど) ├── etc ← システム全体の設定ファイル ├── home ← ユーザーごとの個人フォルダ │ └── yamada ← yamadaさんの「自分の部屋」 ├── lib ← 共有ライブラリ(プログラムの部品) ├── opt ← 追加で入れたソフト ├── proc ← プロセス(実行中のプログラム)の情報(仮想) ├── root ← 管理者(root)の個人フォルダ ├── tmp ← 一時ファイル(再起動で消える) ├── usr ← アプリやコマンド本体の置き場 └── var ← 変化するデータ(ログなど) ``` :::dialogue @lina: `home` と `root` って似ています。別物なんですか? @linny: いいところに気づいたね。`/home/ユーザー名` は**一般ユーザーの部屋**だよ。`/root` は**管理者専用の部屋**なんだ。 @lina: さっき出てきたルートディレクトリ `/` とも似ていて、ややこしいです... @linny: そこは{混同|こんどう}しやすいところだね。表に並べて、今すぐ整理しよう。 ::: ### 「ルート」と付く3つを区別する | 書き方 | 呼び方 | 何を指すか | | ------- | ------------------ | ---------------------------------------- | | `/` | ルートディレクトリ | いちばん上のディレクトリ。すべての出発点 | | `/root` | 管理者のホーム | 管理者(rootユーザー)の個人フォルダ | | `root` | rootユーザー | 管理者アカウントの名前。**人**を指す言葉 | :::tip 同じ「root」という言葉でも、指すものが3つに分かれます。**`/` は場所のてっぺん**、**`/root` は管理者の部屋**、**root は管理者という人の名前**。この3つを分けて覚えれば混乱しません。 ::: ### 3つのグループで覚える | グループ | ディレクトリ | ひとことで言うと | | ---------------- | ------------------------------- | ------------------------ | | 使う人のもの | `/home`, `/root` | 個人のファイル置き場 | | システムのもの | `/etc`, `/bin`, `/usr`, `/lib` | 設定・プログラム本体 | | 動いて変わるもの | `/var`, `/tmp`, `/proc`, `/dev` | ログ・一時・実行中の情報 | ## 4. よく使うディレクトリを詳しく {#details} > **結論**: 自分のものは `/home`、設定は `/etc`、ログは `/var/log`、コマンド本体は `/usr/bin`、一時置きは `/tmp`。 :::dialogue @lina: それぞれ、もう少し具体的に知りたいです。 @linny: よく顔を出すものから順番に見ていこう。「いつ使うか」をセットで覚えると忘れにくいよ。 ::: ### /home - あなたの作業場所 ユーザーごとの個人ディレクトリです。`yamada` というユーザーなら `/home/yamada` が割り当てられ、ターミナルでは `~`(チルダ)で表せます。 ```bash $ cd ~ $ pwd /home/yamada ``` :::tip 普段ファイルを作ったり編集したりするのは、ほぼここの中です。**自分のものは `/home` の下**、と覚えておけば安全です。 ::: ### /etc - 設定ファイルの倉庫 システム全体の設定ファイルが集まる場所です。読み方は「エトセ」や「エトシー」。 ```bash $ ls /etc hostname hosts passwd ssh network ... ``` :::dialogue @lina: `passwd` ってパスワードが書いてあるんですか!? @linny: 名前は紛らわしい。でも `/etc/passwd` に書かれているのはユーザーの一覧情報だよ。パスワードそのものは入っていない(実際の暗号化パスワードは `/etc/shadow` にある)。`/etc` の中身は基本「読むだけ」と覚えておこう。編集には管理者{権限|けんげん}が必要なものが多いよ。 ::: ### /var - 変化し続けるデータ 「variable(変わる)」の略。サイズが増えたり減ったりするデータが入ります。初心者がいちばんお世話になるのは**ログ**です。 ```bash $ ls /var/log syslog auth.log dpkg.log ... ``` :::warning トラブル時に最初に見る場所が `/var/log` です。「何かおかしい」と思ったらここのログを確認する、というのは実務でも基本の動きになります。 ::: ### /bin と /usr/bin - コマンドの本体 あなたが打つ `ls` や `cp` といったコマンドの「実体」がここにあります。 ```bash $ which ls /usr/bin/ls ``` :::dialogue @lina: `ls` ってコマンドだと思っていました。でも、ファイルなんですか? @linny: そう、コマンドの正体は `/usr/bin` などに置かれた**プログラムファイル**なんだ。`which コマンド名` でその場所を確認できるよ。最近のディストリでは `/bin` が `/usr/bin` への近道(シンボリックリンク、実体を指す“ショートカット”のようなもの)になっていることも多いよ。 ::: ### /tmp - 一時置き場 一時的なファイル置き場です。**再起動すると中身が消える**ことが多いので、大事なファイルは置かないこと。 :::danger `/tmp` は「消えてもいいもの専用」。作業中の大事なデータをここに保存して再起動 → 消失、は初心者あるあるの事故です。保存先には必ず `/home` 以下を使いましょう。 ::: ### /opt と /root(ひとことメモ) - **/opt**: 後から追加でインストールした大きめのソフトが入ることがある場所 - **/root**: 管理者(rootユーザー)の個人フォルダ :::dialogue @lina: ちょっと `/root` の中を覗いてみます。...あれ、エラーになりました! ::: ```bash $ cd /root bash: cd: /root: Permission denied ``` :::dialogue @lina: `Permission denied` って出ました。私、何か壊しちゃいましたか!? @linny: 壊れていないから安心して。`/root` は管理者専用の部屋。一般ユーザーのリナは、その部屋の鍵を持っていないんだ。このエラーは「入れません」という合図にすぎないよ。 @lina: よかった...!壊したのかと思って焦りました。 ::: ## 5. {仮想|かそう}的なディレクトリ /proc /dev /sys {#virtual} > **結論**: `/proc` `/dev` `/sys` はディスク上に{実在|じつざい}しない。システムの状態やハードウェア情報を見せる、特別な窓口だ。 :::dialogue @lina: `/proc` を `ls` したら数字のフォルダがいっぱい出てきて、不気味でした... @linny: 怖がらなくて大丈夫。`/proc` は**ディスク上に実在しないディレクトリ**なんだ。プロセス(今動いているプログラムのこと)の情報を、Linuxがその場で見せてくれる、いわば**情報の窓口**だよ。 ::: | ディレクトリ | 中身 | イメージ | | ------------ | ----------------------------------------- | -------------------------- | | `/proc` | 実行中プロセスの情報 | システムの「健康診断窓口」 | | `/dev` | デバイス(ディスク・USBなど) | 機器への「接続口」 | | `/sys` | カーネル(Linuxの中核部分)やハードの情報 | 内部設定の「のぞき窓」 | ```bash $ cat /proc/cpuinfo # CPUの情報が見られる $ ls /dev/sda* # ストレージのデバイス ``` 出てくる内容はパソコンごとに違います。今は「読めなくても大丈夫」です。「こういう窓口がある」とだけ覚えておきましょう。 :::tip これらは「ファイルのフリをした情報」です。仕組みを完全に理解する必要はまだありません。「実体のない特別な箱がある」とだけ知っておけば十分です。 ::: ## 6. 迷子にならないコツ {#navigation} > **結論**: 迷ったら `pwd` で現在地、`cd /` でルート、`cd ~` でホームに戻る。確実なのは絶対パス。 :::dialogue @lina: 構造は分かってきました。でも実際に動いていると、やっぱり迷子になりそうで... @linny: そんなときの「現在地確認3点セット」を教えるね。これさえあれば、どこにいても落ち着けるよ。 ::: ### 現在地確認の3点セット ```bash $ pwd # 今どこにいる?(絶対パスで表示) $ ls # ここに何がある? $ cd / # ルートに戻ってやり直す ``` :::dialogue @lina: `cd /` でいちばん上に戻れるんですね。 @linny: そう。迷ったら `cd /` でルートに戻ろう。そこから `ls` で枝を1つずつたどれば、必ず目的地にたどり着ける。木の根元に戻る感覚だよ。`cd ~` なら自分の部屋(ホーム)に一発で戻れる。 ::: ### 絶対パスと相対パス | 種類 | 例 | 意味 | | -------- | ----------------- | ------------------------ | | 絶対パス | `/var/log/syslog` | `/` から書いた完全な住所 | | 相対パス | `log/syslog` | 今いる場所からの道順 | :::tip 迷ったときは**絶対パス**(`/` から始まる書き方)を使うと確実です。「どこにいても同じ場所を指す」のが絶対パスの強みです。 ::: ## ミニ課題 {#exercises} > **結論**: 3つの課題で「ルートの中身」「自分の部屋への戻り方」「コマンドの本体の場所」を自分の手で確かめる。答えを見る前に、まず自分で打ってみよう。 :::dialogue @linny: それじゃあ、地図が頭に入ったか確認してみよう! ::: **課題1**: ルートを探検しよう。この記事に出てきたディレクトリ(`etc`, `var`, `home` など)が実際にあるか確認しよう :::details ヒント1(方向づけ)を見る `/` の直下に何があるか、一覧表示してみよう。第2章で一度使ったコマンドだよ。 ::: :::details ヒント2(コマンド名)を見る `ls /` を実行しよう。 ::: :::details 答えを見る ```bash $ ls / bin dev home lib mnt proc run srv tmp var boot etc lib64 media opt root sbin sys usr ``` `etc`・`var`・`home` がちゃんとある。 ::: **課題2**: 設定の倉庫に寄り道してから、自分の部屋に戻ろう :::details ヒント1(方向づけ)を見る まず `/etc` に移動して現在地を確認する。そのあと、自分の部屋に一瞬で戻れる記号を使ってみよう。 ::: :::details ヒント2(コマンド名)を見る `cd /etc` → `pwd` → `cd ~` → `pwd` の順に実行しよう。 ::: :::details 答えを見る ```bash $ cd /etc $ pwd /etc $ cd ~ $ pwd /home/yamada ``` `cd ~` で自分のホームに一瞬で戻れた。 ::: **課題3**: コマンドの正体を見よう :::details ヒント1(方向づけ)を見る `ls` コマンドの本体(実体)が、ディスク上のどのファイルなのかを調べるための専用コマンドがあったね。 ::: :::details ヒント2(コマンド名)を見る `which ls` を実行しよう。 ::: :::details 答えを見る ```bash $ which ls /usr/bin/ls ``` 本体は `/usr/bin` の中にあった。 ::: :::dialogue @lina: できました!`/etc` に行っても `cd ~` で一瞬で部屋に帰れるから、もう怖くないです。 @linny: その感覚が大事。迷ったら根元(`/`)か部屋(`~`)に戻る。これがディレクトリ移動の基本だよ。 ::: ## 振り返り {#review} :::dialogue @lina: 今日はLinuxのディレクトリ構造を学びました。`/` が出発点で、そこから `home` や `etc` や `var` が枝分かれしてるんですね! @linny: その通り。そして「自分のものは `/home`」「設定は `/etc`」「ログは `/var/log`」。この3つだけでも実務でかなり戦えるよ。 @lina: 知らない場所が出てきても、「これはシステム側だな」って当たりがつくようになりました。 @linny: 完璧だね。地図が頭にあると、エラーメッセージのパスを見ただけで状況が読めるようになる。次は実際にファイルを作って動かしてみよう! ::: ## 今日の3行まとめ {#summary} 1. Linuxは `/`(ルート)を出発点にした**1本の木**。ドライブレターはない 2. 最重要は3つ —— **自分のものは `/home`、設定は `/etc`、ログは `/var/log`** 3. 迷ったら `pwd` で現在地確認、`cd /` で根元、`cd ~` で自分の部屋に戻る ## 次に読む {#next-steps} ディレクトリ構造の地図が頭に入ったら、実際に移動・操作して体に覚えさせましょう。 - [ターミナルの使い方](/articles/guides/terminal-basics) - [基本コマンドを覚えよう](/articles/tutorials/basic-commands) - [ファイル操作の基礎](/articles/tutorials/file-operations-basics) - [Penguin Gym Linuxで実践練習](/terminal?lesson=basic-pwd) # Linuxディストリビューション比較 - Ubuntu / Debian / Rocky / Arch の選び方 Source: https://penguin-gym-linux.com/articles/guides/linux-distro-comparison ## Linuxディストリビューションとは何か? {#intro} > **結論**: ディストリビューションは、Linux カーネルに「パッケージ管理・インストーラ・既定の設定」を組み合わせて配布形態にまとめたもの。Ubuntu / Debian / Rocky / Arch は中身のカーネルこそ同じでも、運用思想とサポート方針が大きく違う。 Linux 本体(カーネル)は、それ単体ではデスクトップにもサーバにもならない。シェル・パッケージマネージャ・初期設定・インストーラなどを束ねて、すぐ使える形にしたものが**ディストリビューション(distribution、略してディストロ)**である。 ディストリが何百種類もあるのは、「誰が」「どんな用途で」「どの更新ペースで」使うかという想定が違うため。この記事では、用途で迷ったときに名前が挙がる代表 4 種—**Ubuntu / Debian / Rocky Linux / Arch Linux**—を、実務の判断軸で比較する。 ::: tip **この記事の対象** - これから 1 台目の Linux を選ぶ人 - サーバ用途でディストリを決めかねている人 - 「とりあえず Ubuntu」から一歩進んで根拠で選びたい人 ::: ## なぜディストリ選びで迷うのか? {#why-confusing} > **結論**: 迷う原因は「機能の差」ではなく「思想の差」を比べていないから。判断軸は ①リリース方式(固定かローリングか) ②サポート期間 ③パッケージ系統 ④コミュニティか商用か の 4 つに集約できる。 どのディストリも、Web サーバを立てる・開発する・デスクトップとして使う、といった基本はこなせる。だから「できること」で比べても差が出ず、迷う。 選択を分けるのは次の 4 軸である。 | 判断軸 | 何が違うか | | -------------- | --------------------------------------------------------------- | | リリース方式 | 数年ごとの**固定リリース** か、常時更新の**ローリングリリース** | | サポート期間 | セキュリティ更新が何年続くか(5 年 / 10 年 / 無期限など) | | パッケージ系統 | `.deb`(apt)か `.rpm`(dnf)か `pacman` か | | 開発主体 | コミュニティ主導か、企業の商用版が背後にあるか | この 4 軸を頭に置けば、以降の各ディストリ解説が「どの軸で尖っているか」として読める。 ## Ubuntu — 最初の1台に最適か? {#ubuntu} > **結論**: Ubuntu は情報量とエコシステムが最大で、初学者の 1 台目に最も無難。2 年ごとの LTS(長期サポート)版を選べば、5 年間セキュリティ更新が受けられる。 Canonical 社が開発し、Debian を基盤とする。デスクトップからクラウドまで採用が広く、トラブル時に**日本語・英語の情報が圧倒的に見つかりやすい**のが最大の強み。 - **リリース方式**: 固定。6 か月ごとの通常版と、2 年ごとの **LTS 版** - **サポート**: LTS は標準 5 年(有償拡張でさらに延長可) - **パッケージ**: `.deb` / `apt` - **向く用途**: 初学者の学習機、一般的な Web サーバ、クラウドインスタンス ```bash # Ubuntu かどうか・バージョンを確認 $ cat /etc/os-release $ lsb_release -a ``` ::: tip 迷ったら **Ubuntu の LTS 版**から始めるのが定石。バージョン番号が偶数年 `.04`(例: 24.04)のものが LTS。Snap パッケージなど独自要素もあるが、最初は気にしなくてよい。 ::: ## Debian — 安定性の基準点か? {#debian} > **結論**: Debian はコミュニティ主導で、商用に縛られない「安定性の基準点」。stable 版は枯れた構成で長期間動かす用途に強く、多くのディストリ(Ubuntu 含む)の土台でもある。 世界最大級のボランティアコミュニティが開発する、最も歴史あるディストリの一つ。**「動くまで時間をかけてもいいから壊れない」**という思想が徹底している。 - **リリース方式**: 固定。stable は約 2 年ごと(テスト期間が長く、収録ソフトはやや古め) - **サポート**: stable は通常約 3 年 + LTS で計 5 年程度 - **パッケージ**: `.deb` / `apt`(Ubuntu と同系統) - **向く用途**: 長期間さわらず動かすサーバ、組み込み、最新版より枯れた安定を求める場面 ::: warning Debian stable は「収録ソフトが古い」ことがある。最新の言語ランタイムや GUI を追いたいデスクトップ用途では、Ubuntu の方が新しめで扱いやすいことが多い。安定と新しさはトレードオフ。 ::: ## Rocky Linux — RHEL 互換の本命か? {#rocky} > **結論**: Rocky Linux は RHEL(Red Hat Enterprise Linux)とバイナリ互換を目指す無償ディストリ。企業サーバの標準である RHEL 系の作法を、ライセンス費用なしで学び・運用したい場面の本命。 CentOS が「CentOS Stream」へ路線変更したのを受け、2021 年に従来型の RHEL クローンとして立ち上がった。**RHEL 向けの手順書・商用ソフトがほぼそのまま使える**のが価値。 - **リリース方式**: 固定。RHEL のメジャーリリースに追従 - **サポート**: RHEL に準じた長期(メジャー版あたり約 10 年) - **パッケージ**: `.rpm` / `dnf` - **向く用途**: 業務系サーバ、RHEL を採用する現場の検証・学習、長期安定運用 ```bash # RHEL 系の更新(メタデータ取得は自動) $ sudo dnf upgrade ``` ::: tip 同じ立ち位置に **AlmaLinux** がある。どちらも RHEL 互換を目指すため、迷ったらコミュニティ規模やスポンサー方針で選べばよい。RHEL 系の操作感は[パッケージマネージャ概観](/articles/guides/package-manager-overview)の `dnf` の節も参照。 ::: ## Arch Linux — 最新と学習に向くのは? {#arch} > **結論**: Arch は常に最新を追う**ローリングリリース**で、自分で組み上げる DIY 思想。仕組みを深く学びたい中上級者に向くが、初学者の本番サーバには不向き。 固定リリースの「数年ごとに大型更新」とは逆に、Arch は**小さな更新を継続的に適用し続ける**。常に最新の状態を保てる代わりに、更新の影響を自分で管理する責任が伴う。 - **リリース方式**: ローリング(バージョンという区切りがない) - **サポート**: 「期間」という概念がなく、更新し続ける限り最新 - **パッケージ**: `.pkg.tar.zst` / `pacman`、加えて AUR(ユーザーリポジトリ)が広大 - **向く用途**: 仕組みを学びたい学習機、最新ソフトを追う開発機、カスタム志向 ::: warning Arch は**部分更新(partial upgrade)が非推奨**。個別パッケージだけ入れる前にも `sudo pacman -Syu` で全体を同期するのが作法。放置して一気に更新すると壊れやすい。本番サーバには固定リリース系(Ubuntu LTS / Rocky / Debian)を選ぶ方が無難。 ::: ::: tip Arch の最大の資産は **Arch Wiki**。Arch 以外のディストリを使う人にも役立つ、Linux 全般の良質なリファレンスとして知られる。 ::: ## 4ディストリ比較表 {#comparison} > **結論**: 4 種を「リリース方式・サポート・パッケージ・主体・向く用途」で横に並べると、Ubuntu=無難 / Debian=堅実 / Rocky=業務 / Arch=最新 という性格の違いが一目で分かる。 | 項目 | Ubuntu | Debian | Rocky Linux | Arch Linux | | ------------ | ------------------ | -------------- | -------------------- | -------------- | | 開発主体 | Canonical(商用) | コミュニティ | コミュニティ | コミュニティ | | リリース方式 | 固定(LTS あり) | 固定 | 固定(RHEL 追従) | ローリング | | サポート期間 | LTS で 5 年 | 約 5 年 | 約 10 年 | 期間概念なし | | パッケージ | `.deb` / `apt` | `.deb` / `apt` | `.rpm` / `dnf` | `pacman` / AUR | | 新しさ | 中〜やや新 | 枯れ気味 | 枯れ気味(安定重視) | 常に最新 | | 学習難易度 | 易 | 中 | 中 | 難 | | 代表的な用途 | 学習機・汎用サーバ | 長期安定サーバ | 業務・RHEL 互換 | 学習・最新追従 | ::: highlight **覚え方**: 横軸にディストリ、縦軸に判断軸を取った表を 1 枚持っておけば、用途が決まった瞬間に「どの列か」を引くだけで選べる。これがディストリ選びの地図。 ::: ## 用途別のおすすめは? {#use-case} > **結論**: 「学習・汎用なら Ubuntu」「枯れた長期サーバなら Debian」「業務 RHEL 系なら Rocky」「最新追従と深い学習なら Arch」が基本線。迷ったら Ubuntu LTS で始めて損はない。 具体的なシナリオから逆引きする。 - **初めての Linux / 学習用**: Ubuntu LTS。情報量が多く、つまずいても答えが見つかる - **長期間さわらず動かすサーバ**: Debian stable。枯れた構成で安定運用 - **業務システム・RHEL を採用する現場**: Rocky Linux(または AlmaLinux)。商用手順がそのまま通る - **最新ソフトを追う開発機 / 仕組みを学ぶ**: Arch Linux。ただし更新管理の手間を引き受ける覚悟で - **Windows 上で手軽に試す**: まずは [WSL2](/articles/guides/wsl2-introduction) で Ubuntu を動かすのが最短 ::: tip ディストリは**後から乗り換えられる**。最初の選択に正解を求めすぎず、まず 1 つ触ってコマンドの型を覚える方が上達が早い。系統が変わっても操作は[パッケージマネージャ概観](/articles/guides/package-manager-overview)の対応表で読み替えられる。 ::: ## どう選べば失敗しないか? {#how-to-choose} > **結論**: 失敗を避けるコツは「サポート期間」と「リリース方式」を最初に決めること。本番なら固定リリース+長期サポート、学習・最新志向ならローリングや短サイクル版を選ぶ。 選定でつまずく典型は次の 2 つ。 1. **本番サーバにローリングリリースを選ぶ**: Arch を本番に入れると、更新のたびに動作確認が必要になり運用負荷が高い。本番は Ubuntu LTS / Rocky / Debian など固定リリースが無難 2. **古さを嫌って互換性を捨てる**: 最新を追うほど、周辺ツールやドキュメントとのズレが出やすい。業務では「枯れている=情報と実績が揃っている」が利点になる ::: warning **やりがちな失敗** - とりあえず最新版を入れて、半年後の大型更新で動かなくなる - RHEL 系の手順書を見ながら Ubuntu で作業して `dnf` が無くて詰まる - サポート切れ(EOL)のバージョンを使い続けてセキュリティ更新が止まる ::: 現在の自分のディストリは、どの環境でも次のコマンドで確認できる。 ```bash $ cat /etc/os-release ``` ## まとめ / 次に読む {#next} - [パッケージマネージャ概観 - apt / dnf / pacman / zypper](/articles/guides/package-manager-overview) - [Linuxとは何か?](/articles/guides/what-is-linux) - [ファイルシステムの種類入門 - ext4 / xfs / btrfs](/articles/guides/linux-filesystem-types) - [WSL2入門 - WindowsでLinuxを使う方法](/articles/guides/wsl2-introduction) # ファイルシステムの種類入門 - ext4 / xfs / btrfs の違い Source: https://penguin-gym-linux.com/articles/guides/linux-filesystem-types ## ファイルシステムとは?どれを選べばよいか {#intro} > **結論**: 迷ったら **ext4**(枯れて安定)。大容量・並列 I/O なら **xfs**、スナップショットやチェックサムが欲しいなら **btrfs** を選ぶ。 ファイルシステム(filesystem)とは、ディスク上の生のブロックを「ファイルとディレクトリ」という構造に対応づけ、メタデータ(権限・更新日時・サイズ)を管理する仕組みである。Linux では同じ物理ディスクでも、フォーマット時にどのファイルシステムを選ぶかで性能特性・運用機能・拡張縮小の柔軟性が変わる。 この記事では現代の Linux で主流の 3 種類 —— **ext4** / **xfs** / **btrfs** —— を、実務の判断軸で比較する。 ::: tip **3 行サマリ** - **ext4**: 枯れた安定の定番。デスクトップ・一般サーバの無難な既定値 - **xfs**: 大容量・大量並列 I/O に強い。RHEL 系の標準。オンライン拡張可・縮小不可 - **btrfs**: CoW・スナップショット・チェックサム・内蔵 RAID を備える多機能型 ::: ::: warning **前提** - 対象は Linux 上のローカルファイルシステム(ネットワーク FS や FAT/exFAT は対象外) - フォーマット(`mkfs`)は**データを完全消去する**操作。本番では必ずバックアップとデバイス指定を確認すること ::: ## ext4 とは?どんなときに使うのか {#ext4} > **結論**: ext4 は最も実績のある汎用ファイルシステム。journaling と extent でクラッシュ耐性と連続領域性能を確保し、迷ったときの安全な既定値になる。 ext4(fourth extended filesystem)は ext2 / ext3 の系譜を継ぐ Linux 標準ファイルシステムの一つで、Debian / Ubuntu などで長くデフォルトに採用されてきた。最大の強みは**枯れていること**——実運用での実績が圧倒的に多く、トラブル事例とその対処も広く知られている。 主な特徴: - **ジャーナリング(journaling)**: 書き込みを先にジャーナルへ記録するため、電源断後も `fsck` で短時間に整合性を回復できる - **エクステント(extent)**: 連続ブロックを範囲(開始+長さ)でまとめて管理し、断片化を抑えて大きなファイルを高速に扱う - **オンライン拡張**: マウントしたまま `resize2fs` で拡張可能。縮小はアンマウントが必要 ```bash # ext4 でフォーマット(対象デバイスを必ず確認) $ sudo mkfs.ext4 /dev/sdX1 # マウント中のまま拡張(LV やパーティション拡張後) $ sudo resize2fs /dev/sdX1 ``` ::: tip **こんなときに ext4** - 何を選ぶか決め手がない一般用途(デスクトップ / 小〜中規模サーバ) - 安定性と情報量を最優先したい ::: ## xfs とは?どんなときに使うのか {#xfs} > **結論**: xfs は大容量・高並列 I/O に最適化された 64bit ファイルシステム。オンライン拡張はできるが**縮小はできない**点が運用上の最大の注意点。 xfs はもともと SGI が IRIX 向けに開発した高性能ファイルシステムで、現在は RHEL / CentOS Stream / Rocky / AlmaLinux などの**デフォルトファイルシステム**として広く使われている。大きなファイルや大量のメタデータ操作、並列 I/O に強いのが特徴。 主な特徴: - **アロケーショングループ**: ボリュームを複数の独立領域に分割し、並列にメタデータ更新を行うことでスループットを稼ぐ - **大容量志向**: エクスサバイト級まで拡張可能な 64bit 設計で、ファイルサーバや大規模ストレージ向き - **オンライン拡張のみ**: `xfs_growfs` でマウント中に拡張できるが、**縮小機能は提供されない** ```bash # xfs でフォーマット $ sudo mkfs.xfs /dev/sdX1 # マウントポイント指定でオンライン拡張 $ sudo xfs_growfs /mnt/data ``` ::: warning **縮小できない前提で設計する** xfs は容量を後から減らせない。パーティションや LVM のサイズは「将来増やす方向」で計画し、減らす可能性があるなら ext4 や btrfs を検討する。 ::: ::: tip **こんなときに xfs** - 数 TB 以上の大容量ボリューム / ファイルサーバ - RHEL 系で標準に合わせたい(運用ナレッジが揃う) ::: ## btrfs とは?どんなときに使うのか {#btrfs} > **結論**: btrfs は CoW・スナップショット・チェックサム・サブボリュームを備えた多機能型。データ保護とロールバックを重視するなら有力だが、運用は ext4/xfs より複雑になる。 btrfs(B-tree filesystem)は Copy-on-Write(CoW)をベースにした次世代型ファイルシステムで、openSUSE のデフォルトや Fedora のデスクトップ既定として採用されている。単なる保存先を超えて、ボリューム管理機能までファイルシステム側に取り込んでいるのが特徴。 主な特徴: - **CoW(Copy-on-Write)**: 既存データを上書きせず新しい場所へ書く方式。スナップショットとの相性がよく、書き込み途中のクラッシュでも古いデータが壊れにくい - **スナップショット**: ある時点の状態を瞬時に保存し、失敗した更新を巻き戻せる。アップデート前の保険に使える - **チェックサム**: データとメタデータにチェックサムを持ち、サイレントなビット破損(bit rot)を検出できる - **サブボリューム / 内蔵 RAID**: 1 つのファイルシステム内を論理分割し、複数デバイスをまたぐ構成も組める - **オンライン拡張・縮小の両対応**: `btrfs filesystem resize` でマウント中に増減できる ```bash # btrfs でフォーマット $ sudo mkfs.btrfs /dev/sdX1 # スナップショットの作成例(サブボリューム @ を /snap へ) $ sudo btrfs subvolume snapshot / /snap/root-backup ``` ::: warning **多機能ゆえの運用コスト** CoW は断片化しやすく、データベースや仮想ディスクのような頻繁な上書きワークロードでは性能が落ちることがある(その用途では CoW を無効化する運用もある)。機能を使いこなす学習コストも ext4/xfs より高い。 ::: ::: tip **こんなときに btrfs** - スナップショットでアップデート前後を巻き戻したい(openSUSE の snapper 等) - チェックサムでデータ破損を検出したい / 複数デバイスを 1 つにまとめたい ::: ## ext4 / xfs / btrfs をどう比較して選ぶか {#compare} > **結論**: 「安定重視→ext4」「大容量・並列→xfs」「スナップショット・データ保護→btrfs」が基本軸。縮小の可否と機能の多さがトレードオフになる。 | 観点 | ext4 | xfs | btrfs | | ---------------------- | ---------------- | ------------------ | ------------------------- | | 設計の成熟度 | 非常に高い | 高い | 発展途上(機能は豊富) | | 得意領域 | 汎用・安定 | 大容量・並列 I/O | データ保護・柔軟運用 | | ジャーナリング | あり | あり | CoW(別方式) | | スナップショット | なし(要 LVM) | なし(要 LVM) | ネイティブ対応 | | チェックサム | メタデータのみ | メタデータ中心 | データ+メタデータ | | オンライン拡張 | 可 | 可(`xfs_growfs`) | 可 | | オンライン縮小 | 不可(要umount) | **不可** | 可 | | 代表的な既定ディストロ | Debian/Ubuntu | RHEL 系 | openSUSE / Fedora(一部) | ::: highlight **選び方の早見** - 決め手がない → **ext4** - 大容量ファイルサーバ・RHEL 系 → **xfs** - スナップショットでロールバックしたい / データ破損を検出したい → **btrfs** ::: ## 自分のシステムのファイルシステムを確認するには? {#check} > **結論**: `df -T` で種類込みの使用量、`lsblk -f` で各デバイスの FS、`blkid` で UUID と TYPE が分かる。フォーマット前のデバイス確認にも使う。 ```bash # マウント済み FS を種類(Type)込みで一覧 $ df -T ``` ```output Filesystem Type 1K-blocks Used Available Use% Mounted on /dev/sda2 ext4 48000000 20000000 25600000 44% / /dev/sda1 vfat 523248 6132 517116 2% /boot/efi ``` ```bash # ブロックデバイスごとに FS タイプ・UUID・マウント先を確認 $ lsblk -f # 単一デバイスの TYPE / UUID を確認(mkfs 前の取り違え防止) $ blkid /dev/sdX1 ``` ::: tip ルートファイルシステムだけ素早く知りたいときは次のどちらでもよい。 ```bash $ findmnt -no FSTYPE / $ stat -f -c %T / ``` ::: ::: warning `mkfs.*` や `wipefs` を実行する前に、必ず `lsblk` / `blkid` で**対象デバイス名が正しいか**を二重確認すること。1 文字違いで別ディスクを消去する事故が後を絶たない。 ::: ## まとめ - 迷ったら ext4、要件で xfs / btrfs {#summary} ファイルシステム選びは「最高の 1 つ」を探すより、**要件への当てはめ**で決めるのが実務的だ。 - **ext4**: 枯れた安定の既定値。迷ったらこれ - **xfs**: 大容量・並列 I/O・RHEL 系標準。ただし縮小不可 - **btrfs**: CoW・スナップショット・チェックサムの多機能型。運用は少し複雑 まずは `df -T` と `lsblk -f` で、いま自分が使っているファイルシステムを確認するところから始めよう。 - [ディレクトリ構造の全体像](/articles/guides/linux-directory-structure) - [ディスクがいっぱい(No space left)の対処](/articles/troubleshooting/no-space-left-on-device) - [コンテナと仮想マシンの違い](/articles/guides/containers-vs-vms) - [ターミナルで実際に試す](/terminal) # man/info/--help を使い分ける - 公式情報の引き方 Source: https://penguin-gym-linux.com/articles/guides/man-info-help ## この記事で解決できること {#intro} ::: dialogue @lina: ターミナルでコマンドを打ってたら「あれ、このオプション何だっけ?」ってなって、毎回ネット検索してるんですよね... @linny: 実はコマンドライン上で公式情報をすぐ調べられるんだよ。`--help`・`man`・`info` の3つを覚えると、たいていのことは自力で解決できるよ。 ::: この記事では次の3つを学べる。 - `--help`・`man`・`info` の **違いと使い分け** が分かる - `man` ページの **キー操作と{検索|けんさく}方法** が身につく - コマンド名が思い出せないときの **{探|さが}し方** が分かる ::: tip **まずこれだけ覚えよう** 1. すぐに確認したい → `コマンド --help` 2. 詳しく読みたい → `man コマンド` 3. コマンド名を探したい → `man -k キーワード` ::: ## 1. --help: 一番手軽な確認方法 {#help-option} > **結論**: `コマンド --help` はほぼ全コマンドで使え、オプション一覧と書式をすぐ確認できる最も手軽な方法。 ::: dialogue @lina: まず何から試せばいいの? @linny: まずは `--help` をつけるだけでいいよ。「オプション」は、コマンドの後ろにつけて動きを変える指定のことなんだ。`--help` はほぼ全てのコマンドで使えて、すぐ結果が出るよ。 ::: ### 使い方 ```bash ls --help ``` ```output Usage: ls [OPTION]... [FILE]... List information about the FILEs (the current directory by default). -a, --all do not ignore entries starting with . -A, --almost-all do not list implied . and .. -l use a long listing format ...(以下省略) ``` 出力が画面に収まらないときは `less` と組み合わせよう。`less` は、長い文章を 1 画面ずつ表示してくれる「ページャー」というプログラムだ。 ```bash ls --help | less ``` 真ん中の `|` は「パイプ」と呼ぶ記号で、左のコマンドの出力を右のコマンドに渡す(日本語キーボードでは Shift を押しながら「¥」のキーで入力する)。表示された画面はスペースキーで次のページへ進み、`q` キーで終了できる。 ::: tip `-h` で同じ結果が出るコマンドもある(例: `curl -h`)。どちらか試してみよう。 ::: ### --help の特徴 | 項目 | 内容 | | ------------ | ------------------------ | | 速さ | すぐ表示 | | 情報量 | 簡潔(オプション一覧) | | 対応コマンド | ほぼ全て | | 主な用途 | オプション名・書式の確認 | ## 2. man: 公式マニュアルを読む {#man} > **結論**: `man コマンド` は完全な公式マニュアルを表示し、`less` と同じキー操作と `/` 検索で読み進められる。 ::: dialogue @lina: `--help` より詳しい情報が知りたいときは? @linny: そのときは `man` コマンドだよ。Manual の略で、各コマンドの完全な{仕様書|しようしょ}が読める。 ::: ### 使い方 ```bash man ls ``` `less` と同じキー操作でページを読み進められる。 ### キー操作一覧 {#man-keys} | キー | 動作 | | -------------- | ------------------------------------ | | スペース / `f` | 次のページ | | `b` | 前のページ | | `q` | 終了 | | `/キーワード` | キーワードで検索(例: `/recursive`) | | `n` | 次の検索結果 | | `N` | 前の検索結果 | | `g` | 先頭へ移動 | | `G` | 末尾へ移動 | ::: tip `/` による検索が一番便利。`man chmod` を開いて `/octal` と打つと、数値指定の説明にジャンプできる。 ::: ### manページの構成 {#man-structure} manページは決まったセクション{構成|こうせい}になっている。 | セクション | 内容 | | ----------- | ------------------------ | | NAME | コマンド名と一行説明 | | SYNOPSIS | 書式(使い方の雛形) | | DESCRIPTION | 詳細説明 | | OPTIONS | 全オプションの説明 | | EXAMPLES | 使用例(記載があるとき) | | SEE ALSO | 関連コマンド | ::: dialogue @lina: SYNOPSIS の `[OPTION]...` とか `[FILE]...` って{記号|きごう}の意味がわからなくて... @linny: `[]` は「省略してもOK」という意味で、`...` は「複数指定できる」という意味だよ。`ls [OPTION]... [FILE]...` は「オプションもファイルも 0 個以上指定できる」ということ。 ::: ### セクション番号を指定する {#man-section-number} 同じ名前でコマンドと設定ファイルの両方にマニュアルがある場合、番号で区別できる。 ```bash man 1 passwd # passwd コマンドのマニュアル man 5 passwd # /etc/passwd ファイルのマニュアル ``` よく使うセクション番号: | 番号 | 内容 | | ---- | -------------------------------- | | 1 | ユーザーコマンド(通常使うもの) | | 5 | 設定ファイルの書式 | | 8 | システム管理コマンド | ## 3. man -k: コマンド名を探す {#man-k} > **結論**: `man -k キーワード`(=`apropos`)でやりたいことに関連するコマンドを一覧から探せる。 ::: dialogue @lina: コマンド名が思い出せないときはどうするの? @linny: `man -k キーワード` で探せるよ。やりたいことに関連する英単語で検索すると、{候補|こうほ}が一覧で出てくる。 ::: ```bash man -k compress ``` ```output bzip2 (1) - a block-sorting file compressor, v1.0.8 compress (1) - compress and expand data gzip (1) - compress or expand files xz (1) - Compress or decompress .xz and .lzma files zip (1) - package and compress (archive) files ``` ::: tip `man -k` は `apropos` コマンドと同じ動作。どちらを使っても同じ結果が出る。 ::: ::: warning 初回実行時に「nothing appropriate」と出たら、まず `sudo mandb` でデータベースを{更新|こうしん}しよう。 ::: ## 4. info: GNU ツールの詳細情報 {#info} > **結論**: `info コマンド` は GNU ツール(grep / awk / find など)の詳細な解説書で、`man` より詳しいことがある。 ::: dialogue @lina: `man` があれば `info` は使わなくていい? @linny: GNU プロジェクトのツール(grep / awk / find など)は `info` の方が詳しい場合があるんだ。`man` は概要、`info` は詳細な{解説書|かいせつしょ}というイメージかな。 ::: ### 使い方 ```bash info grep ``` ### キー操作 | キー | 動作 | | -------- | -------------------- | | スペース | 次のページ | | `b` | 前のページ | | `q` | 終了 | | `n` | 次のノード(章)へ | | `p` | 前のノード(章)へ | | `u` | 上位ノード(目次)へ | | Enter | リンクをたどる | ::: warning `info` がインストールされていない環境では `man` が代わりに表示されることがある。Ubuntu では `sudo apt install info` でインストールできる。 ::: ## リナ、`man` から抜け出せない(失敗例) {#man-quit-fail} ::: dialogue @lina: `man chmod` を開いたんですけど、ずっと画面から戻れません…`Ctrl+C` を連打しちゃいました! ::: ```bash man chmod ``` ```output CHMOD(1) User Commands CHMOD(1) NAME chmod - change file mode bits ...(以下省略) (END) ``` ::: dialogue @lina: 画面の下に `(END)` って出てるのに、何も反応しないんです… @linny: 落ち着いて。`man` は `less` と同じキー操作で終了するんだ。`Ctrl+C` じゃなくて `q` キーを押してみて。 @lina: あ、`q` で戻れました! そうか、`man` は独自の画面に入るから、普通の `Ctrl+C` じゃ{抜|ぬ}けられないんですね。 @linny: その通り。`man`・`info`・`less` はどれも同じ「ページャー」(画面に収まらない文章を1画面ずつ表示する仕組み)を使っていて、`q` が{共通|きょうつう}の終了キーなんだ。 ::: ## 5. 練習してみよう {#practice} > **結論**: `cp --help`・`man chmod` の `/octal` 検索・`man -k owner` を実際に試して使い方を定着させる。 ::: dialogue @lina: 習ったことを実際に試してみたい! @linny: じゃあ次の問題をターミナルで試してみよう。それぞれヒントを3段階用意したから、詰まったら少しずつ開いてね。 ::: ### 問題1: cp のオプションを確認しよう {#practice-1} **やること**: `cp` コマンドのオプション一覧を手軽に確認してみよう。 :::details ヒント1(方向づけ)を見る 一番手軽にオプションを確認する方法を思い出してみよう。コマンド名の後ろに何かをつけるだけだったね。 ::: :::details ヒント2(コマンド名)を見る `cp` の後ろに `--help` をつけて実行してみよう。 ::: :::details 答えを見る ```bash cp --help ``` `-i`(上書き前に確認)や `-r`(ディレクトリごとコピー)などのオプション一覧が表示される。 ::: ### 問題2: chmod のマニュアルで octal を検索しよう {#practice-2} **やること**: `chmod` のマニュアルを開いて、`octal` で検索してみよう。 :::details ヒント1(方向づけ)を見る マニュアルの中で特定の単語を探したいときに使うキー操作を思い出してみよう。`man` のキー操作一覧にあったね。 ::: :::details ヒント2(コマンド名)を見る `man chmod` でマニュアルを開いてから、`/octal` と入力するよ。 ::: :::details 答えを見る ```bash man chmod ``` 1. `man chmod` でマニュアルを開く 2. `/octal` と入力して Enter 3. 数値(8進数)による権限指定の説明にジャンプできる 4. `q` で終了 ::: ### 問題3: 所有者を変更するコマンドを探そう {#practice-3} **やること**: ファイルの所有者を変更するコマンドの名前を、コマンドラインだけで調べてみよう。 :::details ヒント1(方向づけ)を見る コマンド名がわからないときに使う調べ方を思い出してみよう。キーワードで検索できたね。 ::: :::details ヒント2(コマンド名)を見る `man -k` に「owner」や「ownership」というキーワードをつけて実行するよ。 ::: :::details 答えを見る ```bash man -k owner ``` `chown` などが見つかる。 ::: ## 6. 使い分けのまとめ {#summary} ::: dialogue @lina: 3つあって最初は混乱したけど、使い方がだいぶわかってきた! @linny: よかった。最初は `--help` と `man` だけ覚えておけば十分だよ。`man -k` と `info` は必要になったときに思い出してね。 ::: | やりたいこと | 使うもの | | ------------------------------ | ------------------- | | オプション名をすぐに確認 | `コマンド --help` | | コマンドの詳細な仕様を読む | `man コマンド` | | コマンド名が思い出せない | `man -k キーワード` | | GNU ツール(grep / awk)の詳細 | `info コマンド` | ::: tip **よくある場面と対応** - `ls -la` のオプションを確認 → `ls --help` - `chmod` の数値の意味を調べる → `man chmod` - ファイル圧縮コマンドを探す → `man -k compress` - `awk` の詳しい使い方 → `info awk` ::: ## 今日の3行まとめ {#summary-3lines} 1. すぐに確認したいときは `コマンド --help` 2. じっくり読みたいときは `man コマンド`(`q` で終了) 3. コマンド名を思い出せないときは `man -k キーワード` で探す ## 次に読む {#next} - [ターミナルの使い方 - コマンドライン入門](/articles/guides/terminal-basics) - [ディレクトリ構造の全体像](/articles/guides/linux-directory-structure) # Linuxの次に学ぶべき技術ロードマップ - Docker・Git・AWSへの橋渡し Source: https://penguin-gym-linux.com/articles/guides/next-steps-after-linux ## この記事で分かること {#intro} - Linux基礎の次に **Docker・Git・AWS** のどれを学ぶと効果が高いか、自分で判断できる - 各技術が **Linux知識のどこにつながるのか**(namespace / プロセス / SSH / ネットワーク)を理解できる - 遠回りせず学習を続けるための **順序と道筋** を描ける ::: tip **結論(先に要点)** - Linux の基本コマンドは、Docker・Git・クラウドの **共通の土台** になっている - 次の3領域はいずれも Linux 知識を「捨てずに積み増す」方向の学習であり、遠回りにならない - 迷ったら **Git → Docker → AWS** の順が、詰まりにくく成果を実感しやすい ::: ::: warning **前提(この記事の対象)** - `pwd` / `ls` / `cd` / `cat` / `grep` などの基本コマンドを触ったことがある。SSH でのサーバー接続も一度は試している - 本記事は各技術の深いチュートリアルではない。「なぜ学ぶか」「Linux との接点」を整理する概観記事だ ::: ## Linuxスキルが活きる次の3領域 {#landscape} > **結論**: Linux 基礎は単体で完結せず、Docker・Git・クラウドという実務3領域の土台として直接活きる。 Linux の基本コマンドを覚えると、「次に何を学べばいいのか」で手が止まりやすい。ここで重要な点は1つ。**次の技術は Linux 知識をゼロから置き換えるものではない**。むしろ、ここまでに身につけたプロセス・ファイル・ネットワークの理解を、そのまま土台として積み増していく。 代表的な3領域と、Linux 知識との接点を整理すると次のようになる。 | 次に学ぶ領域 | 何のための技術か | 活きる Linux 知識 | | ------------------ | -------------------------- | --------------------------------- | | **Docker** | アプリを環境ごと隔離・配布 | プロセス・namespace・cgroups | | **Git** | 変更履歴の管理・共同開発 | CLI 操作・ファイル・差分の考え方 | | **AWS / クラウド** | サーバーをクラウド上で運用 | SSH・ネットワーク・パーミッション | どれも「Linux の上で動く」技術だ。そして操作の中心はコマンドラインになる。つまり、ターミナルに慣れていること自体が次の学習の追い風になる。 ## Docker — コンテナ技術への接続 {#docker} > **結論**: Docker はプロセスを隔離する Linux カーネル機能(namespace / cgroups)を使いやすく包んだツール。プロセスの理解がそのまま活きる。 Docker は、アプリケーションを **環境ごとパッケージ化** する技術だ。パッケージ化とは、動かすのに必要なものを1つにまとめて持ち運べる形にすること。これでどのマシンでも同じように動かせる。「自分の環境では動くのに本番では動かない」という問題を、環境の差ごと持ち運ぶことで解決する。 Linux を学んだ人にとって Docker は理解しやすい。コンテナ(アプリを隔離して動かす箱)の正体が **Linux のプロセスそのもの** だからだ。 - **namespace**(名前空間): プロセスから見える範囲(PID・ネットワーク・マウント等)を区切り、独立した OS のように見せる - **cgroups**(コントロールグループ): プロセスが使える CPU・メモリなどの資源量を制限する `ps` でプロセスを眺め、`top` で負荷を確認した経験があれば、コンテナは「隔離されたプロセス」として素直に読める。「コンテナ = 軽量な仮想マシン」という誤解を手放そう。そうすれば、Docker のコマンドは魔法ではなく理屈で追える。 ::: tip **Linux 知識の橋渡し** namespace と cgroups の考え方は [コンテナと仮想マシンの違い](/articles/guides/containers-vs-vms) で仕組みから解説している。Docker に触る前に読んでおくと、コマンドの意味がつかみやすい。 ::: ## Git — バージョン管理への接続 {#git} > **結論**: Git はファイルの変更履歴を扱う CLI ツール。ターミナル操作の実用的な延長として、最初に身につけると効果が高い。 Git は、ソースコードや設定ファイルの **変更履歴を記録・共有** するためのバージョン管理システムだ。バージョン管理システムとは、ファイルの変更を1つずつ記録し、あとから取り出せるようにする道具のこと。「いつ・誰が・何を変えたか」を追跡できる。だから複数人での開発も、過去の状態への巻き戻しも安全に行える。 Git が Linux 学習の自然な次の一歩になる理由は明快だ。**操作の中心がコマンドライン** だからだ。これまで練習してきたターミナル操作がそのまま使える。 - `git status` で作業ツリーの状態を確認する(`ls` で中身を確認する感覚に近い) - `git diff` で変更点を見る(`diff` コマンドの考え方の延長) - `git log` で履歴をたどる ファイルとディレクトリの構造、テキストの差分——Linux で慣れた概念の上に、「履歴」という一段が乗るだけだ。3領域の中では詰まりにくく、日々の学習記録や設定の管理にもすぐ役立つ。だから **最初に着手する候補** として相性が良い。 ::: tip **Linux 知識の橋渡し** Linux コマンドと絡めた Git の入り口は [GitとLinuxコマンドの基礎](/articles/tutorials/git-linux-basics) で扱っている。 ::: ## AWS / クラウド — SSH・ネットワーク知識の延長 {#aws} > **結論**: クラウド上のサーバーも中身は Linux。SSH 接続・ネットワーク・パーミッションの知識がそのまま運用に直結する。 AWS をはじめとするクラウドは、サーバーを借りて使うための基盤だ。自分で機械を買って置く必要はなく、必要なときに必要なだけ借りられる。そして、**その上で動いているサーバーの多くは Linux** である。 クラウドを学ぶうえで Linux 知識が活きる場面は多い。 - **SSH 接続**(暗号化された通信で遠くのサーバーに入る仕組み): クラウド上のサーバーへ入る操作は、これまでの `ssh user@host` と同じ。鍵認証の考え方も共通 - **ネットワーク**: ポート・IP・ファイアウォールの理解が、セキュリティグループ(AWS で通信を許可・拒否する設定)に直結する - **パーミッション**: ファイルの所有者・権限の知識が、サーバー運用時のトラブル対応で効いてくる つまりクラウドは「遠くにある Linux サーバーを、Web の管理画面や CLI で操作する」世界だ。手元で身につけた操作の感覚を、そのまま持ち込める。 ::: tip **Linux 知識の橋渡し** クラウド運用の入口となる鍵認証は [SSH鍵認証のセットアップ](/articles/tutorials/ssh-key-setup) で解説している。 ::: ## 学習の進め方(ロードマップ) {#roadmap} > **結論**: 迷ったら Git → Docker → AWS の順。詰まりにくさと成果の実感しやすさで並べた目安であり、興味が強い領域から始めてもよい。 3領域に絶対的な正解の順序はない。ただ **詰まりにくさ** と **日々の学習で成果を感じやすいか** で並べると、次の順序が一つの目安になる。 | 順序 | 領域 | 始めやすい理由 | | ---- | ------ | ------------------------------------------------ | | 1 | Git | CLI 操作の延長で始められ、学習記録にもすぐ使える | | 2 | Docker | プロセスの理解を土台に、環境構築の悩みが減る | | 3 | AWS | SSH・ネットワーク知識を実運用へ橋渡しする | 大切なのは、**Linux の基礎練習を止めないこと**。次の技術を学びながらも、ターミナルでの基本操作を毎日使い続けよう。土台が固まれば、応用も速くなる。興味の強い領域があるなら、その熱量を優先して始めるのも良い選択だ。 ::: warning 一度に3つすべてを詰め込む必要はない。まず1領域に絞ろう。基本操作に手が慣れてから次へ広げるほうが、結局は近道になる。 ::: ## 次に読む {#next} - [コンテナと仮想マシンの違い](/articles/guides/containers-vs-vms) - [LinuxユーザーのためのDocker入門概論](/articles/guides/docker-for-linux-users) - [GitとLinuxコマンドの基礎](/articles/tutorials/git-linux-basics) - [SSH鍵認証のセットアップ](/articles/tutorials/ssh-key-setup) - [なぜLinuxを学ぶべきか?](/articles/guides/why-learn-linux) - [DevOps学習ハブ(Docker・Git・AWSのコース比較)](/devops) # パッケージマネージャ(package manager)概観 - apt / dnf / pacman / zypper の地図 Source: https://penguin-gym-linux.com/articles/guides/package-manager-overview ## パッケージマネージャとは何か? {#intro} > **結論**: パッケージマネージャは、ソフトウェアの導入・更新・削除・依存解決を一手に引き受けるツール。ディストリビューションごとに `apt` / `dnf` / `pacman` / `zypper` と名前は違うが、やることは同じ。 Linux でソフトウェアを入れるとき、Windows のように個別のインストーラを探して回る必要はない。パッケージマネージャが**中央のリポジトリ**から目的のパッケージを取得し、依存関係(必要な別のライブラリ)を自動で解決して導入する。 この記事の狙いは、4 大パッケージマネージャを **1 枚の地図** にまとめること。コマンド名は違っても「更新インデックスの取得 → インストール → 検索 → 削除」という**操作の型は共通**である。型さえ掴めば、未経験のディストリでも迷わない。 ::: tip **この記事の対象** - 複数ディストリを触る / 触る予定がある中級者 - `apt` は使えるが `dnf` や `pacman` で手が止まる人 - Dockerfile やサーバ移行でディストリ差分に直面した人 ::: ## なぜディストリビューションごとに違うのか? {#why-different} > **結論**: パッケージ形式(`.deb` / `.rpm` 等)と、それを管理する設計思想がディストリ系統ごとに異なるため。系統は大きく Debian 系・Red Hat 系・Arch 系・SUSE 系の 4 つに分かれる。 歴史的に各ディストリは独自にパッケージ管理を発展させた。結果、**パッケージ形式**と**ツール**が系統ごとに分かれている。 | 系統 | 代表ディストリ | 形式 | 高レベルツール | 低レベルツール | | ---------- | --------------------- | -------------- | -------------- | -------------- | | Debian 系 | Debian / Ubuntu | `.deb` | `apt` | `dpkg` | | Red Hat 系 | Fedora / RHEL / Rocky | `.rpm` | `dnf` | `rpm` | | Arch 系 | Arch / Manjaro | `.pkg.tar.zst` | `pacman` | `pacman` | | SUSE 系 | openSUSE / SLE | `.rpm` | `zypper` | `rpm` | 「高レベルツール」はリポジトリ取得・依存解決を担い、「低レベルツール」は単一パッケージファイルの展開・登録を担う。普段触るのは高レベル側だが、関係は[後述](#low-level)する。 ## apt(Debian / Ubuntu 系)の地図 {#apt} > **結論**: `apt` は最も普及した高レベルツール。`sudo apt update` でインデックスを更新してから `sudo apt install <パッケージ>` で導入するのが基本の型。 Debian と Ubuntu、およびその派生で使う。`apt` は旧来の `apt-get` / `apt-cache` を統合した対話向けフロントエンド。 ```bash # インデックス(パッケージ一覧)を更新 $ sudo apt update # インストール済みパッケージを一括更新 $ sudo apt upgrade # パッケージを導入 $ sudo apt install nginx # 削除(設定ファイルは残す) $ sudo apt remove nginx # 設定ファイルごと削除 $ sudo apt purge nginx # 検索 / 情報表示 $ apt search keyword $ apt show nginx ``` ::: warning `apt update`(インデックス更新)と `apt upgrade`(パッケージ更新)は**別物**。`update` だけでは実体は新しくならない。新規インストール前は `update` を先に走らせる。 ::: ## dnf(Fedora / RHEL 系)の地図 {#dnf} > **結論**: `dnf` は Red Hat 系の標準ツールで、かつての `yum` の後継。`apt` と違い、メタデータ更新は各コマンド実行時に自動で行われる。 Fedora、RHEL 8 以降、Rocky Linux、AlmaLinux などで使う。`yum` コマンドは現在 `dnf` へのエイリアスとして残っていることが多い。 ```bash # システム全体を更新(メタデータ取得は自動) $ sudo dnf upgrade # 更新可能なパッケージの確認のみ $ dnf check-update # パッケージを導入 $ sudo dnf install nginx # 削除 $ sudo dnf remove nginx # 検索 / 情報表示 $ dnf search keyword $ dnf info nginx ``` ::: tip `apt` のような明示的なインデックス更新コマンドは不要。`dnf install` / `dnf upgrade` の実行時に、期限切れのメタデータが自動でリフレッシュされる。 ::: ## pacman(Arch 系)の地図 {#pacman} > **結論**: `pacman` は短いオプション文字の組み合わせで操作する。`-S`(同期=導入)、`-R`(削除)、`-Q`(問い合わせ)、`-y`(DB 更新)、`-u`(アップグレード)が基本要素。 Arch Linux と Manjaro などで使う。慣れると最速だが、オプションが記号的で最初は戸惑いやすい。 ```bash # パッケージ DB を同期してシステムを全更新(最頻出) $ sudo pacman -Syu # パッケージを導入 $ sudo pacman -S nginx # 削除(不要になった依存も一緒に消す) $ sudo pacman -Rs nginx # 検索(リモート) / 情報表示(リモート) $ pacman -Ss keyword $ pacman -Si nginx # インストール済み一覧 $ pacman -Q ``` ::: warning Arch はローリングリリース。**部分更新(partial upgrade)は非推奨**で、個別パッケージだけ入れる前にも `pacman -Syu` で全体を同期するのが作法。整合性が崩れるとシステムが壊れやすい。 ::: ## zypper(openSUSE 系)の地図 {#zypper} > **結論**: `zypper` は openSUSE / SLE の標準ツール。`refresh`(インデックス更新)→ `install` / `update` という流れは `apt` に近い。各サブコマンドに短縮形がある。 openSUSE Leap / Tumbleweed と SUSE Linux Enterprise で使う。`.rpm` 形式だが、ツールは `dnf` とは別系統。 ```bash # リポジトリのインデックスを更新 $ sudo zypper refresh # インストール済みを更新 $ sudo zypper update # パッケージを導入(短縮形: in) $ sudo zypper install nginx # 削除(短縮形: rm) $ sudo zypper remove nginx # 検索(短縮形: se) / 情報表示 $ zypper search keyword $ zypper info nginx ``` ::: tip ローリングリリースの Tumbleweed では通常の `update` ではなく `sudo zypper dup`(distribution upgrade)で全体を上げるのが推奨される。Leap(固定リリース)では `update` でよい。 ::: ## 4 つのコマンド対応表 {#comparison} > **結論**: 操作の型(更新・導入・削除・検索)で横に並べれば、未知のディストリでも対応表を引くだけで作業できる。これがこの記事の中心となる地図。 | やりたいこと | apt | dnf | pacman | zypper | | -------------------- | ---------------------- | ---------------------- | ------------------- | ---------------------- | | インデックス更新 | `apt update` | (自動) | `pacman -Sy` | `zypper refresh` | | システム全体を更新 | `apt upgrade` | `dnf upgrade` | `pacman -Syu` | `zypper update` | | パッケージを導入 | `apt install ` | `dnf install ` | `pacman -S ` | `zypper install ` | | パッケージを削除 | `apt remove ` | `dnf remove ` | `pacman -R ` | `zypper remove ` | | 検索 | `apt search ` | `dnf search ` | `pacman -Ss ` | `zypper search ` | | 情報表示 | `apt show ` | `dnf info ` | `pacman -Si ` | `zypper info ` | | インストール済み一覧 | `apt list --installed` | `dnf list --installed` | `pacman -Q` | `zypper search -i` | ::: highlight **覚え方**: 「更新 → 導入 → 削除 → 検索」の 4 動作を縦軸に、ディストリを横軸に取る。新しい環境に入ったら、この表の縦 1 列を確認するだけでよい。 ::: ## 低レベルツール(dpkg / rpm)との関係は? {#low-level} > **結論**: `dpkg` / `rpm` は単一パッケージファイルを直接操作する低レベルツール。依存解決をしないため、依存を自動で揃えたいなら高レベルツール(`apt` / `dnf` 等)を使う。 ダウンロード済みの `.deb` / `.rpm` を直接入れたいときに低レベルツールを使う。 ```bash # .deb を直接インストール(依存は解決しない) $ sudo dpkg -i package.deb # .rpm を直接インストール(依存は解決しない) $ sudo rpm -i package.rpm ``` ::: warning 低レベルツールは依存を解決しないため、`dpkg -i` 後に「依存が足りない」状態になることがある。その場合は `sudo apt install -f` で依存を補修する。基本は高レベルツール経由が安全。 ::: ## どう使い分け・移行するか? {#migration} > **結論**: 使うディストリで決まるので「選ぶ」ものではない。移行時は対応表でコマンドを読み替え、ローリング系(Arch / Tumbleweed)は部分更新を避ける点だけ追加で意識する。 実務での判断ポイントを整理する。 - **サーバ運用**: Ubuntu(`apt`)と RHEL 系(`dnf`)が二大勢力。Dockerfile を書くなら両方の `install` 構文を押さえておく - **デスクトップ / 最新志向**: Arch(`pacman`)や Fedora(`dnf`)。`pacman -Syu` の作法に慣れる - **エンタープライズ**: SUSE 系(`zypper`)。`zypper dup` と `update` の使い分けに注意 - **移行時**: コマンドは[対応表](#comparison)で 1:1 に読み替え可能。詰まりやすいのは「インデックス更新が自動か手動か」「ローリングか固定か」の 2 点 ::: tip **最初の一歩**: 自分の環境のディストリ系統([なぜ違うのか](#why-different)の表)を確認し、その列のコマンドだけを覚える。他系統は必要になったとき対応表を引けばよい。 ::: ## まとめ / 次に読む {#next} - [Linuxとは何か?](/articles/guides/what-is-linux) - [ディレクトリ構造の全体像](/articles/guides/linux-directory-structure) - [man/info/--help の使い分け](/articles/guides/man-info-help) # プロセスの状態とライフサイクル(process states)- 実行・待機・ゾンビの正体 Source: https://penguin-gym-linux.com/articles/guides/process-states-lifecycle ## この記事で解決できること {#intro} - `ps` の `STAT` 列に出る **R / S / D / T / Z** が何を意味するか分かる - プロセスが `fork` で生まれ `exit` で消えるまでの **ライフサイクル** を追える - 「ゾンビが消えない」「`kill -9` でも死なない」ときの **切り分けの軸** が手に入る ::: tip **結論(読み方の型)** - `ps` の状態は **1 文字の符号** でカーネル内部状態を表す - `R`=実行/実行可能、`S`=待機(割り込み可)、`D`=待機(割り込み不可)、`T`=停止、`Z`=ゾンビ - ゾンビは「**終了済みだが親が回収していない**」状態。プロセスではなく **後始末漏れ** ::: ::: warning **前提(対象環境)** - 一般的な Linux(procps 系の `ps` / `top`) - 状態符号は `ps aux` の `STAT` 列・`ps -eo stat` で確認する ::: ## プロセスの状態とは何か? {#what} > **結論**: プロセス状態とは、カーネルがそのプロセスを今どう扱っているか(CPU 上で動かす・待たせる・止める・回収待ち)を示す内部区分であり、`ps` はそれを 1 文字に射影して見せる。 Linux のプロセスは生まれてから消えるまで、常にいずれかの **状態(state)** にある。状態はカーネルがスケジューリングのために管理する内部属性で、「CPU を割り当てる対象か」「何かを待っているか」「もう終わっているか」を区別する。 `ps` や `top` が表示する状態符号は、この内部状態を **1 文字に対応づけた表示** である。まず全体像を `ps` で観察する。 ```bash $ ps -eo pid,stat,comm ``` ```output PID STAT COMMAND 1 Ss systemd 812 S sshd 1043 R+ ps 1044 S+ bash 2210 Z defunct ``` `STAT` 列の **先頭 1 文字が主状態**、2 文字目以降は補助フラグ(後述)である。`COMMAND` が `defunct` の行がゾンビだ。 ## R / S / D - 実行と待機の違いは? {#run-sleep} > **結論**: R は CPU で動いている(または順番待ち)、S は割り込み可能な待機、D は割り込み不可能な待機。D が増えていたら I/O 詰まりを疑う。 日常的に最も多く目にするのが、実行系の `R` と待機系の `S` / `D` だ。 - **`R`(Running / Runnable)**: CPU 上で実行中、または実行待ちの **ランキュー** に載っている状態。「動ける」プロセス。 - **`S`(Interruptible Sleep)**: 何かのイベント(入力・タイマー・ソケット受信など)を **待っている** 状態。signal で割り込める。デスクトップやサーバのプロセスの大半は普段この `S`。 - **`D`(Uninterruptible Sleep)**: 主に **ディスク I/O など低レベルの完了待ち**。signal でも割り込めない。通常は一瞬で抜けるが、**長く `D` に留まるプロセスが増えるのは要注意サイン**(ストレージ障害・NFS 応答なし等)。 ```bash # D 状態(I/O 待ち)のプロセスだけ抽出 $ ps -eo pid,stat,comm | awk '$2 ~ /D/' ``` ::: warning `D` のプロセスは signal を受け付けないため、`kill -9` でも **すぐには死なない**。「`kill -9` したのに消えない」プロセスの多くは、この `D`(I/O 待ち)かゾンビ(`Z`)のどちらか。原因の I/O が完了するまで待つしかない。 ::: ## T / t - 止まっているプロセスとは? {#stopped} > **結論**: T は SIGSTOP / Ctrl+Z などで一時停止された状態、t はデバッガにトレースされて止まっている状態。どちらも CPU は使わず、再開待ち。 - **`T`(Stopped)**: ジョブ制御で停止された状態。`Ctrl+Z`(`SIGTSTP`)や `kill -STOP` で入る。`fg` / `bg` または `SIGCONT` で再開する。 - **`t`(Tracing stop)**: `gdb` や `strace` などのデバッガにトレースされて停止している状態。 ```bash $ sleep 300 & $ kill -STOP %1 # 停止 → STAT が T になる $ ps -o pid,stat,comm -p $(pgrep -n sleep) ``` ```output PID STAT COMMAND 3120 T sleep ``` ```bash $ kill -CONT %1 # 再開 → S に戻る ``` ::: tip `T` は「死んでいる」のではなく「**一時停止して再開を待っている**」。終了させたい場合は、`SIGCONT` で再開してから `SIGTERM` を送るのが安全(停止中のプロセスは TERM を処理できないことがある)。 ::: ## Z(ゾンビ)の正体は?なぜ消えないのか? {#zombie} > **結論**: ゾンビ(Z / defunct)は終了済みだがまだ親プロセスが終了ステータスを回収(reap)していない状態。本体は消えており、残るのはプロセステーブルの 1 エントリだけ。 プロセスが `exit()` で終了すると、即座に消えるわけではない。カーネルは **終了ステータス(exit code)** を保持したまま、親プロセスがそれを受け取りに来るのを待つ。この回収待ちの状態が **ゾンビ(zombie)** であり、`ps` では `Z`、`COMMAND` 欄では `` と表示される。 ゾンビの重要な性質は次の通り。 - すでに **メモリ・ファイル・CPU は解放済み**。残るのはプロセステーブルの 1 エントリ(PID と終了ステータス)だけ - 「すでに死んでいる」ため **`kill` で殺せない**(signal を受け取る実体がない) - 親プロセスが `wait()` / `waitpid()` を呼べば **即座に消える**(reap = 刈り取り) ```bash # ゾンビだけを抽出 $ ps aux | awk '$8 ~ /Z/ { print $2, $11 }' ``` ::: danger ゾンビを消す相手は **ゾンビ自身ではなく親プロセス** である。`kill -9 <ゾンビのPID>` は無意味。親が `wait` を怠っている(バグ)なら、**親プロセスに `SIGCHLD` を送る**、あるいは **親を終了させる** ことで、reap を促す(または PID 1 に引き取らせる)。 ::: 少数のゾンビは無害だが、**大量に溜まる場合は親プロセスのバグ**(子を `wait` し損ねている)を示す。PID は有限資源なので、ゾンビが PID を食い潰すと新規プロセスを作れなくなる。 ## 孤児(orphan)プロセスとゾンビの違いは? {#orphan} > **結論**: 孤児は親が先に死んだ生きているプロセスで、PID 1(init / systemd)に引き取られる。ゾンビは死んだプロセス。孤児は正常、放置ゾンビは異常。 混同されやすいのが **孤児プロセス(orphan)** とゾンビだ。両者は正反対に近い。 | 観点 | 孤児(orphan) | ゾンビ(Z) | | ---------- | --------------------- | ------------------------- | | 生死 | **生きている** | 死んでいる(終了済み) | | 発生原因 | 親が先に終了した | 親が `wait` していない | | 引き取り先 | PID 1(init/systemd) | 親(または PID 1)が reap | | 問題か | 通常は無害 | 大量なら親のバグ | 親プロセスが子より先に終了すると、子は **孤児** になり、自動的に **PID 1(`init` または `systemd`)に再ペアレント(reparent)** される。PID 1 は定期的に `wait` を呼ぶので、その孤児が将来終了しても **すぐ reap され、ゾンビとして残らない**。これが「`nohup` やデーモン化したプロセスがターミナルを閉じても生き続ける」仕組みの一部でもある。 ```bash # 親 PID が 1 になっていれば孤児(= PID 1 に引き取られた) $ ps -eo pid,ppid,stat,comm | awk '$2 == 1' ``` ## fork から exit まで - プロセスのライフサイクル {#lifecycle} > **結論**: プロセスは fork で複製され、exec で中身を入れ替え、実行(R/S/D)と停止(T)を経て exit で終了、親の wait で reap されて完全に消える。ゾンビはこの最後の一歩が未完の状態。 ここまでの状態をライフサイクルとして繋げると、プロセスの一生は次の流れになる。 1. **`fork()`**: 親プロセスが自分の複製として子プロセスを生成する。直後の子は親とほぼ同一。 2. **`exec()`**: 子プロセスが自分のメモリイメージを **別のプログラムで上書き** する(`fork` + `exec` が新規コマンド起動の基本形)。 3. **実行と待機**: スケジューラの管理下で `R`(実行)⇄ `S` / `D`(待機)⇄ `T`(停止)を行き来しながら仕事をする。 4. **`exit()`**: プログラムが終了する。終了ステータスを残して **ゾンビ(Z)** になり、親の回収を待つ。 5. **`wait()` / `waitpid()`**: 親が子の終了ステータスを受け取る(**reap**)。この瞬間にゾンビは消え、PID が解放される。 このモデルから、よくある症状が一本の線で説明できる。 - **ゾンビが残る** → ステップ 5 が抜けている(親が `wait` していない) - **孤児になる** → ステップ 4 より前に親が終了 → PID 1 が肩代わり - **`kill -9` で死なない** → ステップ 3 の `D`(I/O 待ち)か、すでにステップ 4 のゾンビ ```bash # 状態ごとのプロセス数をざっくり集計 $ ps -eo stat --no-headers | cut -c1 | sort | uniq -c | sort -rn ``` ```output 142 S 12 I 4 R 1 Z ``` ## STAT 列の補助フラグの読み方 {#flags} > **結論**: STAT の 2 文字目以降は補助情報。+ は前面ジョブ、s はセッションリーダー、l はマルチスレッド、< / N は優先度。主状態の文字と組み合わせて読む。 `ps aux` の `STAT` が `Ss` や `R+` のように 2 文字以上になるのは、主状態に **補助フラグ** が付いているためだ。主なものを押さえる。 | フラグ | 意味 | | ------ | ---------------------------------------- | | `s` | セッションリーダー(session leader) | | `l` | マルチスレッド(multi-threaded) | | `+` | 前面(foreground)プロセスグループに所属 | | `<` | 優先度高(nice 値が負) | | `N` | 優先度低(nice 値が正) | | `L` | メモリページがロックされている | たとえば `systemd` の `Ss` は「割り込み可能な待機(`S`)かつセッションリーダー(`s`)」、`ps` 自身の `R+` は「実行中(`R`)かつ前面ジョブ(`+`)」と読む。 ::: tip 状態の **主文字だけ** で集計したいときは `ps -eo stat | cut -c1` のように先頭 1 文字を取り出すと、補助フラグを無視できる(前掲の集計例がこの手法)。 ::: ## まとめ:状態符号の早見表 {#summary} > **結論**: R は動ける、S/D は待機(D は割り込み不可)、T は停止、Z はゾンビ。ゾンビと D は kill で消えない点が実務上の落とし穴。 | 符号 | 状態 | 意味 | kill で消える? | | ---- | ---------------- | -------------------- | -------------------- | | `R` | 実行 / 実行可 | CPU 上 or ランキュー | 可 | | `S` | 割り込み可待機 | イベント待ち(通常) | 可 | | `D` | 割り込み不可待機 | I/O 完了待ち | **不可(I/O 待ち)** | | `T` | 停止 | Ctrl+Z / SIGSTOP | CONT で再開後に可 | | `Z` | ゾンビ | 終了済み・reap 待ち | **不可(親を処理)** | 状態を読めるようになると、「重い」「終わらない」「消えない」という曖昧な症状を、**カーネルがそのプロセスに何をさせているか** という具体に翻訳できる。signal の送り方とセットで覚えると、プロセスのトラブル対応が一段速くなる。 ## 次に読む {#next} - [signal(シグナル)とは - SIGTERM / SIGKILL / SIGHUP の違い](/articles/guides/signals-concept) - [プロセス管理の基本(ps / top / kill)](/articles/tutorials/process-management-basics) - [ゾンビプロセスが消えないときの対処](/articles/troubleshooting/zombie-process) # rootとsudoの考え方 - なぜrootで作業しないのか Source: https://penguin-gym-linux.com/articles/guides/root-vs-sudo-concept ## sudo を付けろと言われたけど、root って結局なに? {#intro} ネットの手順をなぞっていると、`sudo apt update` のように先頭に **`sudo`** が付いたコマンドをよく見かける。「とりあえず付けておけば動く呪文」くらいの認識で使っていないだろうか。さらに調べると「**root** で作業するな」「**最小{権限|けんげん}**で」といった注意書きにぶつかる。これで余計に混乱する。 `root` は Linux で **何でもできる管理者アカウント**だ。`sudo` は **その権限を1コマンドだけ一時的に借りる仕組み**である。この記事では、なぜ普段は root を使わず sudo を使うのかを、リナとライニー先輩の会話で整理していく。読み終わるころには、`sudo` を「呪文」ではなく「意味の分かる安全装置」として使えるようになる。 ## この記事でわかること {#toc} - `root`(スーパーユーザー)が「何でもできる」特別なアカウントであること - なぜ root で作業し続けると危険なのか - `sudo` が「1コマンドだけ権限を借りる」仕組みであること - `su` と `sudo` の違い - `sudo command` という基本の使い方 - 「最小権限の原則」という考え方 ## 1. そもそも root とは何か? {#what-is-root} > **結論**: root は Linux で唯一すべての操作が許された特別な管理者アカウント。権限チェックを一切受けず、システムのどんなファイルでも読み書き・削除できる。 ::: dialogue @lina: ライニー先輩、コマンドの前に付ける `sudo` ってよく見るんですけど、そもそも `root` って何ですか? @linny: `root`(ルート)は、Linux の中で「何でもできる」一番えらいアカウントだよ。「スーパーユーザー」「管理者」「特権ユーザー」も、ぜんぶ同じものを指す別の呼び方なんだ。 @lina: 何でもできる、というのは? @linny: 学校でたとえるなら「全部屋のマスターキーを持っている人」だね。普通のユーザーが触れないシステムの設定ファイルを書き換えられる。ソフトをマシン全体にインストールもできる。他人のファイルすら消せるんだ。 @lina: 鍵のかかった部屋が、root にだけは無いということですね。 @linny: そのとおり。Linux の権限チェックは root には適用されないんだ。 @lina: えっ、じゃあ最強じゃないですか。普段から root を使えば全部解決するのでは…? @linny: そう思うよね。でもそれが一番やってはいけないことなんだ。理由はこのあと順番に話すよ。 ::: ::: tip **root = 権限チェックを{免除|めんじょ}された特別アカウント** - ユーザー名は `root`、ユーザー ID(UID)は `0` - ファイルのパーミッション(`rwx`)に関係なく、すべて読み書き・削除できる - システム全体に影響する操作(パッケージ導入・サービス再起動・ユーザー追加など)ができる 「強い」のではなく「制限が外れている」と捉えるのが正確だ。パーミッションそのものについては [パーミッションの考え方](/articles/guides/file-permissions-mental-model) を参照。 ::: ## 2. なぜ root で作業し続けると危険なのか? {#why-not-root} > **結論**: root は権限チェックが効かないため、打ち間違い1つでシステム全体を壊せる。ミスやマルウェアの{被害|ひがい}が「全領域」に及ぶのが危険の本質。 ::: dialogue @lina: 何でもできて便利なのに、なぜ root を普段使いしちゃダメなんですか? @linny: 「何でもできる」は「何でも壊せる」と同じ意味だからだよ。一般ユーザーなら、うっかり大事なファイルを消そうとしても止めてもらえる。 @lina: どう止まるんですか? @linny: 画面に「権限がありません(Permission denied)」と出て、操作が中止されるんだ。 @lina: root だと、その表示は出ないんですか? @linny: 出ない。root にはそのブレーキが無いんだ。たとえば消すディレクトリを打ち間違えたとする。root なら警告なしでそのまま実行されてしまう。 @lina: 打ち間違えたところまで消えるんですか? @linny: そう。一般ユーザーなら権限の壁で守られていた領域まで、まるごと消える。 @lina: こわい…。でも自分は気をつければ大丈夫じゃないですか? @linny: 人間は必ずミスをする。それに、危ないのはミスだけじゃないんだ。 @lina: ほかに何があるんですか? @linny: マルウェア(悪意のあるプログラム。ウイルスも同じものを指す呼び方)だよ。root で動かしたプログラムにマルウェアが混じっていたとする。その被害も root の権限で実行される。つまりシステム全体が乗っ取られてしまう。 @lina: 自分のミス以外でも危ないんですね。 @linny: そう。だから「普段は権限を絞っておく」のが{鉄則|てっそく}なんだ。 ::: ::: danger **root での操作はミスが致命傷になる** 権限チェックという安全装置が外れているため、root では次のような事故が起きやすい。 - 削除コマンドの打ち間違いで、消すつもりのなかったシステムファイルまで削除 - 設定ファイルを誤って上書きし、起動できなくなる - マルウェアを root で実行し、システム全体を改変される 普段の作業は必ず一般ユーザーで行い、管理者権限が要る瞬間だけ借りる。これが被害範囲を最小に抑えるコツ。 ::: ::: warning **削除は GUI と違って「ゴミ箱」に行かない** Windows や Mac のゴミ箱に慣れていると、ここが一番の落とし穴になる。 1. **GUI との差分**: `rm` で消したファイルはゴミ箱に入らない。その場で完全に消える 2. **安全なオプション**: `rm -i` を使うと、1 件ずつ「本当に消す?」と確認してくれる 3. **安心してほしいこと**: 本サイトの仮想ターミナルは学習用だ。あなたのパソコンは壊れない。安心して試してほしい root で `rm` を実行すると、この「消えたら戻らない」範囲がシステム全体に広がる。だから普段は一般ユーザーでいる。 ::: ## 3. sudo は何をするコマンドなのか? {#what-is-sudo} > **結論**: sudo は「この1コマンドだけ root の権限で実行する」ための仕組み。普段は一般ユーザーのまま、必要なときだけ一時的に権限を借りられる。 ::: dialogue @lina: じゃあ管理者権限が必要なときは、どうすればいいんですか? @linny: そこで `sudo`(スードゥ / sudo)の出番。`sudo` は "**s**uper**u**ser **do**" の略で、「この1回だけ管理者として実行して」とお願いするコマンドなんだ。 @lina: 1回だけ、というのがポイントですか? @linny: そう。`sudo apt update` と打つと、その `apt update` というコマンド**だけ**が root の権限で動く。終わればすぐ普段の一般ユーザーに戻る。ずっと root でいるわけじゃない。 @lina: 職員室の鍵を借りて、使ったらすぐ返すのと同じですね。 @linny: まさにそれ。しかも誰がいつ何を sudo で実行したかは記録(ログ)に残る。「いつでも root」と違って、{責任|せきにん}の所在もはっきりするんだ。 ::: ::: tip **sudo = 権限を「1コマンドだけ」借りる仕組み** - `sudo` を付けたコマンドだけが root 権限で実行される - 実行後はすぐ元の一般ユーザーに戻る(root に居続けない) - 初回などはログイン中ユーザー自身のパスワードを聞かれる(root のパスワードではない) - 誰がいつ何を実行したかがログに残る 「ずっと管理者」ではなく「必要な一瞬だけ管理者」。これが安全の核心だ。 ::: ![user が sudo にコマンドを渡し、root の権限でコマンドが実行され、結果が user に返るまでを時系列で並べた図。root の縦線には権限が有効な区間を示すオレンジの帯がある](/images/diagrams/sudo-sequence.svg){.article-diagram} {.diagram-figure} 図1: 権限を借りているのは、右端の縦線がオレンジの帯になっている一瞬だけ。それ以外の時間、user は一般ユーザーのままだ。右端の `root` は別の人ではなく root の権限そのもの、`stdout` は実行結果のことだ。パスワードの確認は図では省いている。 {.diagram-caption} ## 4. su と sudo はどう違うのか? {#su-vs-sudo} > **結論**: su は root に「成り代わって居座る」コマンド、sudo は権限を「1コマンドだけ借りて即返す」コマンド。普段使いで安全なのは sudo。 ::: dialogue @lina: 似た名前で `su` っていうコマンドも見かけました。これは sudo と何が違うんですか? @linny: いい質問。`su`(substitute user=ユーザーの入れ替え)は「ユーザーを切り替える」コマンドだよ。引数なしで使うと root に切り替わる。一度切り替えると、`exit` するまでずっと root のまま作業することになる。 @lina: ずっと root のまま…さっき危ないって言ってたやつですね。 @linny: そう。`su` で root になると、そのあと打つコマンドは全部 root 権限。ブレーキの無い状態が続く。一方 `sudo` はコマンド単位だから、危険な状態にいる時間が一瞬で済む。 @lina: だから sudo のほうが推奨されるんですね。 @linny: そのとおり。最近の Ubuntu などは、そもそも root に直接ログインできない設定が標準。日常の管理作業は sudo で済ませるのが現代の作法だよ。 ::: | 項目 | `su` | `sudo` | | ------------ | ------------------------------------------------- | -------------------------------- | | 何をする | root に切り替わって居座る | 1コマンドだけ権限を借りる | | 権限の範囲 | `exit` するまでずっと root | そのコマンド実行中だけ | | 聞かれるパス | root のパスワード | 自分(実行ユーザー)のパスワード | | ログ | root になった記録は残るが、その後の操作は残らない | 誰が何をしたか残る | | 普段使い | 非推奨 | 推奨 | ::: warning **`sudo su` という形も使われるが、まずは `sudo command` から** `sudo su` や `sudo -i` で「sudo 経由で root シェル(打ったコマンドを受け取って実行する対話プログラム。ターミナル・コンソールもほぼ同じものを指す呼び方)に入る」操作も存在する。連続して管理作業をしたい場面では使われるが、root シェルに居座る点では `su` と同じリスクがある。初心者のうちは「コマンドごとに `sudo` を付ける」基本形に慣れるのが安全。 ::: ## 5. sudo はどう使う?(基本の形) {#how-to-use} > **結論**: 管理者権限が要るコマンドの前に `sudo` を付けるだけ。`sudo <コマンド>` が基本形で、初回などは自分のパスワードを聞かれる。 ::: dialogue @lina: 実際の使い方を教えてください。 @linny: 簡単だよ。管理者権限が必要なコマンドの**前**に `sudo` を付けるだけ。たとえばパッケージ(Linux にソフトを配る形式)の一覧を更新するなら、こう打つ。 @lina: コマンドの頭に付けるだけなんですね。 @linny: そう。最初に1回、自分のパスワードを聞かれる(画面には何も表示されないけど入力はできている)。一度通れば、しばらくは聞かれずに続けて使えるよ。 ::: ```bash sudo apt update ``` ```output [sudo] password for lina: Hit:1 http://archive.ubuntu.com/ubuntu jammy InRelease ... ``` 今の自分が誰なのかは `whoami` で確認できる。 ```bash whoami ``` ```output lina ``` `sudo` を付けたコマンドの中だけ root として動き、終われば `lina` に戻っている。 ::: highlight **自分に何が許されているか `sudo -l` で確認** `sudo` を使えるユーザーや実行できるコマンドは、`/etc/sudoers` という設定で管理されている。自分にどんな sudo 操作が許可されているかは、次のコマンドで{一覧|いちらん}できる。 ```bash sudo -l ``` 「自分は何を管理者権限で実行できるのか」を把握しておくと、`Permission denied` で詰まったときの切り分けが速くなる。 ::: ## 6. 最小権限の原則とは? {#least-privilege} > **結論**: 「作業に必要な最小限の権限だけを持つ」という考え方。普段は一般ユーザーで過ごし、管理者権限は必要な瞬間だけ借りることで事故と被害を減らせる。 ::: dialogue @lina: ここまでの話、ひとことでまとめると何になりますか? @linny: 「**最小権限の原則**」だね。英語だと least privilege。必要最小限の権限しか持たないようにする、という考え方だよ。 @lina: 普段は弱いまま、必要なときだけ強くなる、みたいな? @linny: まさにそう。普段から最強(root)でいると、ミスやマルウェアの被害も最強になる。でも普段は一般ユーザーでいれば、被害は自分の届く範囲で止まる。そして root が要る一瞬だけ `sudo` で借りる。 @lina: なるほど。だから「root で作業しない」「sudo を使う」がセットで言われるんですね。 @linny: その通り。これは Linux だけの話じゃなくて、セキュリティの世界では共通の鉄則。覚えておくとずっと役に立つよ。 ::: ::: tip **最小権限の原則は習慣で身につく** - 普段のログインは一般ユーザーで行う(root で常用しない) - 管理者権限が要るコマンドだけ `sudo` を付ける - `sudo` を付ける前に「このコマンドは本当に管理者権限が必要か」を一度考える - 何を実行しようとしているか分からない `sudo` コマンドは、コピペで実行しない 「強い権限はすぐ返す」を習慣にすれば、事故の大半は防げる。 ::: ## 7. 手を動かして確かめるには? {#practice} > **結論**: `whoami` で今の自分を確かめ、`sudo` 付き/なしでコマンドの通り方がどう変わるかを実際に打つのが、いちばん速い理解の近道。 ::: dialogue @lina: 頭では分かった気がしますが、まだ実感がないです… @linny: こういうのは打って確かめるのが一番。`whoami` で今の自分を確認して、権限が要る操作を `sudo` あり・なしで試すと、「あ、ここで権限が必要なんだ」と体でわかるよ。 @lina: 自分のマシンで管理者コマンドを試すのはちょっと怖いです。 @linny: それなら、ブラウザで安全に試せる練習場を使うといい。壊す心配なく `whoami` や権限の感覚を確かめられるよ。 ::: ```bash whoami id ``` ```output lina uid=1000(lina) gid=1000(lina) groups=1000(lina),27(sudo) ``` `id` の出力に `sudo`(Ubuntu・Debian 系)または `wheel`(RHEL 系)のグループが含まれていれば、そのユーザーは `sudo` を使える。`uid=1000` のように 0 以外なら、今は root ではなく一般ユーザーだ。 ::: tip [Penguin Gym Linux のターミナル](/terminal) で `whoami` を打って、「今の自分が誰か」を確かめてみよう。権限の感覚をつかむと、`sudo` を付ける/付けない の判断が自然にできるようになる。 `id` と `sudo -l` は本サイトのターミナルでは未対応だ。この2つは実機の Ubuntu などで試してほしい。 ::: ## リナ、sudo を付け忘れる {#first-try} > **結論**: 一般ユーザーのままシステム領域にファイルを作ると `Permission denied` で止まる。`sudo` を付けると通る(実機の Ubuntu での例)。 ::: dialogue @lina: システムの設定ファイルを置く練習をしてみます。`/etc` に空のファイルを作ってみますね。 ::: ```bash touch /etc/example.conf ``` ```output touch: cannot touch '/etc/example.conf': Permission denied ``` ::: dialogue @lina: あれ、断られました。コマンドは合っているはずなのに…。 @linny: それでいいんだよ。`/etc` は一般ユーザーが書き込めない場所なんだ。ブレーキがちゃんと効いた{証拠|しょうこ}だね。 @lina: エラーじゃなくて、守られていたということですか。 @linny: そう。ここで初めて「このコマンドには管理者権限が要る」と分かる。だから `sudo` を付け直すんだ。 ::: ```bash sudo touch /etc/example.conf ls -l /etc/example.conf ``` ```output -rw-r--r-- 1 root root 0 Jun 6 10:10 /etc/example.conf ``` ::: dialogue @lina: 今度は通りました。しかも所有者が `root` になっていますね。 @linny: いいところに気づいたね。`sudo` で作ったファイルは root のものになるんだ。「先に `sudo` なしで試して、断られたら付ける」——この順番なら、要らない権限を使わずに済むよ。 ::: ## ミニ課題 {#exercises} > **結論**: この記事で出てきた `whoami` / `id` / `sudo -l` を実際に打って、自分の権限を確かめよう。 ::: dialogue @linny: 学んだことを自分の手で確かめてみよう。3 つの課題を用意したよ。 ::: ### 課題1: 今の自分が誰かを確かめよう {#exercise-1} **やること**: 今ログインしているユーザー名を表示しよう。 :::details ヒント1(方向づけ)を見る 「私は誰?」を英語でそのまま並べたような名前のコマンドがある。 ::: :::details ヒント2(コマンド名)を見る `whoami` を使うよ。who am i(私は誰)をつなげた名前だね。 ::: :::details 答えを見る ```bash whoami ``` ```output lina ``` ::: ### 課題2: 自分が sudo グループに入っているか確かめよう {#exercise-2} **やること**: 自分の所属グループを表示して、`sudo` が含まれるか見よう。 :::details ヒント1(方向づけ)を見る ユーザー ID と所属グループをまとめて表示するコマンドがある。2 文字だよ。 ::: :::details ヒント2(コマンド名)を見る `id` を使うよ。出力の `groups=` の部分に注目しよう。 ::: :::details 答えを見る ```bash id ``` ```output uid=1000(lina) gid=1000(lina) groups=1000(lina),27(sudo) ``` `groups=` の中に `27(sudo)`(RHEL 系なら `wheel`)があれば、そのユーザーは `sudo` を使える。この課題は実機の Ubuntu などで試そう。 ::: ### 課題3: 許された sudo 操作を一覧しよう {#exercise-3} **やること**: 自分にどんな sudo 操作が許可されているかを表示しよう。 :::details ヒント1(方向づけ)を見る `sudo` 自身にオプションを付けると、許可の一覧を出せる。list(一覧)の頭文字だよ。 ::: :::details ヒント2(コマンド名)を見る `sudo -l` を使うよ。 ::: :::details 答えを見る ```bash sudo -l ``` ```output User lina may run the following commands on penguin: (ALL : ALL) ALL ``` 実際は先頭に `Matching Defaults entries` のブロックも出るため、ここでは一部を省いている。初回はパスワードを聞かれる。入力しても画面には何も出ないが、そのまま打って Enter でよい。この課題も実機の Ubuntu などで試そう。 ::: ## 振り返り {#review} > **結論**: root は制限が外れたアカウント、sudo は権限を一瞬だけ借りる仕組み。この 2 つの区別が最小権限の原則につながる。 ::: dialogue @lina: つまり root は「マスターキーを持ったまま歩き回る状態」で、sudo は「必要なときだけ鍵を借りて、すぐ返す」ということですね。 @linny: その理解で正しいよ。だから `su` で root に居座らず、コマンドごとに `sudo` を付けるほうが安全なんだ。 @lina: `sudo` なしで試して、断られたら付け直す。この順番も覚えました。 @linny: いい習慣だね。それが最小権限の原則を、毎日の操作で実践するということだよ。 ::: ## 今日の3行まとめ {#summary} > **結論**: root の正体・sudo の仕組み・最小権限の原則の 3 点を押さえれば、管理者権限は怖くない。 1. **root は権限チェックが免除されたアカウント** - 何でもできる代わりに、ミスも被害もシステム全体に及ぶ 2. **`sudo <コマンド>` は権限を1コマンドだけ借りる仕組み** - `su` と違って root に居座らず、実行の記録も残る 3. **最小権限の原則を習慣に** - 普段は一般ユーザー、必要な一瞬だけ管理者。許可の確認は `sudo -l` ## 次に読む {#next} - [パーミッションの考え方 - rwxとowner/group/other](/articles/guides/file-permissions-mental-model) - [パーミッションの基本 - chmod / chown 入門](/articles/tutorials/permissions-basics) - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) # shell 比較(bash/zsh/fish)- 違いと選び方 Source: https://penguin-gym-linux.com/articles/guides/shell-comparison ## 結論:どのシェルを選ぶべきか {#conclusion} 用途別の選択は明確に決まる。 - **bash** — スクリプト・サーバー管理・互換性最優先の場面 - **zsh** — インタラクティブ操作の快適さと bash 互換性を両立したい場面(macOS デフォルト) - **fish** — 設定ゼロで補完・ハイライトを使いたい場面 スクリプトの移植性が必要なら bash 一択。設定コストをかけずに快適なインタラクティブ操作をしたいなら fish。その中間で豊富なプラグインエコシステムを活用したいなら zsh が出発点になる。 ## bash とはなにか? {#what-is-bash} bash(Bourne Again Shell)はほぼすべての Linux ディストリビューションのデフォルトシェル。POSIX 互換性が高く、サーバー間・CI/CD 環境での移植性は三者の中で最高。 ```bash echo $BASH_VERSION ``` ```output 5.2.21(1)-release ``` **主な特徴:** - GNU/Linux 標準。ほぼ確実にどこにでも存在する - POSIX sh との互換性が高い - 補完・ハイライトは設定なしでは最小限 - スクリプトの実績と資料が膨大 ::: tip `#!/bin/bash` を書いたシェルスクリプトは bash で動く前提。サーバー上でスクリプトを配布・実行する場合は bash を基準に書く。 ::: bash の補完を強化したい場合は `bash-completion` パッケージを追加インストールする。ただしインタラクティブな快適さでは zsh・fish に劣る。 ## zsh とはなにか? {#what-is-zsh} zsh(Z shell)は bash の上位互換として設計されたシェル。Apple が macOS Catalina(2019)以降のデフォルトを bash から zsh に切り替えたことで広く普及した。 ```bash echo $ZSH_VERSION ``` ```output 5.9 ``` **主な特徴:** - bash スクリプトの大半をそのまま実行できる(高い互換性) - 組み込みの補完機能が強力 - Oh My Zsh / Powerlevel10k 等のエコシステムが充実 - グロブ展開の拡張・スペルミス修正・右側プロンプト(RPROMPT)に対応 ::: tip bash から zsh へ移行する場合、既存スクリプトの大半はそのまま動く。移行コストが低い点が選ばれる主な理由のひとつ。 ::: Oh My Zsh を追加することで数百のプラグインとテーマが使えるが、初期化スクリプトが重くなるとシェル起動が遅くなる点に注意する。 ## fish とはなにか? {#what-is-fish} fish(Friendly Interactive Shell)は「設定しなくても使える」をコンセプトに設計されたモダンシェル。補完・シンタックスハイライト・提案機能がインストール直後から動作する。 ```bash echo $version ``` ```output 3.7.1 ``` **主な特徴:** - 入力中にコマンドをリアルタイムでハイライト(正しいコマンドは白、存在しないコマンドは赤) - 履歴と man ページベースの自動補完が標準搭載 - 設定ファイルは `~/.config/fish/config.fish` - **POSIX sh 非互換の独自構文** ::: warning fish の構文は bash/zsh と非互換。`VAR=value` での変数代入、`if [ ... ]`、`export` 等は fish では動作しない。インタラクティブ操作専用として割り切り、スクリプトは別途 bash で書く運用が現実的。 ::: ## bash/zsh/fish の違いを比較する {#comparison} | 項目 | bash | zsh | fish | | ---------------------- | ---------- | ------------ | -------- | | デフォルト採用環境 | Linux 全般 | macOS | なし | | bash 互換性 | — | 高い | 低い | | 補完(標準) | 基本 | 強力 | 最も強力 | | シンタックスハイライト | 設定要 | 設定要 | 標準搭載 | | 自動補完提案 | なし | プラグイン要 | 標準搭載 | | 設定コスト | 低い | 中〜高 | 低い | | スクリプト移植性 | 最高 | 高い | 低い | | プラグインエコシステム | 少ない | 豊富(OMZ) | fisher | | POSIX 準拠 | 高い | 高い | 低い | ## どのような場面でどれを使うか? {#use-cases} ### サーバー管理・DevOps bash 一択。スクリプトの移植性とリモートログイン時の可用性が最優先。本番サーバーに fish や zsh がインストールされていないケースは多く、想定外のシェルを前提にしたスクリプトは障害要因になる。 ```bash #!/bin/bash # サーバー間で共有するスクリプトは bash で書く set -euo pipefail DEPLOY_DIR="/var/www/app" echo "deploy started: ${DEPLOY_DIR}" ``` ### 日常のインタラクティブ操作(macOS・WSL) zsh か fish が適している。macOS では zsh がデフォルトのため、Oh My Zsh または Powerlevel10k を追加するのが最短ルート。fish はインストール直後から補完・ハイライトが使えるため、設定に時間をかけたくない人に向く。 ```bash # zsh をデフォルトシェルに変更 chsh -s /usr/bin/zsh # fish をデフォルトシェルに変更(インストール済みの場合) chsh -s /usr/bin/fish ``` ### Linux 学習・入門 bash から始めることを推奨する。理由は以下の 3 点。 1. チュートリアル・書籍・オンラインリソースの大半が bash を前提にしている 2. `#!/bin/bash` のスクリプトをそのまま動かせる 3. どこにでもあるため、学んだことがどの環境でも使える fish の快適さは魅力だが、bash 構文を学ばずに fish に慣れると後でスクリプトを書く際に詰まりやすい。 ## 現在のシェルを確認・変更するには? {#change-shell} ### 現在のシェルを確認 ```bash echo $SHELL ``` ```output /bin/bash ``` または `ps $$` でプロセス名から確認する方法もある。 ```bash ps $$ ``` ```output PID TTY STAT TIME COMMAND 12345 pts/0 Ss 0:00 -bash ``` ### 使用可能なシェル一覧を表示 ```bash cat /etc/shells ``` ```output /bin/sh /bin/bash /usr/bin/bash /bin/rbash /usr/bin/rbash /usr/bin/sh /bin/dash /usr/bin/dash /usr/bin/zsh /bin/zsh ``` ### デフォルトシェルを変更 ```bash # zsh に変更 chsh -s /usr/bin/zsh # fish に変更(インストール済みの場合) chsh -s /usr/bin/fish ``` ::: warning `chsh` の変更は次回ログイン(またはターミナルの再起動)から有効。現在のセッションには影響しない。変更後は `echo $SHELL` で確認する。 ::: fish がインストールされていない場合は以下でインストールする。 ```bash # Ubuntu / Debian sudo apt install fish # macOS(Homebrew) brew install fish ``` ## よくある誤解 {#misconceptions} **「zsh は bash より速い」** — 起動速度は設定量に依存する。プラグインを大量に追加した Oh My Zsh は素の bash より遅くなることがある。`.zshrc` の読み込み時間は `time zsh -i -c exit` で計測できる。 **「fish はスクリプトが書けない」** — fish 独自の構文でスクリプトは書ける。ただし POSIX 非互換のため他環境への移植性はない。fish のスクリプトは fish 専用機能(関数・補完定義)に限定し、汎用スクリプトは bash で書く分担が実際的。 **「bash は古くて使えない」** — インタラクティブ操作の快適さでは fish・zsh に劣るが、サーバー管理・自動化・CI/CD パイプラインでは bash の安定性と移植性は強みになる。用途次第。 ## 次に読む {#next} - [ターミナルの使い方 - コマンドライン入門](/articles/guides/terminal-basics) - [man/info/--help を使い分ける](/articles/guides/man-info-help) - [Linuxとは何か?](/articles/guides/what-is-linux) # signal(シグナル)とは - SIGTERM / SIGKILL / SIGHUP の違い Source: https://penguin-gym-linux.com/articles/guides/signals-concept ## この記事で解決できること {#intro} - `SIGTERM` / `SIGKILL` / `SIGHUP` の **違いと使い分け** が分かる - 「`kill -9` を最初から使うべきか?」に **根拠を持って答えられる** ようになる - プロセスが「終了しない」「設定が反映されない」ときの **切り分けの軸** が手に入る ::: tip **結論(実務の型)** - まず `kill`(= `SIGTERM`)で **穏便に終了** を試す - 反応しなければ `kill -KILL`(= `SIGKILL`)で **強制終了** - `kill -9` を **反射的に使わない**。後始末をスキップさせる最終手段と理解する ::: ## signal(シグナル)とは何か? {#what} > **結論**: signal はカーネルがプロセスへ送る非同期の通知。プロセスはそれを受けて終了・停止・再読み込みなどの規定動作を行う。 signal(シグナル)は、**カーネルからプロセスへ送られる非同期のソフトウェア割り込み** である。「終了してほしい」「停止してほしい」といった短いメッセージを番号で伝える仕組みで、プロセス間通信(IPC)の最も軽量な形のひとつにあたる。 signal が送られる経路は主に 3 つ。 - **キーボード操作**: `Ctrl+C` → `SIGINT`、`Ctrl+Z` → `SIGTSTP` - **`kill` / `pkill` コマンド**: 任意のプロセスへ任意の signal を送る - **カーネル / 他プロセス**: メモリ不足時の `SIGKILL`(OOM Killer)、不正メモリアクセス時の `SIGSEGV` など 利用可能な signal の一覧は `kill -l` で確認できる。 ```bash $ kill -l ``` ```output 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM ... ``` ::: warning signal 番号は **すべてが固定ではない**。`SIGHUP`(1) / `SIGINT`(2) / `SIGKILL`(9) / `SIGTERM`(15) など低番号は安定しているが、`SIGUSR1` などはアーキテクチャで番号が変わる。スクリプトでは **番号より名前**(`-TERM` / `-KILL`)で指定するのが安全。 ::: ## SIGTERM とは?なぜ既定なのか? {#sigterm} > **結論**: SIGTERM(15)は「穏便に終了して」という依頼。プロセスは捕捉して後始末(ファイル保存・接続クローズ)してから終われる。kill の既定値。 `SIGTERM`(番号 15)は、`kill` コマンドが **signal を指定しなかったときに送る既定の signal** である。 ```bash # この 2 つは等価 $ kill 12345 $ kill -TERM 12345 ``` `SIGTERM` の重要な性質は、プロセスが **これを捕捉(catch)できる** ことだ。受け取ったプロセスは終了する前に、 - 編集中ファイルの保存 - データベース接続やソケットのクローズ - 一時ファイルの削除 - 子プロセスへの終了通知 といった **後始末(cleanup)** を実行してから自分で `exit()` できる。これが **graceful shutdown(穏便な終了)** であり、`systemd` がサービス停止時にまず `SIGTERM` を送るのもこのためだ。 ::: tip データを扱うプロセス(DB・エディタ・アプリサーバ)を止めるときは、**必ず `SIGTERM` を先に試す**。いきなり強制終了するとデータ破損のリスクがある。 ::: ## SIGKILL とは?SIGTERM と何が違う? {#sigkill} > **結論**: SIGKILL(9)はカーネルが即座にプロセスを消す強制終了。捕捉・無視・ブロックが一切できず、後始末も走らない最終手段。 `SIGKILL`(番号 9)は、`kill -9` でおなじみの **強制終了** signal である。`SIGTERM` との決定的な違いは次の 1 点に集約される。 ::: danger **`SIGKILL` と `SIGSTOP` だけは、プロセス側で捕捉・無視・ブロックが一切できない。** カーネルがプロセスのハンドラを通さず直接終了させるため、後始末コードは **一切実行されない**。 ::: つまり `kill -9` は、 - 編集中のデータが **保存されない** - 開いていたファイル・ソケットが **正常にクローズされない** - 一時ファイルや lock ファイルが **残る** - 子プロセスが **孤児(orphan)になる** といった副作用を伴う。これらは OS がある程度回収するが、アプリケーションレベルの整合性は保証されない。 | 観点 | SIGTERM (15) | SIGKILL (9) | | ----------------- | ------------ | ------------------- | | 捕捉できるか | できる | **できない** | | 後始末(cleanup) | 走る | **走らない** | | データ保存 | 期待できる | されない | | 使いどころ | 通常の終了 | TERM が効かないとき | ::: warning **`kill -9` を最初から使うのは避ける**。「終わらないからとりあえず -9」は、固まった原因(デッドロック・I/O 待ち)を隠したままデータを失う行為になりがち。まず `SIGTERM`、数秒待って反応がなければ `SIGKILL`、という順序を守る。 ::: ## SIGHUP とは?終了以外の使い道 {#sighup} > **結論**: SIGHUP(1)は元来「端末が切れた」通知だが、デーモンでは「設定ファイルを再読み込みせよ」の合図として再利用される慣習がある。 `SIGHUP`(番号 1、hang up = 回線切断)は、もともと **制御端末との接続が切れたときに送られる** signal だ。ターミナルを閉じると、そこで起動したプロセスには `SIGHUP` が届き、既定動作では終了する。`nohup` コマンドが「`SIGHUP` を無視して実行を継続する」ためのものなのは、この仕様への対処である。 一方、多くの **デーモン(常駐プロセス)** は `SIGHUP` を独自に解釈し、「**プロセスを再起動せずに設定ファイルを読み直す**」合図として使う慣習がある。 ```bash # nginx に設定リロードを指示(プロセスは止めない) $ kill -HUP $(cat /var/run/nginx.pid) ``` ::: tip この「再読み込み」は signal の仕様ではなく、各デーモンが実装している **慣習** にすぎない。挙動はソフトごとに異なるため、必ず公式ドキュメントで `SIGHUP` の扱いを確認すること。 ::: ## SIGINT / SIGSTOP など他の主要 signal {#others} > **結論**: 日常で触れる signal は SIGINT(Ctrl+C)と SIGTSTP(Ctrl+Z)。SIGSTOP / SIGCONT は捕捉不可で、プロセスの一時停止と再開に使う。 ターミナルでよく発生する signal を押さえておく。 - **`SIGINT`(2)**: `Ctrl+C`。「割り込み」。既定では終了するが捕捉可能(多くの CLI が確認プロンプトを出すのはこのため)。 - **`SIGTSTP`(20)**: `Ctrl+Z`。フォアグラウンドのジョブを一時停止。`fg` / `bg` で再開できる。 - **`SIGSTOP`(19)/ `SIGCONT`(18)**: プロセスの停止と再開。`SIGSTOP` は `SIGKILL` 同様 **捕捉不可**。 - **`SIGQUIT`(3)**: `Ctrl+\`。終了 + コアダンプ生成。 `Ctrl+Z` で止めたジョブの扱いは [ジョブ制御の基本](/articles/tutorials/job-control-basics) で詳しく扱っている。 ## 実務での送り方:kill / pkill / killall {#commands} > **結論**: PID 指定なら kill、名前指定なら pkill / killall。誤爆を避けるため、まず pgrep で対象を確認してから送るのが安全。 signal を送る代表的なコマンドは 3 つ。 ```bash # PID を指定して送る(最も基本) $ kill -TERM 12345 # プロセス名で送る(部分一致) $ pkill -TERM nginx # プロセス名で送る(完全一致、killall) $ killall -TERM nginx ``` ::: warning `pkill` / `killall` は **マッチした全プロセスに送る**。意図しないプロセスを巻き込まないよう、送る前に `pgrep -a <名前>` で対象を必ず確認すること。 ```bash $ pgrep -a nginx ``` ::: 「まず TERM、効かなければ KILL」を 1 行で書くなら、間に待機を挟む。 ```bash $ kill -TERM 12345; sleep 5; kill -KILL 12345 2>/dev/null ``` ## まとめ:signal 使い分けの早見表 {#summary} > **結論**: 穏便な終了は SIGTERM、最終手段が SIGKILL、設定リロードは SIGHUP。番号より名前で指定し、強制終了は最後に回す。 | やりたいこと | 送る signal | コマンド例 | | -------------------- | ----------------- | ---------------------- | | 普通に終了させる | SIGTERM | `kill ` | | どうしても止まらない | SIGKILL | `kill -KILL ` | | 設定を再読み込み | SIGHUP | `kill -HUP ` | | 一時停止 / 再開 | SIGSTOP / SIGCONT | `kill -STOP` / `-CONT` | ## 次に読む {#next} - [プロセス管理の基本を学ぶ](/articles/tutorials/process-management-basics) - [Ctrl+C / Ctrl+Z とジョブ制御](/articles/tutorials/job-control-basics) - [SIGKILL で消せないゾンビプロセスの対処](/articles/troubleshooting/zombie-process) - [OOM Killer がプロセスを SIGKILL する仕組み](/articles/troubleshooting/oom-killer-handling) # SSH鍵認証 vs パスワード認証 - なぜ鍵が安全なのか Source: https://penguin-gym-linux.com/articles/guides/ssh-key-vs-password-concept ## この記事で解決できること {#intro} - SSH鍵認証とパスワード認証の **本質的な違い** が分かる - 「なぜ鍵の方が安全なのか」を **暗号の仕組みから** 説明できる - `ssh-keygen` での鍵作成・設置・パスワード認証無効化まで **実務の型** が身につく ::: tip **結論(先に要点)** - パスワード認証は **秘密(パスワード)をサーバ側で照合** する。鍵認証は **秘密(秘密鍵)をクライアントから一切出さない** - 鍵認証は **総当たり攻撃・パスワード使い回し・漏洩リスク** に構造的に強い - 本番サーバは **鍵認証 + パスワード認証無効化** が定番の型 ::: ::: warning **前提(対象環境)** - OpenSSH(Ubuntu / Debian / RHEL 系など一般的なLinuxサーバのSSH実装) - クライアントもOpenSSH(macOS / Linux / WSL / Windows 10以降) ::: ## SSH鍵認証とパスワード認証は何が違うのか? {#difference} > **結論**: パスワード認証は「知っている秘密」を毎回サーバへ送って照合する。鍵認証は「持っている秘密鍵」をネットワークに出さずに所有を証明する。 両者は「本人をどう確かめるか」の方式が根本的に異なる。 | 観点 | パスワード認証 | 鍵認証(公開鍵認証) | | ------------------ | -------------------------------------- | --------------------------- | | 秘密の正体 | パスワード(記憶) | 秘密鍵(ファイル) | | 秘密の流れ | 暗号化された経路でサーバへ送る | ネットワークに **出さない** | | サーバ側が持つもの | パスワードのハッシュ | 公開鍵(`authorized_keys`) | | 突破の主な経路 | 推測・総当たり・使い回し・フィッシング | 秘密鍵ファイルの盗難 | ::: tip パスワードは「頭の中の文字列」、秘密鍵は「手元のファイル」。守るべき対象の性質が違うので、対策の考え方も変わる。 ::: ## なぜ鍵認証の方が安全なのか? {#why-secure} > **結論**: 秘密鍵がネットワークを流れないため盗聴で奪えず、鍵長が事実上総当たり不能で、サーバ側が漏洩しても秘密鍵は復元できないため。 鍵認証の安全性は主に3つの構造から生まれる。 ### 1. 秘密がネットワークを流れない パスワード認証では、暗号化されているとはいえパスワードそのものがサーバへ届く。鍵認証では秘密鍵はクライアントに留まり、送られるのは **署名(秘密鍵で作った証明)だけ**。秘密鍵自体は経路上に存在しない。 ### 2. 総当たりが事実上不可能 人間が覚えられるパスワードは桁数・複雑さに限界があり、総当たり・辞書攻撃の標的になる。一方 Ed25519 や 2048bit 以上の RSA 鍵は、鍵空間が天文学的で総当たりは現実的でない。 ### 3. サーバ漏洩に強い サーバが保持するのは **公開鍵** だけ。公開鍵から秘密鍵を逆算することはできないため、`authorized_keys` が流出しても他人がログインできるようにはならない。パスワードのハッシュ流出(オフライン解析の起点)とは前提が違う。 ::: warning 「鍵認証は安全」は **秘密鍵を守れている前提** が成り立つ場合の話。秘密鍵ファイルそのものを盗まれれば突破される。守る対象がパスワードから秘密鍵ファイルに移っただけ、という視点を忘れない。 ::: ## 公開鍵と秘密鍵はどう機能するのか? {#how-keys-work} > **結論**: クライアントが秘密鍵で署名し、サーバが対応する公開鍵で検証する。秘密鍵を渡さずに「秘密鍵を持っている」ことを証明できる。 鍵認証は **公開鍵暗号** に基づくチャレンジ・レスポンスで成立する。流れは概ね次の通り。 1. クライアントが「この公開鍵で認証したい」とサーバへ伝える 2. サーバは `authorized_keys` に該当する公開鍵があるか確認する 3. クライアントはセッション固有のデータに **秘密鍵で署名** して返す 4. サーバは **公開鍵で署名を検証** する。一致すれば本人と判断する ポイントは、署名はセッションごとに異なるデータに対して作られるため、過去の通信を盗聴・再生しても次の認証には使えないこと。秘密鍵そのものは一度もネットワークに出ない。 ::: tip 公開鍵 = 配ってよい錠前、秘密鍵 = 手元に隠す鍵、というイメージ。錠前(公開鍵)はサーバに何個でも置けるが、それを開けられるのは対応する鍵(秘密鍵)を持つ本人だけ。 ::: ## 鍵ペアをどう作成して設置するのか? {#setup} > **結論**: `ssh-keygen -t ed25519` で鍵ペアを作り、`ssh-copy-id` で公開鍵をサーバの `authorized_keys` に設置する。 ### 1. 鍵ペアを作成する ```bash $ ssh-keygen -t ed25519 -C "you@example.com" ``` - `-t ed25519`:現在の推奨アルゴリズム(高速で安全。古い環境向けには `-t rsa -b 4096`) - `-C`:コメント(どの鍵か後で識別するための目印) - 生成物:秘密鍵 `~/.ssh/id_ed25519` と 公開鍵 `~/.ssh/id_ed25519.pub` 途中で **パスフレーズ** を求められる。これは秘密鍵ファイル自体を暗号化する追加の鍵で、設定を強く推奨する(後述)。 ### 2. 公開鍵をサーバへ設置する ```bash $ ssh-copy-id user@server ``` `ssh-copy-id` は公開鍵をサーバの `~/.ssh/authorized_keys` に安全に追記し、権限も適切に設定する。手動で行う場合は公開鍵(`.pub` の方)の中身を `authorized_keys` に1行追記する。 ::: warning 設置するのは **公開鍵(`.pub`)** だけ。秘密鍵(`id_ed25519`)をサーバへコピーしてはいけない。秘密鍵は手元のクライアントから出さないのが大原則。 ::: ### 3. 接続を確認する ```bash $ ssh user@server ``` パスワードを聞かれずに(またはパスフレーズだけで)ログインできれば成功。 ## パスワード認証は無効化すべきか? {#disable-password} > **結論**: 鍵認証が確実に動くことを確認した上で、本番サーバはパスワード認証を無効化するのが定番。総当たりの入口を物理的に塞げる。 鍵認証を有効にしても、パスワード認証が生きていれば総当たり攻撃の入口は残る。`/etc/ssh/sshd_config` で無効化する。 ```bash # /etc/ssh/sshd_config PubkeyAuthentication yes PasswordAuthentication no ``` 設定後、SSHサービスを再起動する。 ```bash $ sudo systemctl restart ssh # Debian / Ubuntu 系 # RHEL 系はサービス名が sshd $ sudo systemctl restart sshd ``` ::: danger **締め出し事故に注意。** パスワード認証を無効化する前に、必ず **別のターミナルで鍵ログインが成功すること** を確認する。現在のセッションは開いたまま、新しい接続でテストするのが鉄則。鍵が動かない状態で無効化すると、自分もログインできなくなる。 ::: ## 秘密鍵を安全に扱うには? {#protect-key} > **結論**: パスフレーズを設定し、`~/.ssh` と秘密鍵のパーミッションを絞り、`ssh-agent` で入力負担を減らす。 鍵認証の安全性は秘密鍵を守れている前提に立つ。最低限、次を守る。 ### パスフレーズを設定する 秘密鍵にパスフレーズを掛けておけば、ファイルを盗まれても即座には使えない。`ssh-keygen` 実行時に設定するのが最も簡単。 ### パーミッションを絞る ```bash $ chmod 700 ~/.ssh $ chmod 600 ~/.ssh/id_ed25519 $ chmod 600 ~/.ssh/authorized_keys ``` OpenSSH は秘密鍵や `~/.ssh` の権限が緩いと、安全のため認証を拒否する。権限エラーで弾かれる定番ポイント。 ### ssh-agent で入力を減らす ```bash $ eval "$(ssh-agent -s)" $ ssh-add ~/.ssh/id_ed25519 ``` `ssh-agent` にパスフレーズ解除済みの鍵を預けておくと、セッション中はパスフレーズの再入力が不要になる。安全性(パスフレーズあり)と利便性を両立できる。 ::: warning やってはいけないこと: - 秘密鍵をメールやチャット、クラウドストレージで共有する - 秘密鍵を複数人で使い回す(誰の操作か追跡できなくなる) - パスフレーズなしの秘密鍵を持ち運ぶノートPCに置きっぱなしにする ::: ## まとめ:鍵認証への移行チェックリスト {#next} 鍵認証は「秘密をネットワークに出さない」という構造そのものが安全性の源泉である。移行は次の順で進めると事故りにくい。 - `ssh-keygen -t ed25519` で **パスフレーズ付き** の鍵を作る - `ssh-copy-id` で公開鍵を設置し、**別セッション** で鍵ログインを確認する - `~/.ssh` と秘密鍵の **パーミッションを 700 / 600** に絞る - 鍵ログインが確実に動いてから `PasswordAuthentication no` にする 次に読むと理解が深まる記事: - [rootとsudoの考え方](/articles/guides/root-vs-sudo-concept) - [ファイル転送の基本(scp / rsync)](/articles/tutorials/scp-rsync-basics) - [ファイルパーミッションの考え方](/articles/guides/file-permissions-mental-model) # 標準入出力とは - stdin / stdout / stderr の仕組み Source: https://penguin-gym-linux.com/articles/guides/stdin-stdout-stderr-concept ## 標準入出力って何だろう? {#intro} コマンドを学んでいくと、`> file` や `|`(パイプ)という記号によく出会う。この記号の裏側にあるのが「{標準入出力|ひょうじゅんにゅうしゅつりょく}(stdin / stdout / stderr)」という仕組みだ。 この記事では、3つの標準入出力の役割、出力が2種類に分かれる理由、リダイレクトとパイプの使い方を、リナとライニー先輩の会話で整理する。 ## この記事でわかること {#toc} - stdin / stdout / stderr の3つがそれぞれ何を担当しているか - なぜ出力が stdout と stderr の2種類に分かれているのか - 番号 0 / 1 / 2(ファイルディスクリプタ)の意味 - `>` `2>` `&>` で出力先を切り替える方法 - パイプ `|` と入力リダイレクト `<` の使い方 ## 1. 標準入出力とは何か? {#what} > **結論**: 標準入出力は、コマンドが持つ3つの通り道。データを受け取る口が1つ、結果を出す口とエラーを出す口が2つある。それぞれ stdin・stdout・stderr と呼ぶ。 ::: dialogue @lina: ライニー先輩、「標準入出力」という言葉を見ました。難しそうです… @linny: 名前は難しそうだけど、考え方は簡単だよ。工場をイメージして。材料を運び込む入り口、完成品を送り出す出口、不良品を知らせるアラームの出口——コマンドにもこの3つがあるんだ。 @lina: 工場みたいに、入り口と出口が3つあるんですね。 @linny: そう。名前も工場の仕組みと同じで、役割ごとに決まっている。 ::: - **標準入力(stdin。データの入り口)**: 材料が運び込まれる入り口にあたる - **標準出力(stdout。結果を出す出口)**: 完成品が流れる出口にあたる - **標準エラー出力(stderr。エラー専用の出口)**: 不良品を知らせるアラームにあたる ![コマンドを表す箱に stdin (0) から入り、stdout (1) と stderr (2) の2つの出口へ分かれていく流れ図](/images/diagrams/stdin-stdout-stderr-flow.svg){.article-diagram} {.diagram-figure} 図1: 入り口は stdin の1つ、出口は stdout と stderr の2つ。かっこ内の数字は「番号 0 / 1 / 2 って何?」で説明する。 {.diagram-caption} ::: dialogue @lina: 入り口が1つ、出口が2つ、ですね。 @linny: その通り。普段は意識しなくても、コマンドは必ずこの3つの通り道を持って動いている。 ::: ::: tip **「標準」の意味** 「標準」とは「特に指定しなければ使う、決まった通り道」という意味。例えば `ls` の結果が画面に出るのは、stdout の標準の行き先が画面(ターミナル)だからだ。この行き先を後から変える操作が、リダイレクトとパイプの正体だ。 ::: ## 2. なぜ出力が stdout と stderr に分かれているの? {#why-two} > **結論**: 「次に渡したい結果」と「人に読ませたいエラー」を混ぜないためだ。分けておけば、結果だけを保存したり、エラーだけを取り出したりできる。 ::: dialogue @lina: でも、なんで出口が2つもあるんですか? 一つにまとめたほうが楽じゃないですか? @linny: いい質問だね。たくさんのファイルを処理して、結果をファイルに保存する場面を考えよう。もし処理結果とエラーメッセージが同じ出口から出てきたら、保存したファイルにエラー文が混ざってしまう。 @lina: あ、たしかに。きれいな結果だけが欲しいのに、ゴミが混ざりますね。 @linny: そう。だから Linux は最初から出口を分けている。 ::: - **stdout** = 正常な結果(次の処理やファイルに渡したいデータ) - **stderr** = エラーや警告(人に知らせたいメッセージ) ::: dialogue @linny: こうしておけば、結果だけをファイルに保存しても、エラーは画面に出る。だから見落とさずに済むんだ。 @lina: なるほど! 役割で出口を分けているんですね。工場の入り口と出口の話と同じです。 ::: ::: highlight **さっきの工場の例で言うと** stdout は完成品を運ぶベルトコンベア、stderr は不良品を知らせるアラーム。完成品の箱(ファイル)にアラームの音を入れたくないから、出口を分けてある。 ::: ## 3. 番号 0 / 1 / 2 って何? {#fd} > **結論**: stdin・stdout・stderr には 0・1・2 という番号(ファイルディスクリプタ)が割り当てられている。`2>` の `2` はこの番号を指す。 ::: dialogue @lina: 設定例で `2>` という書き方を見ました。この `2` は何ですか? @linny: それは stderr の番号だ。3つの標準入出力には、それぞれ整数の{背番号|せばんごう}がついている。 @lina: 背番号、ですか…? @linny: 「ファイルディスクリプタ(FD と略されることもある)」という、コマンドが入出力の口を区別するための番号だよ。覚えるのは3つだけ。 ::: | 番号 | 名前 | 役割 | | ---- | ------ | ---------------------- | | 0 | stdin | 標準入力(入り口) | | 1 | stdout | 標準出力(通常の出口) | | 2 | stderr | 標準エラー出力(出口) | ::: tip **番号と記号の対応** リダイレクトの記号は、この番号と直結している。 - `>` は `1>` の省略形(stdout を送る) - `2>` は stderr を送る - `<` は `0<` の省略形(stdin から読む) 「`2>` の 2 は stderr の背番号」と覚えれば、記号の意味がわかる。 ::: ## 4. リダイレクトで出力先を変えるには? {#redirect} > **結論**: `>` で stdout を、`2>` で stderr をファイルへ送れる。両方まとめるなら `> file 2>&1` または `&> file`。 ::: dialogue @lina: 出口を分けているのはわかりました。結果だけをファイルに保存するには、どうすればいいですか? @linny: 「リダイレクト」を使うよ。出口の行き先を、画面からファイルに付け替える操作だ。まずは stdout をファイルに保存してみよう。 ::: ```bash ls /etc > filelist.txt ``` `>` を使うと、画面に出るはずだった stdout の内容が `filelist.txt` に書き込まれる。画面には何も表示されない。`>` は上書き、`>>` は追記になる。 ::: dialogue @lina: `>` は便利ですね。さっそく使ってみます。`ls /etc > notes.txt` を実行して、そのあとに `ls /home > notes.txt` も実行しました。 @linny: ちょっと待って。その2回目のコマンドで、1回目に保存した `notes.txt` の中身は消えてしまったよ。 @lina: え! 上書きされたんですか…! 前の結果を見返したかったのに… @linny: `>` は上書き専用なんだ。同じファイル名にもう一度 `>` を使うと、前の中身は消えてしまう。残しておきたいときは `>>`(追記)を使おう。 @lina: `>` は「保存」じゃなくて「置き換え」なんですね...! 次からは `>>` を使います。 ::: ::: warning **`>` による上書き事故に注意** GUI でファイルを保存するときは「同じ名前のファイルがあります。上書きしますか?」という確認が出ます。`>` には、この確認がありません。Enter を押した瞬間に、元の中身は消えます。ゴミ箱にも残りません。 安全に使うために、次の2つを習慣にしましょう。 - 前の内容を残したいときは `>>`(追記)を使う - 保存する前に `ls ファイル名` で、同じ名前のファイルがないか確かめる なお、本サイトの{仮想|かそう}ターミナルは学習用です。ここで `>` を試しても、あなたのパソコンのファイルは壊れません。安心して練習してください。 ::: エラーだけを保存したいときは `2>` を使う。 ```bash ls /not-exist 2> errors.txt ``` このとき画面には何も表示されない。`errors.txt` の中に、次の1行が記録される。 ```output ls: cannot access '/not-exist': No such file or directory ``` ::: dialogue @lina: stdout は `>`、stderr は `2>` ですね。両方を1つのファイルにまとめたいときは、どうすればいいですか? @linny: そのときは `2>&1` を付け足す。「stderr(2)を、stdout(1)と同じ行き先に流す」という意味だよ。 ::: ```bash command > output.txt 2>&1 ``` ::: warning **`2>&1` は順番が大事** `2>&1` は「stderr を、その時点の stdout の行き先に合わせる」という意味だ。そのため `> output.txt` を**先に**書く。順番を逆にして `command 2>&1 > output.txt` と書くと、stderr は古い行き先(画面)のままになる。順番を気にしたくないなら、`&> file` という書き方でも両方まとめて送れる。ただし、これは bash(Ubuntu などで標準的に使われるシェル。打ったコマンドを受け取って実行するプログラム)専用の書き方だ。 ```bash command &> output.txt ``` ::: ## 5. パイプと標準入力はどう使う? {#pipe-stdin} > **結論**: パイプ `|` は左コマンドの stdout を右コマンドの stdin につなぐ。`<` はファイルの中身を stdin として渡す。 ::: dialogue @lina: stdin(入り口)は、いつ使うんですか? あまり意識したことがなくて… @linny: 実は、パイプを使うたびに stdin が働いているんだ。パイプ `|` は、左のコマンドの stdout を、右のコマンドの stdin に直接つなぐ仕組みだよ。 ::: ```bash ls /etc | grep conf ``` この例では、`ls /etc` の出力(stdout)が、そのまま `grep conf` の入力(stdin)に流れ込む。`grep` は入力の中から `conf` を含む行だけを拾い出す。 ::: highlight **流れのイメージ** ```output ls /etc ──stdout──▶ | ──stdin──▶ grep conf ──stdout──▶ 画面 ``` パイプは、前のコマンドの出口と、次のコマンドの入り口を直結するホースのようなものだ。 ::: ファイルの中身を stdin として渡したいときは `<` を使う。 ```bash sort < names.txt ``` これは `names.txt` の中身を `sort` の標準入力に流し込み、並べ替えて表示する。結果は `sort names.txt` と同じだ。でも、「ファイルを stdin につなぐ」という標準入力の動きが分かりやすい例になっている。 ::: tip パイプやリダイレクトをもっと実践的に練習したいなら、[パイプとリダイレクトの基本](/articles/tutorials/pipe-redirect-basics) で手を動かしながら学べる。出力を画面とファイルの両方に出したいときは [tee コマンドの使い方](/articles/tutorials/tee-basics) も便利だ。 ::: ## ミニ課題 {#exercises} > **結論**: stdout・stderr のリダイレクトとパイプを、実際に手を動かして確認する。 ::: dialogue @linny: それじゃ、3つ課題を出すよ。コマンド名がすぐに浮かばなくても、ヒントを順番に開けば大丈夫。 @lina: やってみます! ::: **課題1**: `ls /etc` の結果だけを `etc-list.txt` に保存しよう(画面には何も表示しない) :::details ヒント1(方向づけ)を見る 出力の行き先を、画面からファイルに変える記号がある。上書きでよい。 ::: :::details ヒント2(コマンド名)を見る `>` を使う。 ::: :::details 答えを見る ```bash ls /etc > etc-list.txt ``` 画面には何も出ず、`etc-list.txt` に `/etc` の一覧が保存される。 ::: **課題2**: 存在しないディレクトリを `ls` で指定し、そのエラーメッセージだけを `err.txt` に保存しよう(画面にはエラーを出さない) :::details ヒント1(方向づけ)を見る 出力ではなく、エラー専用の出口だけをファイルに向ける記号がある。 ::: :::details ヒント2(コマンド名)を見る `2>` を使う。 ::: :::details 答えを見る ```bash ls /no-such-dir 2> err.txt ``` 画面には何も出ず、`err.txt` に `ls: cannot access '/no-such-dir': No such file or directory` が記録される。 ::: **課題3**: `ls /etc` の結果から、`conf` を含む行だけを画面に表示しよう(ファイルには保存しない) :::details ヒント1(方向づけ)を見る 前のコマンドの出力を、そのまま次のコマンドの入力として渡すしくみを使う。 ::: :::details ヒント2(コマンド名)を見る パイプ `|` と `grep` を使う。 ::: :::details 答えを見る ```bash ls /etc | grep conf ``` `ls /etc` の出力から `conf` を含む行だけが画面に表示される。 ::: ## まとめ {#summary} - 標準入出力は stdin(入力)/ stdout(通常の出力)/ stderr(エラー出力)の3つの通り道 - 出力が2種類あるのは「結果」と「エラー」を混ぜないため - 3つには番号 0 / 1 / 2(ファイルディスクリプタ)が割り当てられている - `>` で stdout、`2>` で stderr をファイルへ送れる(両方なら `> file 2>&1` または `&> file`) - パイプ `|` は stdout を次の stdin につなぎ、`<` はファイルを stdin として渡す ## 次に読む {#next} - [パイプとリダイレクトの基本](/articles/tutorials/pipe-redirect-basics) - [tee コマンドの使い方](/articles/tutorials/tee-basics) - [シェル(shell)とは何か](/articles/guides/what-is-a-shell) # ターミナルの使い方 - コマンドライン入門 Source: https://penguin-gym-linux.com/articles/guides/terminal-basics 「黒い画面に白い文字がズラッと並んでいて、何が何だか分からない...」そんな風にターミナルを避けていませんか?この記事では、ライニー先輩とリナの対話を通じて、**ターミナルの基本操作**を一緒に学んでいきましょう。 ## この記事でわかること {#what-you-will-learn} - ターミナルとは何か、なぜエンジニアが使うのかがわかる - プロンプトの読み方と `pwd`・`ls`・`cd` の基本3コマンドを習得できる - よくあるエラーへの対処法とTab補完などの便利な機能を覚えられる ## 1. 導入:ターミナルって何? {#intro} > **結論**: ターミナルとは文字で命令を送るアプリで慣れるとマウス操作より速く正確に作業できる。 :::dialogue @lina: ライニー先輩、ターミナルを開いてみたんですけど...黒い画面に何か文字が表示されてて、どうすればいいか分からなくて... @linny: 大丈夫、最初はみんなそうだよ。まずはターミナルが何なのかを説明するね。 ::: :::tip **ターミナルとは** **ターミナル**は、コンピューターに**文字で命令を送る**ためのアプリケーションです。マウスでクリックする代わりに、キーボードでコマンド(命令)を入力して操作します。 ::: :::dialogue @lina: マウスで操作するより難しそうですね... @linny: 最初はそう感じるかもしれないね。でも慣れると、マウス操作より**速くて正確**に作業できるようになるよ。例えば、100個のファイル名を変えるとき、マウスでやると1時間かかることが、ターミナルなら数秒で終わるんだ。 @lina: えっ、そんなに違うんですか! ::: ### ターミナルのメリット - **速い**:慣れるとマウス操作より高速 - **自動化できる**:同じ作業を繰り返し実行可能 - **正確**:細かい設定を正確に指定できる - **サーバーで必須**:多くのサーバーにはGUIがない ## 2. プロンプトを読み解こう {#prompt} > **結論**: プロンプトはuser@host:ディレクトリの構成で~はホームを.は現在地を表す記号だ。 :::dialogue @lina: 画面に `user@computer:~$` って表示されてるんですけど、これは何ですか? @linny: これは**プロンプト**と呼ばれるものだよ。「コマンドを入力してね」という合図なんだ。プロンプトには大切な情報が含まれているから、読み方を覚えよう。 @lina: 大切な情報、ですか? @linny: 例えば「今いるディレクトリ」だよ。ディレクトリは、WindowsやMacでいう「フォルダ」のことなんだ。Linuxでは「ディレクトリ」と呼ぶことが多いよ。 ::: ### プロンプトの構成 ``` yamada@penguin-gym:~/Documents$ ``` | 部分 | 意味 | | ------------- | ---------------------------------- | | `yamada` | ユーザー名(今ログインしている人) | | `@` | 区切り文字 | | `penguin-gym` | コンピューター名(ホスト名) | | `:` | 区切り文字 | | `~/Documents` | 今いる場所(ディレクトリ) | | `$` | 一般ユーザーの印(#ならroot) | :::dialogue @lina: `~`って何ですか? @linny: `~`は**ホームディレクトリ**を表す記号だよ。ホームディレクトリはユーザーごとの「自分の部屋」みたいな場所で、`/home/ユーザー名`のことなんだ。 ::: ### 覚えておきたい特殊記号 | 記号 | 意味 | | ---- | ---------------------------- | | `~` | ホームディレクトリ | | `.` | 現在のディレクトリ | | `..` | 親ディレクトリ(一つ上) | | `/` | ルートディレクトリ(最上位) | ## 3. 最初の3つのコマンド {#first-commands} > **結論**: pwd・ls・cdの3コマンドで現在地確認・中身確認・移動というターミナル基本フローを習得する。 :::dialogue @lina: コマンドって覚えることがいっぱいありそうで不安です... @linny: 最初は3つだけ覚えれば大丈夫。`pwd`、`ls`、`cd`だよ。この3つで「今どこにいるか」「何があるか」「どこに行くか」が分かるんだ。 ::: ### pwd - 今いる場所を確認 :::dialogue @linny: `pwd`は「Print Working Directory」の略で、今いるディレクトリを表示するコマンドだよ。 ::: ```bash $ pwd /home/yamada ``` :::dialogue @lina: あ、今 `/home/yamada` にいるってことですね! @linny: その通り。迷子になったら `pwd` を打てば、すぐに現在地が分かるよ。 ::: ### ls - ファイル一覧を表示 :::dialogue @linny: `ls`は「list」の略で、今いる場所にあるファイルやフォルダの一覧を表示するよ。 ::: ```bash $ ls Documents Downloads Pictures file.txt ``` :::dialogue @lina: フォルダが4つあるってことですか? @linny: `Documents`、`Downloads`、`Pictures`はフォルダ(ディレクトリ)で、`file.txt`はファイルだね。`-l`オプションを付けると詳細が見られるよ。 ::: ```bash $ ls -l drwxr-xr-x 2 yamada yamada 4096 Jan 24 10:00 Documents drwxr-xr-x 2 yamada yamada 4096 Jan 24 10:00 Downloads drwxr-xr-x 2 yamada yamada 4096 Jan 24 10:00 Pictures -rw-r--r-- 1 yamada yamada 52 Jan 24 10:00 file.txt ``` ### cd - ディレクトリを移動 :::dialogue @linny: `cd`は「change directory」の略で、別のディレクトリに移動するコマンドだよ。 ::: ```bash $ cd Documents $ pwd /home/yamada/Documents ``` :::dialogue @lina: `cd Documents`で`Documents`フォルダに入れたんですね! @linny: そうだね。`cd`がコマンド本体で、そのあとの`Documents`は「{引数|ひきすう}」と呼ぶんだ。引数はコマンドに渡す「対象」のことで、コマンドとの間は半角スペースで区切るよ。 @lina: コマンドと引数は、半角スペースで区切るんですね。 @linny: そのとおり。一つ上のディレクトリに戻りたいときは `cd ..`、ホームディレクトリに戻りたいときは `cd ~` または単に `cd` だけでOKだよ。 ::: ```bash $ cd .. # 一つ上に戻る $ cd ~ # ホームに戻る $ cd # ホームに戻る(省略形) ``` ## 4. 便利なショートカット {#shortcuts} > **結論**: Tab補完と矢印キーの履歴機能を使えば入力が速くなりCtrl+Cで緊急中止できる。 :::dialogue @lina: 毎回コマンドを全部打つのは大変じゃないですか? @linny: いい質問だね!ターミナルには便利なショートカットがあるんだ。特に**Tab{補完|ほかん}**と**{履歴|りれき}機能**は必須だよ。 ::: ### Tab補完(超重要) :::dialogue @linny: 途中まで入力して Tab キーを押すと、自動で補完してくれるんだ。 ::: ```bash $ cd Doc[Tab] # → cd Documents/ に補完される ``` :::dialogue @lina: わあ、便利!タイプミスも減りそうですね。 @linny: その通り。候補が複数あるときは Tab を2回押すと一覧が出るよ。 ::: ### よく使うショートカット | ショートカット | 機能 | | -------------- | ----------------------------- | | `Tab` | 自動補完 | | `↑` / `↓` | コマンド履歴を遡る/進む | | `Ctrl + C` | 実行中のコマンドを中止 | | `Ctrl + A` | 行の先頭に移動 | | `Ctrl + E` | 行の最後に移動 | | `Ctrl + U` | カーソルから行頭まで削除 | | `Ctrl + L` | 画面をクリア(`clear`と同じ) | :::dialogue @lina: `Ctrl + C` は覚えておいた方がいいですか? @linny: **絶対に覚えておいて!** コマンドが終わらなくなったときの{緊急|きんきゅう}脱出ボタンだよ。困ったら `Ctrl + C` と覚えておこう。 ::: ## 5. よくあるエラーと対処法 {#common-errors} > **結論**: No such file or directoryはスペルとpwdで現在地を確認し原因を特定して対処する。 :::dialogue @lina: `cd Documents` って打ったら「No such file or directory」って出たんですけど...えっ、何がダメだったんでしょう? @linny: それはよくあるエラーだね。いくつか原因が考えられるから、一緒に見ていこう。 ::: ### エラー1: No such file or directory ```bash $ cd Documents bash: cd: Documents: No such file or directory ``` ### 原因と対処法 :::tip 1. **スペルミス**: 大文字・小文字は区別される。`documents`と`Documents`は別物 2. **存在しない**: まず`ls`で存在を確認する 3. **現在地が違う**: `pwd`で今いる場所を確認する ::: :::dialogue @lina: `pwd`で確かめたら、私はホームじゃなくて別の場所にいました!だから`Documents`が見つからなかったんですね。 @linny: そのとおり。同じ`cd Documents`でも、今いる場所が違えば結果は変わるんだ。エラーが出たら、まず`pwd`と`ls`で現在地と中身を確認しよう。 @lina: なるほど。エラーは「入力が間違っている」だけじゃなくて、「今いる場所が違う」ときにも出るんですね。 ::: ### エラー2: command not found ```bash $ lss bash: lss: command not found ``` :::dialogue @linny: これはコマンド名のタイプミスだね。`lss`ではなく`ls`が正しいよ。 ::: ### エラー3: Permission denied ```bash $ cd /root bash: cd: /root: Permission denied ``` :::dialogue @lina: これはどういう意味ですか? @linny: 「権限がない」というエラーだよ。`/root`は管理者専用の場所だから、一般ユーザーは入れないんだ。権限については別の記事で詳しく説明するね。 ::: ### 困ったときの確認手順 1. `pwd` で今いる場所を確認 2. `ls` で中身を確認 3. コマンドのスペルを確認(大文字・小文字に注意) 4. `man コマンド名` でヘルプを見る ## ミニ課題 {#exercises} > **結論**: pwd・ls・cdを組み合わせてミニ課題を実践しターミナル操作の基本フローを体で覚える。 :::dialogue @linny: それじゃあ、今日学んだことを確認するためにミニ課題をやってみよう! ::: ### 課題1: 現在地を確認しよう {#exercise-1} **やること**: 今いるディレクトリ(現在地)を表示するコマンドを実行しよう。 :::details ヒント1(方向づけ)を見る 「今、自分がどこにいるか」を画面に表示してくれるコマンドを思い出してみよう。 ::: :::details ヒント2(コマンド名)を見る 使うのは `pwd` だよ。「Print Working Directory」の略だったね。 ::: :::details 答えを見る ```bash $ pwd ``` ```output /home/yamada ``` ::: ### 課題2: ファイル一覧を見てみよう {#exercise-2} **やること**: 今いる場所にあるファイルやフォルダを一覧表示しよう。慣れたら `-l` オプションで詳細表示も試してみよう。 :::details ヒント1(方向づけ)を見る 「今いる場所に何があるか」を一覧で見せてくれるコマンドを探そう。 ::: :::details ヒント2(コマンド名)を見る 使うのは `ls` だよ。「list(一覧)」の略だったね。 ::: :::details 答えを見る ```bash $ ls ``` ```output Documents Downloads Pictures file.txt ``` ::: ### 課題3: ディレクトリを移動してみよう {#exercise-3} **やること**: 存在するディレクトリに移動して、移動できたかどうかを確認しよう。その後、元の場所に戻ろう。 :::details ヒント1(方向づけ)を見る まず移動先が本当にあるか確認して、それから移動、最後に今いる場所を確認、の3ステップだよ。戻るときも同じ考え方でいいよ。 ::: :::details ヒント2(コマンド名)を見る `ls` で確認 → `cd`(Change Directory の略)で移動 → `pwd` で確認、の順だよ。戻るときは `cd ..` を使うよ。 ::: :::details 答えを見る ```bash $ cd Documents $ pwd ``` ```output /home/yamada/Documents ``` ```bash $ cd .. $ pwd ``` ```output /home/yamada ``` ::: :::dialogue @lina: できました! `cd` する前に `ls` で確認すればエラーを防げますね。 @linny: 完璧だね!それがターミナル操作の基本フローだよ。 ::: ## 振り返り {#review} > **結論**: pwd・ls・cd・Tab補完・Ctrl+Cを押さえればターミナル操作の土台が完成する。 :::dialogue @lina: 今日はターミナルの基本を学びました。`pwd`で今いる場所、`ls`で中身、`cd`で移動、ですね! @linny: その通り!そして困ったら `Ctrl + C` で中止、`Tab` で補完。この基本を押さえておけば、これからどんどんコマンドを覚えていけるよ。 @lina: 黒い画面も、なんだか怖くなくなってきました! @linny: 素晴らしいね。次は実際にファイルを作ったり削除したりするコマンドを学んでいこう! ::: ## 今日の3行まとめ {#summary} > **結論**: pwd・ls・cd・Tab補完・Ctrl+Cの5点を3行に凝縮してターミナル基礎を定着させる。 1. `pwd`で今いる場所、`ls`で中身、`cd`で移動 - この3つが基本 2. 困ったら `Ctrl + C` で中止、`Tab` で入力補完 3. エラーが出たら `pwd` と `ls` で現状確認から始める ## 次に読む {#next-steps} > **結論**: 次はPenguin Gymの仮想ターミナルや基本コマンド記事でpwd・ls・cdを実践する。 ターミナルの基礎を理解したら、次は実際のコマンドを練習してみましょう。 - [Penguin Gym Linuxで実践練習](/terminal?lesson=basic-pwd) - [基本コマンドを学ぶ](/articles/tutorials/basic-commands) - [ファイル操作の基礎](/articles/tutorials/file-operations-basics) # 文字コード入門 - UTF-8と文字化けの仕組み Source: https://penguin-gym-linux.com/articles/guides/text-encoding-utf8 ## この記事で解決できること {#intro} - **文字コード**と **UTF-8** が何を指すのか、用語の関係を整理できる - 「文字化け(mojibake)」が **なぜ起きるのか** を仕組みから理解できる - Linux で文字コードを **確認・変換する型**(`file` / `iconv` / `nkf` / `locale`)が身につく ::: tip **結論(先に要点)** - 文字コード=「文字」と「バイト列」の対応表。**符号化文字集合(Unicode)** と **符号化方式(UTF-8)** は別物 - UTF-8 は ASCII 互換・可変長(1〜4バイト)で、現在の Web/Linux の事実上の標準 - 文字化けは **書いたときと読むときの符号化方式が食い違う** ときに起きる - 確認は `file -i` / `nkf -g`、変換は `iconv` が基本の型 ::: ## 文字コードとは? {#what} > **結論**: 文字コードは「文字」と「バイト列」を対応づける規則。コンピュータはバイトしか扱えないため、文字を保存・送信するには必ず符号化が必要になる。 コンピュータが内部で扱えるのはバイト(0〜255 の数値)だけ。「あ」や「A」という**文字そのもの**は保存できない。そこで「どの文字をどのバイト列で表すか」を決めた対応表が必要になる。これが**文字コード**。 ここで混同しやすい 2 つの層を分けて理解すると、文字化けの理屈が一気に明確になる。 | 層 | 役割 | 例 | | ------------------------------- | ---------------------------------------------- | -------------------------------- | | 符号化文字集合(character set) | 文字に一意の番号(コードポイント)を割り当てる | Unicode、JIS X 0208 | | 符号化方式(encoding) | コードポイントを実際のバイト列に変換する規則 | UTF-8、UTF-16、Shift_JIS、EUC-JP | 例えば「あ」は Unicode で `U+3042` という番号を持つ。この `U+3042` を**どんなバイト列にするか**は符号化方式によって変わる。 ```output 文字「あ」 = Unicode コードポイント U+3042 UTF-8 では: E3 81 82 (3バイト) UTF-16 では: 30 42 (2バイト) EUC-JP では: A4 A2 (2バイト) ``` ::: highlight **ポイント**: 同じ「あ」でも符号化方式が違えばバイト列は別物になる。だから「どの方式で書かれたか」を取り違えると、正しく文字に戻せなくなる。これが文字化けの正体。 ::: ## UTF-8 とは?なぜ主流なのか {#utf8} > **結論**: UTF-8 は Unicode をバイト列にする符号化方式の一つ。ASCII 互換・可変長・バイト順問題なしという特性から、Web と Linux の事実上の標準になっている。 UTF-8 は Unicode の全文字を**可変長(1〜4 バイト)**で表現する符号化方式。主流になった理由は次の特性にある。 - **ASCII 互換**: `U+0000`〜`U+007F`(英数字・記号)は 1 バイトで、ASCII とまったく同じ並び。既存の英語前提ツールがそのまま動く - **可変長で無駄が少ない**: 英数字は 1 バイト、日本語などは 2〜3 バイト。固定長より省容量になりやすい - **バイト順(エンディアン)問題がない**: UTF-16 と違い BOM(バイト順マーク)に依存しない - **自己同期性**: 各バイトの先頭ビットで「先頭バイトか継続バイトか」が判別でき、途中から読んでも文字境界を復元しやすい ```bash # 文字列のバイト数を確認(UTF-8 環境) echo -n "あ" | wc -c ``` ```output 3 ``` ::: warning **UTF-8 と Unicode は同義ではない**。Unicode は「文字に番号を振る規格」、UTF-8 は「その番号をバイトにする方式の一つ」。「UTF-8 で保存」は正しいが、「Unicode で保存」は本来あいまいな言い方になる。 ::: ### BOM 付き UTF-8 の注意 UTF-8 の先頭に **BOM**(`EF BB BF` の 3 バイト)が付くことがある。Windows のメモ帳などが付与するケースがあり、シェルスクリプトの先頭に紛れ込むと `#!/bin/bash` が認識されず実行エラーになる。Linux では **BOM なし UTF-8** が基本。 ## 文字化け(mojibake)はなぜ起きるのか? {#mojibake} > **結論**: 文字化けは、書いたときの符号化方式と読むときの符号化方式が食い違うことで起きる。バイト列は壊れておらず、解釈のルールがずれているだけ。 文字化け(海外でも _mojibake_ で通じる)は、**バイト列を間違った符号化方式で解釈する**と発生する。典型例は次の 3 つ。 1. **Shift_JIS で書いたファイルを UTF-8 として開く**(逆も同様) 2. **UTF-8 のファイルを Latin-1(ISO-8859-1)として開く**(「é」のような並びになる) 3. **端末のロケールがファイルの符号化と一致していない** 重要なのは、**元のバイト列は壊れていない**という点。解釈の規則を正しく合わせれば、多くの場合は元に戻せる。 ```output 正しい: こんにちは (UTF-8 のバイト列を UTF-8 で解釈) 化ける: ã“ã‚“ã«ã¡ã¯ (UTF-8 のバイト列を Latin-1 で解釈) ``` ::: tip 文字化けを見たら「ファイルが壊れた」と慌てる前に、**「何の方式で書かれ、何の方式で読まれているか」**を切り分けるのが先決。原因の大半はこの不一致。 ::: ## 文字コードを確認・変換するには? {#commands} > **結論**: 確認は `file -i` か `nkf -g`、変換は `iconv` が基本の型。端末側の表示問題は `locale` と `LANG` を疑う。 ### ファイルの符号化を推定する ```bash # MIME 形式で文字コードを表示 file -i notes.txt ``` ```output notes.txt: text/plain; charset=utf-8 ``` `file` はあくまで**推定**であり、短いファイルや曖昧なバイト列では `unknown-8bit` などになることもある。日本語の判定には `nkf -g`(guess)が有効。 ```bash # 日本語エンコーディングを判定(nkf は要インストール) nkf -g legacy.txt ``` ```output Shift_JIS ``` ### 符号化方式を変換する `iconv` は「変換元(`-f`)」と「変換先(`-t`)」を指定して変換する。 ```bash # Shift_JIS から UTF-8 へ変換して保存 iconv -f SHIFT_JIS -t UTF-8 legacy.txt -o utf8.txt ``` ```bash # 利用可能な符号化方式の一覧を確認 iconv -l ``` ::: warning 変換元の指定を間違えると、化けたまま別の方式に変換され**復旧が難しくなる**ことがある。変換前に `file -i` / `nkf -g` で必ず元の符号化を確認し、元ファイルは残しておくこと。 ::: ### 端末・ロケールの確認 ファイル自体は UTF-8 なのに端末で化ける場合、**ロケール設定**が原因のことが多い。 ```bash # 現在のロケールを確認 locale ``` ```output LANG=en_US.UTF-8 LC_CTYPE="en_US.UTF-8" ... ``` `LANG` や `LC_CTYPE` が `*.UTF-8` になっていれば端末は UTF-8 として表示する。`C` や `POSIX` だと日本語が化けることがある。一時的に切り替えるには次のようにする。 ```bash export LANG=en_US.UTF-8 ``` ## よくあるトラブルと対処 {#troubleshooting} > **結論**: 「ファイルは正しいか」「端末は正しいか」を分けて切り分ける。多くは符号化の不一致かロケール設定で説明できる。 | 症状 | 主な原因 | 対処 | | -------------------------- | ------------------------ | ----------------------------------- | | 開いたテキストが全部化ける | 符号化方式の取り違え | `file -i` で確認 → `iconv` で変換 | | 端末でだけ日本語が化ける | ロケールが UTF-8 でない | `locale` 確認 → `LANG=...UTF-8` | | スクリプト先頭でエラー | BOM 付き UTF-8 | BOM を除去して保存し直す | | ファイル名が化ける | 作成時と別の符号化で表示 | ロケールを合わせる/`convmv` で変換 | ::: tip **実践のコツ**: 迷ったらまず `file -i`。これで「ファイル側の問題」か「端末側の問題」かをほぼ切り分けられる。新規ファイルは常に **BOM なし UTF-8** で作るのが最も事故が少ない。 ::: ブラウザ上の仮想ターミナルで `echo` や `wc -c` を実際に試したい場合は、[学習ターミナル](/terminal) で手を動かしながら確認できる。 ## まとめ {#summary} 文字コードは「文字とバイト列の対応表」であり、**符号化文字集合(Unicode)** と **符号化方式(UTF-8 など)** を分けて捉えるのが理解の鍵。文字化けはバイト列の破壊ではなく**解釈の不一致**で起きるため、`file -i` / `nkf -g` で符号化を確認し、`iconv` で変換、端末側は `locale` を疑う、という型で多くは解決できる。新規ファイルは BOM なし UTF-8 で統一しておくのが最も安全。 - [標準入出力とは - stdin / stdout / stderr の仕組み](/articles/guides/stdin-stdout-stderr-concept) - [ディレクトリ構造の全体像 - Linuxファイルシステムを理解する](/articles/guides/linux-directory-structure) - [man/info/--help を使い分ける - 公式情報の引き方](/articles/guides/man-info-help) # シェル(shell)とは何か - bash / zsh / sh とカーネルの関係 Source: https://penguin-gym-linux.com/articles/guides/what-is-a-shell ## シェルって何だろう? {#intro} 「bash」「zsh」「シェルスクリプト」——コマンドを学び始めると、こういう言葉によく出会う。でも「シェルそのものが何なのか」は意外と説明されないまま進んでしまいがち。 この記事では、シェル(shell)とは何か、bash / zsh / sh は何が違うのか、そしてシェルとカーネルがどう協力しているのかを、リナとライニー先輩の会話でやさしく整理していく。 ## この記事でわかること {#toc} - シェル(shell)が「人間とOSの{通訳|つうやく}」である理由 - シェルとカーネルの役割分担 - bash / zsh / sh の違いと、どれを使えばいいか - 今自分が使っているシェルを確認するコマンド - ログインシェルを安全に切り替える方法 ## 1. シェルとは何か? {#what} > **結論**: シェルは、あなたが打ったコマンドを受け取ってOSの中心(カーネル)に伝え、結果を返す「通訳役」のプログラム。 ::: dialogue @lina: ライニー先輩、そもそも「シェル」って何ですか? bashとかzshとか、名前は聞くんですけど… @linny: いい質問だね。シェル(shell)は、リナが打ったコマンドを受け取って、OSの中心にいる「カーネル」に伝える通訳プログラムなんだ。 @lina: 通訳…? @linny: そう。リナは「ls」って打つよね。でもカーネルは「ls」という文字をそのまま理解するわけじゃない。シェルがその文字を解釈して、「ファイル一覧を出すプログラムを実行して」とカーネルに{依頼|いらい}する。そして結果をリナが読める形で画面に返してくれる。 @lina: なるほど! 私とOSの間に立ってくれてるんですね。 @linny: その通り。「shell(貝殻)」という名前も、カーネル(kernel=核)を包む外側、という意味から来ているんだ。 ::: ::: tip **シェル=コマンドインタプリタ** シェルの正式な役割は「コマンドインタプリタ」。打ち込まれた文字列を{解釈|かいしゃく}し、対応するプログラムを起動する。ターミナル(画面)とシェル(解釈役)は別物だと意識すると混乱しにくい。 ::: ## 2. シェルとカーネルの関係は? {#kernel} > **結論**: カーネルがハードウェアを直接管理する「核」、シェルはその核と人間をつなぐ「外側の{窓口|まどぐち}」。役割が分かれている。 ::: dialogue @lina: カーネルって、さっきから出てくるけど何者なんですか? @linny: カーネルはOSの一番中心にある部分。メモリやCPU、ディスクといったハードウェアを直接管理している、まさに「核」だね。 @lina: じゃあ、私たちが直接カーネルに話しかけたらダメなんですか? @linny: 直接は難しいんだ。カーネルは強力すぎて、扱いを間違えるとシステム全体が壊れかねない。だから間にシェルを置いて、安全に・人間にわかる言葉でやり取りするようになっている。 @lina: {受付|うけつけ}の人を通して偉い人にお願いするみたいな感じですね。 @linny: まさにそのイメージ。流れを整理するとこうなるよ。 ::: ```output あなた → ターミナル → シェル → カーネル → ハードウェア (入力) (表示) (解釈) (実行) (CPU/メモリ/ディスク) ``` ::: highlight **3つの登場人物** - **ターミナル**: 文字を入力・表示する「画面」 - **シェル**: コマンドを解釈してカーネルに渡す「通訳」 - **カーネル**: ハードウェアを動かすOSの「核」 ::: ## 3. bash / zsh / sh は何が違う? {#bash-zsh-sh} > **結論**: shは最も古い基本形、bashはその後継で最も普及、zshはbash互換で機能が豊富。中身は同じ「シェル」の仲間。 ::: dialogue @lina: で、bashとかzshって何なんですか? ぜんぶシェルなんですよね? @linny: そう、ぜんぶ「シェルの種類」なんだ。同じ通訳でも、得意なことや書き方が少しずつ違う。代表的な3つを見てみよう。 @lina: お願いします! @linny: まず **sh**。これは一番古くからある基本のシェルで、「sh」は shell の略。今は多くの環境で、より新しいシェルへの別名(リンク)になっていることも多いよ。 @lina: bashは? @linny: **bash** は "Bourne Again SHell" の略で、sh を強化した{後継|こうけい}版。多くのLinuxで標準シェルとして採用されていて、いま一番よく使われている。困ったらまず bash、で大丈夫。 @lina: じゃあ zsh は? @linny: **zsh**(Z Shell)は bash とほぼ互換性がありつつ、入力補完やテーマがすごく強力なシェル。macOS の標準シェルにもなっているね。見た目や使い勝手を育てたい人に人気だよ。 ::: | シェル | 読み方 | 特徴 | 立ち位置 | | ------ | ------------ | ------------------- | ---------------------------- | | sh | エスエイチ | 最も基本的・最小限 | 古典・スクリプトの基準 | | bash | バッシュ | sh の後継、最も普及 | 多くのLinuxで標準 | | zsh | ゼットシェル | bash 互換+高機能 | macOS 標準・カスタム派に人気 | ::: tip **まず迷ったら bash** 初心者のうちは bash で十分。Web上の解説やコマンド例も bash 前提のものが多く、学習がスムーズに進む。慣れてきたら zsh などへ乗り換えを検討すればいい。 ::: 各シェルのより詳しい比較は [bash/zsh/fish の違いと選び方](/articles/guides/shell-comparison) でも解説している。 ## 4. いま使っているシェルを確認するには? {#check} > **結論**: `echo $SHELL` でログインシェル、`ps -p $$` で今動いているシェルがわかる。 ::: dialogue @lina: 私、自分がどのシェルを使ってるのか分かってないかも… @linny: 確認するコマンドがあるよ。まずはログインシェル(ログイン時に起動するシェル)を見てみよう。 ::: ```bash echo $SHELL ``` ```output /bin/bash ``` `$SHELL` は「変数」の一つだ。変数とは、値を入れておく名前付きの箱のこと。この箱には、ログイン時に起動するシェルのパス(ファイルやフォルダの住所。`/bin/bash` のように `/` で区切って書く)が入っている。上の例なら bash を使っている。 いま実際に動いているシェルを確認したいときは次のコマンドを使う。`ps` は、いま動いているプログラムを一覧表示するコマンドだ。 ```bash ps -p $$ ``` ```output PID TTY TIME CMD 2451 pts/0 00:00:00 bash ``` ::: tip 「プロセス」とは、いま動いているプログラム 1 つ分のこと。プロセスID はその 1 つずつに付く通し番号だ。`$$` は「いま動いているシェル自身のプロセスID」を表す特別な変数で、`ps -p $$` を使うと、そのシェルが bash なのか zsh なのかを直接確認できる。`echo $SHELL`(設定上のシェル)と結果が違うこともあるので、両方知っておくと安心。 ::: 使える(インストール済みの)シェル一覧は次で確認できる。 ```bash cat /etc/shells ``` ```output /bin/sh /bin/bash /usr/bin/bash /bin/zsh /usr/bin/zsh ``` ::: highlight [ターミナルの使い方](/articles/guides/terminal-basics) のページでは、コマンドの打ち方そのものを基礎から練習できる。シェルの確認コマンドも実際に試してみよう。 ::: ## 5. ログインシェルを変えるには? {#change} > **結論**: `chsh -s` で変更できる。指定するシェルは `/etc/shells` に載っているものだけにする。 ::: dialogue @lina: bash から zsh に変えてみたくなりました! どうやるんですか? @linny: `chsh`(change shell)コマンドを使うよ。ただし、変更前にいくつか注意点があるんだ。 ::: まず、変えたいシェルが `/etc/shells` に載っているか確認する(載っていないシェルは指定できない)。確認できたら次のように変更する。 ```bash chsh -s /bin/zsh ``` 変更は次回ログインから{反映|はんえい}される。 ::: warning **安全に試すコツ** - `/etc/shells` に**載っていないパス**を指定しない(ログインできなくなる原因になる) - いきなり変更せず、まず `zsh` とだけ打って一時的に起動し、使い心地を試してから決める - 元に戻したいときは `chsh -s /bin/bash` で bash に戻せる ::: ::: dialogue @lina: なるほど。まず `zsh` って打って試してから、気に入ったら chsh で本採用ですね! @linny: 完璧な順番だね。これでリナも、自分の手元のシェルを自信を持って選べるようになったよ。 ::: ## リナ、echoでつまずく {#first-try} > **結論**: `$SHELL`は大文字と小文字を区別する。小文字の`$shell`と打つと変数が見つからない。何も表示されない。 ::: dialogue @lina: 教わったとおりにやってみます。`echo $shell`っと…あれ、何も出ません。 ::: ```bash echo $shell ``` ```output ``` ::: dialogue @lina: 画面が空っぽです。どうしてでしょう? @linny: 惜しい! 正しい変数名は大文字の`$SHELL`だよ。`$shell`という変数はないから、何も出なかったんだ。 ::: ```bash echo $SHELL ``` ```output /bin/bash ``` ::: dialogue @lina: 大文字にしたら出ました! ミスなく打つのが大事なんですね。 @linny: そのとおり。シェルは打った文字をそのまま受け取るんだ。だから、間違えずに打つことが大事なんだよ。 ::: ## ミニ課題 {#exercises} > **結論**: 今日学んだコマンドを実際に打って確認しよう。 ::: dialogue @linny: 今日学んだことを確認するために、ミニ課題に挑戦してみよう。 ::: ### 課題1: ログインシェルを確認しよう {#exercise-1} **やること**: ログインシェルのパスを確認するコマンドを打とう。 :::details ヒント1(方向づけ)を見る ログイン時に起動するシェルは、ある変数に入っている。その変数を表示してみよう。 ::: :::details ヒント2(コマンド名)を見る `echo`コマンドで`$SHELL`という変数を表示するよ。 ::: :::details 答えを見る ```bash echo $SHELL ``` ```output /bin/bash ``` ::: ### 課題2: 今動いているシェルをプロセスから確認しよう {#exercise-2} **やること**: 今動いているシェルのプロセスを確認しよう。 :::details ヒント1(方向づけ)を見る プロセスを表示するコマンドを思い出そう。合言葉は「今動いているシェル自身のプロセスID」だよ。 ::: :::details ヒント2(コマンド名)を見る `ps`コマンドを`-p $$`オプション付きで使うよ。 ::: :::details 答えを見る ```bash ps -p $$ ``` ```output PID TTY TIME CMD 2451 pts/0 00:00:00 bash ``` ::: ### 課題3: 使えるシェルの一覧を確認しよう {#exercise-3} **やること**: このシステムにインストールされているシェルの一覧を表示しよう。 :::details ヒント1(方向づけ)を見る シェルの一覧が書かれているファイルがある。中身を表示してみよう。 ::: :::details ヒント2(コマンド名)を見る `cat`コマンドで`/etc/shells`ファイルを表示するよ。 ::: :::details 答えを見る ```bash cat /etc/shells ``` ```output /bin/sh /bin/bash /usr/bin/bash /bin/zsh /usr/bin/zsh ``` ::: ## 振り返り {#review} > **結論**: シェルは通訳役、カーネルは核。種類は bash / zsh / sh があり、確認と変更のコマンドまで押さえられた。 ::: dialogue @lina: 今日わかったことをまとめます。シェルは私とカーネルの間に立つ通訳役で、bash や zsh はその種類なんですね。 @linny: よく整理できているね。そして自分がどのシェルを使っているかは `echo $SHELL` と `ps -p $$` で確認できる。 @lina: 変えたいときは `chsh -s` ですね。でも、その前に `zsh` と打って試してから決めます! @linny: 完璧だよ。次は [ターミナルの使い方](/articles/guides/terminal-basics) で、コマンドの打ち方そのものを練習してみよう。 ::: ## 今日の3行まとめ {#summary} > **結論**: 今日のポイントは、シェルとカーネルの関係、bash/zsh/shの違い、確認・変更コマンドの3つだ。 1. **シェルはカーネルとの通訳役** - コマンドを解釈してカーネルに伝え、結果を返す 2. **sh・bash・zsh はシェルの種類** - sh は基本形、bash は一番普及した後継、zsh は bash 互換で高機能 3. **確認は`echo $SHELL`・`ps -p $$`、変更は`chsh -s`** - 変更先は`/etc/shells`にあるものだけ選べる ## 次に読む {#next} - [ターミナルの使い方 - コマンドライン入門](/articles/guides/terminal-basics) - [Linuxとは何か?](/articles/guides/what-is-linux) - [bash/zsh/fish の違いと選び方](/articles/guides/shell-comparison) # Linuxとは?初心者でもわかるOS入門 Source: https://penguin-gym-linux.com/articles/guides/what-is-linux ## この記事でわかること {#what-you-will-learn} - LinuxとはどんなOSか、WindowsやMacと何が違うのか - Linuxがどこで使われているか、なぜIT業界で必須スキルとされるのか - ディストリビューションの種類と初心者におすすめの選び方 ## はじめに {#intro} > **結論**: LinuxはAndroid・Webサービス・クラウドを支える無料のOSで、世界中の開発者が協力して改良している。 :::dialogue @lina: ライニー先輩、「Linux」ってよく聞くんですけど、結局何なんですか?WindowsやMacとは違うんでしょうか? @linny: いい質問だね!Linuxは、WindowsやMacと同じ「オペレーティングシステム(OS)」の一種だよ。でも、無料で使えて、世界中の{開発者|かいはつしゃ}が一緒に作っているという大きな特徴があるんだ。 @lina: 無料なのに、ちゃんと使えるんですか? @linny: もちろん!実は、スマートフォンのAndroidもLinuxがベースなんだ。Google、Amazon、Netflixなど大手企業のサーバー(Webサイトやアプリのデータを送り出す、ずっと動き続けているコンピューター)もLinuxで動いているよ。今日はLinuxの基本から一緒に学んでいこう。 ::: ## 1. Linuxとは何か? {#what-is-linux} > **結論**: LinuxはMicrosoftでもAppleでもない第三のOSで、無料・オープンソース・高セキュリティの3特徴を持つ。 :::dialogue @linny: まず、Linuxの基本を整理するね。Linuxは、コンピューターを動かすための「OS(オペレーティングシステム)」の一種だよ。 @lina: OSって、パソコンの電源を入れたら最初に動くやつですよね? @linny: その通り!OSがないと、アプリを動かしたり、ファイルを保存したりできないんだ。 ::: ### Linuxの3つの特徴 1. **無料で使える**: ライセンス料金が一切かからない 2. **オープンソース**: ソースコードが公開されていて、誰でも改良できる 3. **高いセキュリティ**: 世界中の開発者が常に改善している :::dialogue @lina: 「オープンソース」って何ですか? @linny: プログラムの「設計図(ソースコード)」を誰でも見られるようにしているということだよ。だから、世界中の開発者が「ここを直したほうがいい」「こうすれば速くなる」と改善に参加できるんだ。 @lina: みんなで作るOSなんですね! ::: ### Linuxの歴史 :::dialogue @linny: Linuxは1991年に、フィンランドの大学生だったリーナス・トーバルズさんが作ったんだ。当時21歳だったんだよ。 @lina: え、大学生が作ったんですか!すごい! @linny: 名前も「Linus(リーナス)」と「Unix(ユニックス、当時あったOS)」を組み合わせて「Linux」になったんだよ。 ::: ## 2. WindowsやMacとの違い {#linux-vs-others} > **結論**: LinuxはWindowsより安全でカスタマイズ性が高いが初心者には難しく、IT業界では必須スキルとなっている。 :::dialogue @lina: WindowsやMacと比べると、どこが違うんですか? @linny: 表で整理してみよう。 ::: | 項目 | Linux | Windows | macOS | | ---------------------- | -------------- | ----------------- | --------------- | | **価格** | 無料 | 有料 | Mac購入時に付属 | | **開発形態** | オープンソース | Microsoft社が開発 | Apple社が開発 | | **カスタマイズ性** | 非常に高い | 普通 | 限定的 | | **セキュリティ** | 非常に堅牢 | ウイルス対策必須 | 比較的安全 | | **初心者の使いやすさ** | やや難しい | 使いやすい | 直感的 | :::dialogue @lina: Linuxは「やや難しい」んですね...。 @linny: 最初はそう感じるかもしれないけど、基本を覚えれば大丈夫。それに、IT業界では必須スキルだから、学ぶ価値は十分あるよ。 ::: ## 3. Linuxが使われている場所 {#where-used} > **結論**: LinuxはWebサーバー・クラウド・Android・銀行・スーパーコンピュータなど日常生活の基盤で動いている。 :::dialogue @lina: Linuxって、普通の人はあまり使わないんですか? @linny: 実は、毎日使っているかもしれないよ。気づかないだけでね。 ::: ### Webサービス・サーバー - **Webサーバー**: Google、Facebook、Amazonなど大手サイトのほとんどがLinuxで運用 - **クラウドサービス**: AWS、Google Cloud、Microsoft Azureの大部分がLinux ### スマートフォン・IoT - **Android**: 世界シェアの大半を占めるAndroidスマホはLinuxベース - **家電・IoT機器**: スマートテレビ、ルーター、車載システムなど ### 企業・政府 - **銀行システム**: ATMや基幹システム - **株式取引所**: 高速取引システム - **スーパーコンピューター**: 世界のスーパーコンピューターの大多数がLinux :::dialogue @lina: えっ、Androidもなんですか!じゃあ私、毎日Linuxを使ってるってことですね! @linny: そうなんだよ。NetflixやYouTubeを見るとき、裏側ではLinuxサーバーが動いている。Linuxは現代のIT社会を支える{基盤|きばん}なんだ。 ::: ## 4. なぜLinuxが重要なのか? {#why-important} > **結論**: LinuxスキルはIT業界の必須要件で、インフラやDevOps・セキュリティ系職種では年収差にも直結している。 :::dialogue @lina: Linuxを学ぶと、どんないいことがあるんですか? @linny: 大きく分けて3つのメリットがあるよ。 ::: ### IT業界での必須スキル :::dialogue @linny: Web開発、データ分析、AI・機械学習、インフラ構築など、現代のIT分野でLinuxスキルは必須だよ。特にサーバー管理やクラウド運用では、Linuxの知識がないと仕事にならないんだ。 ::: ### キャリアの可能性 :::dialogue @linny: Linuxスキルを持つエンジニアは、持たないエンジニアより年収が高い傾向があるよ。特にインフラエンジニア、DevOpsエンジニア、セキュリティエンジニアなどの{職種|しょくしゅ}では必須スキルになっている。 @lina: 仕事で使えるスキルなんですね! ::: ### コンピューターの理解が深まる :::dialogue @linny: Linuxを学ぶと、OSがどう動いているか、ファイルシステムやネットワークの仕組みなど、コンピューターの基礎が身につくんだ。WindowsやMacだけ使っていると、なかなか見えない部分だよ。 ::: ## 5. ディストリビューション {#distributions} > **結論**: ディストリビューションとはLinuxカーネルにソフトをまとめたもので、初心者にはUbuntuが最適な出発点だ。 :::dialogue @lina: Linuxをインストールしたいんですけど、「Ubuntu」とか「CentOS」とか色々あるみたいで...。 @linny: それは「ディストリビューション」と呼ばれるものだよ。Linuxのカーネル(中核部分)に、いろいろなソフトウェアを組み合わせて使いやすくパッケージ化したものなんだ。 ::: ### 初心者におすすめのディストリビューション **Ubuntu(ウブントゥ)** 最も人気で初心者フレンドリー。日本語情報も豊富で、困ったときに調べやすい。 **Linux Mint** Windowsに似た操作感で、Windowsユーザーが移行しやすい。 **CentOS / Rocky Linux** 企業のサーバー用途でよく使われる。安定性が高い。 :::dialogue @lina: 最初はどれがいいですか? @linny: 初心者なら**Ubuntu**をおすすめするよ。日本語の情報が多いから、わからないことがあっても調べやすいんだ。 ::: ## 6. メリット・デメリット {#advantages} > **結論**: Linuxはコスト・セキュリティ・軽量性で優れる一方、学習コストの高さとソフト対応制限がデメリットだ。 :::dialogue @lina: Linuxのいいところと悪いところを教えてください! @linny: 正直に両方伝えるね。 ::: **メリット** - **完全無料**: OSもソフトウェアも無料 - **高いセキュリティ**: ウイルス{感染|かんせん}リスクが低い - **軽量・高速**: 古いPCでも快適に動作 - **カスタマイズ自由**: 好みに合わせて調整可能 - **プライバシー保護**: 個人データの収集がない - **IT業界での価値**: キャリアに直結するスキル **デメリット** - **学習コストが高い**: 最初は覚えることが多い - **対応ソフトが限定的**: Windows専用ソフトは使えない - **ゲーム環境**: 対応ゲームがWindowsより少ない - **英語の情報が多い**: 日本語情報が少ない場合がある - **一部ハードウェアの対応**: ドライバがない場合がある :::dialogue @lina: やっぱり最初は難しそうですね...。 @linny: 大丈夫!Penguin Gym Linuxなら、ブラウザ上で安全にLinuxコマンドを練習できるよ。まずは基本コマンドから始めてみよう。 ::: ## リナ、さっそく触ってみる {#first-try} > **結論**: Penguin Gym Linuxはすでに仮想的なLinux環境として{起動|きどう}済みなので、ディストリビューション名を入力する必要はない。 :::dialogue @lina: さっき、Ubuntuがおすすめだと聞きました。Penguin Gym Linuxで`ubuntu`と打てば、使い始められると思います。試してみますね! ::: ```bash $ ubuntu ``` ```output bash: ubuntu: command not found ``` :::dialogue @lina: あれ、エラーになっちゃいました…。「見つからない」って、Ubuntuが無いってことですか? @linny: 惜しい!Penguin Gym Linuxは、もう仮想的なLinux環境として起動済みなんだ。だから、起動するコマンドは要らないんだよ。`ubuntu`はディストリビューションの名前だよ。コマンドじゃないんだ。 @lina: あ、そういうことか!もう中に入っているんですね。じゃあ、何を打てばいいんですか? @linny: まずは`pwd`を打ってみよう。今いる場所が表示されるはずだよ。 ::: ## ミニ課題 {#exercises} > **結論**: 今日の課題はスマートフォンの確認・Webサービスの裏側を意識・Penguin Gymでpwdを実行の3ステップだ。 :::dialogue @linny: 今日学んだことを確認するために、3つの課題をやってみよう! ::: ### 課題1: スマートフォンのOSを確認しよう {#exercise-1} **やること**: 自分のスマートフォンがAndroidかiPhoneかを確認しよう。Androidなら、ふだんからLinuxベースのシステムを使っていることになる。 :::details ヒント1(方向づけ)を見る スマホの「設定」アプリのどこかに、機種やOSの情報をまとめた画面があるはずだよ。探してみよう。 ::: :::details ヒント2(確認する場所)を見る 「設定」→「{端末|たんまつ}情報」(機種によっては「このスマホについて」)を開くと、OS名が表示されるよ。 ::: :::details 答えを見る Androidの場合、「設定」→「デバイス情報」などにOS名が表示される。これがLinuxベースのOSだ。iPhoneの場合はiOSと表示され、Linuxベースではない。 ::: ### 課題2: Webサービスの裏側を考えよう {#exercise-2} **やること**: 普段使っているWebサービスを3つ挙げて、それぞれの裏側で何が動いているかを考えよう。 :::details ヒント1(方向づけ)を見る この記事で紹介したWebサービス・クラウドサービスの例を思い出してみよう。 ::: :::details ヒント2(考える視点)を見る Google、YouTube、Amazonなど、ふだんよく使うサービスを思い浮かべよう。サーバー側で動くOSは何だったかな。 ::: :::details 答えを見る Google、YouTube、Amazonなどの多くは、裏側でLinuxサーバーが動いている。私たちが意識していないだけで、Linuxは毎日使われている。 ::: ### 課題3: Penguin Gym Linuxでpwdを実行しよう {#exercise-3} **やること**: [Penguin Gym Linux](/terminal)で`pwd`コマンドを実行し、Linuxの世界に触れてみよう。 :::details ヒント1(方向づけ)を見る 「今、自分がどこにいるか」を画面に表示してくれるコマンドを思い出してみよう。さっきの例で先輩が教えてくれたね。 ::: :::details ヒント2(コマンド名)を見る 使うのは`pwd`だよ。半角の小文字で入力しよう。 ::: :::details 答えを見る ```bash $ pwd ``` ```output /home/user ``` ::: :::dialogue @lina: 課題3が一番楽しそう!やってみます! ::: ## 振り返り {#review} > **結論**: Linuxは無料OSで世界中の開発者が改善している。AndroidやWebの裏側で動いており現代IT社会の基盤だ。 :::dialogue @lina: 今日の内容をまとめると、Linuxは無料で使えるOSで、スマホやWebサービスの裏側で動いているんですね。 @linny: その通り!よく理解できているね。 @lina: IT業界では必須スキルっていうのも驚きでした。最初は難しそうだけど、Penguin Gym Linuxで練習すればいいんですね! @linny: その意気だよ!次は「ターミナルの基礎」を学ぶと、実際にLinuxを操作する感覚がつかめるよ。一歩ずつ進んでいこう。 ::: ## 今日の3行まとめ {#summary} > **結論**: LinuxはAndroid・Web・クラウドを通じて毎日使われている。IT業界で生きるなら基礎から学ぶ価値は大きい。 1. Linuxは**無料で使えるオープンソースのOS**で、世界中の開発者が改善している 2. Android、Webサーバー、クラウドなど、**私たちの生活を支える基盤技術**として広く使われている 3. IT業界では**必須スキル**なので、基本コマンドから少しずつ学んでいこう ## 次に読む {#next-steps} > **結論**: Linuxの次ステップはターミナルの基礎を学んでpwd・ls・cdを実際に操作することから始めよう。 Linuxの基本を理解したら、実際にLinuxコマンドを体験してみましょう。 - [Penguin Gym Linuxで実践練習](/terminal?lesson=basic-pwd) - [次の記事: ターミナルの基礎](/articles/guides/terminal-basics) - [基本コマンドを覚えよう](/articles/tutorials/basic-commands) # なぜLinuxを学ぶべきか?初心者向け完全ガイド Source: https://penguin-gym-linux.com/articles/guides/why-learn-linux 「Linuxって本当に必要なの?」「GUIで十分じゃない?」そんな疑問を持っている方へ。この記事では、ライニー先輩とリナの会話形式で、**なぜLinuxを学ぶべきなのか**を分かりやすく解説します。 ## この記事でわかること {#what-you-will-learn} - LinuxがWebサービス・クラウド・スマホを支える{基盤|きばん}である理由 - Linux学習で仕事の選択肢と作業効率がどう変わるか - 最初に覚えるべき3コマンドと学習を{継続|けいぞく}するコツ ## はじめに {#intro} > **結論**: LinuxはWeb・クラウド・スマホを支える基盤OSで、IT業界ではエンジニア必須スキルの一つだ。 :::dialogue @lina: ライニー先輩、Linuxって聞いたことはあるんですけど…正直、何に使うのかよく分からないんです。普段使ってるパソコンはWindowsだし、スマホはiPhoneだし…。 @linny: なるほど、その疑問はとてもよく分かるよ。でもね、リナが毎日使っているサービスの裏側では、実はLinuxが動いていることが多いんだ。 @lina: えっ、そうなんですか? @linny: 今日は「Linuxって何に使われてるの?」「なぜ学ぶといいの?」を一緒に見ていこう。 ::: ## 1. Linuxはどこで使われている? {#where-linux} > **結論**: LinuxはWebサーバー・クラウド・Android・スーパーコンピュータなどIT社会の基盤として動いている。 :::dialogue @linny: まず、Linuxがどこで使われているか整理してみよう。 ::: | 分野 | 具体例 | | -------------------------- | ------------------------------------- | | **Webサービス** | Google、Amazon、Netflixなどのサーバー | | **クラウド** | AWS、Google Cloud、Azureの基盤 | | **スマートフォン** | AndroidはLinuxベース | | **スーパーコンピューター** | 世界のトップ500の大多数がLinux | | **組み込み機器** | ルーター、テレビ、車載システム | :::dialogue @lina: えっ、Androidもですか!?毎日使ってるサービスの裏側がLinuxだったなんて…。 @linny: そうなんだ。「サーバー」は、Webサイトやアプリのデータを送り出す、ずっと動き続けているコンピューターのことだよ。私たちが見ているWebサイトの多くはLinuxサーバーで動いていて、Linuxは「見えないところで活躍するインフラの基盤」なんだ。 @lina: なるほど…。でも、普通の人がLinuxを学ぶ必要あるんですか? ::: ## 2. 学ぶと何が変わる? {#why-learn} > **結論**: Linuxを学ぶと仕事の選択肢が広がり、繰り返し作業を自動化でき、サーバートラブルに対応できる力が付く。 :::dialogue @linny: いい質問だね。Linuxを学ぶメリットを3つ紹介するよ。 ::: ### メリット1: 仕事の選択肢が広がる :::dialogue @linny: IT業界の求人を見てみると、「Linux経験歓迎」「必須」という記載がとても多いんだ。 ::: | 職種 | Linuxの必要性 | | ------------------ | ------------------------------- | | Webエンジニア | サーバー環境がLinuxのことが多い | | インフラエンジニア | 必須スキル | | データエンジニア | データ処理環境でよく使う | | DevOpsエンジニア | CI/CDの構築に必須 | :::dialogue @lina: IT系の仕事を目指すなら、Linuxは知っておいた方がいいんですね。 ::: ### メリット2: 作業が速くなる :::dialogue @linny: Linuxコマンドを使えると、手作業で時間がかかる処理を一瞬で終わらせられるんだ。 ::: **GUI(マウス操作)**: 100個のファイル名を変更 → 1個ずつ右クリック… → 約30分〜1時間 **Linuxコマンド**: 100個のファイル名を変更 → 1行のコマンドで完了 → 数秒 :::dialogue @lina: 数秒!?そんなに違うんですか? @linny: そうなんだ。繰り返し作業や大量のファイル処理は、コマンドの得意分野だよ。 ::: ### メリット3: トラブルに対応できる :::dialogue @linny: サーバーで問題が起きたとき、原因を調べて解決できる力は重宝されるんだ。 ::: - `top`コマンドでCPU使用率を確認 - `df`コマンドでディスク容量を確認 - `tail`コマンドでログファイルを確認 :::dialogue @lina: 「サーバーが重い」って言われたときに、原因を調べられるってことですね。 @linny: その通り!問題解決できる人は、チームでとても{頼|たよ}りにされるよ。 ::: ## 3. 業務効率化の具体例 {#efficiency} > **結論**: ファイル一括操作・ログ検索・定期バックアップ自動化など、Linuxコマンドは実務の手作業を大幅に{削減|さくげん}できる。 :::dialogue @lina: 具体的にどんな場面で役立つんですか? @linny: いくつか例を挙げてみるね。 ::: ### 例1: ファイルの整理 特定の拡張子のファイルだけを別フォルダに移動する: ```bash $ mv *.jpg images/ ``` :::dialogue @lina: これだけで全部のjpgファイルが移動するんですね! ::: ### 例2: ログの検索 大量のログファイルから「error」を含む行だけを{抽出|ちゅうしゅつ}: ```bash $ grep "error" access.log ``` ### 例3: 定期的なバックアップ 毎日自動でバックアップを取る設定もできる: ```bash $ crontab -e # 毎日午前3時にバックアップ 0 3 * * * rsync -av /data /backup ``` :::dialogue @lina: 自動化までできるんですか!便利ですね。 @linny: そうなんだ。最初は基本コマンドから始めて、少しずつできることを増やしていけばいいよ。 ::: ## 4. よくある疑問 {#faq} > **結論**: GUIで十分・クラウド不要・AIで代替という3疑問はいずれもLinux基礎知識の必要性を示す理由になる。 :::dialogue @lina: でも、いくつか気になることがあって…。 @linny: 何でも聞いてみて。 ::: ### Q: GUIで十分じゃない? :::dialogue @lina: 普段使うパソコンはマウスで操作できるし、GUIで十分じゃないですか? @linny: GUI(Graphical User Interface、マウスでアイコンをクリックして操作する画面のこと)は確かに使いやすいよね。でも実は、サーバーにはGUIがないことが多いんだ。画面を表示する機能を省くことで、サーバーの処理能力を最大限に使えるからね。だから、サーバーを操作するにはコマンドラインが必須なんだよ。 ::: ### Q: クラウドを使えばLinuxは不要? :::dialogue @lina: AWSとかのクラウドサービスを使えば、Linuxを知らなくても大丈夫じゃないですか? @linny: クラウドは便利だけど、その上で動いているのはLinuxサーバーなんだ。設定やトラブル対応で、結局Linuxコマンドが必要になる場面が多いよ。 ::: ### Q: AIがやってくれるのでは? :::dialogue @lina: 最近はAIがコマンドを教えてくれますよね?それで十分じゃないですか? @linny: AIは確かに便利だね。でも、AIが出力したコマンドが「何をするのか」「安全かどうか」を判断するのは、自分自身なんだ。基礎知識がないと、危険なコマンドを実行してしまう可能性もあるよ。 @lina: たしかに…。AIを上手く使うためにも、基礎は知っておいた方がいいんですね。 ::: ## 5. 最初の一歩 {#first-step} > **結論**: まずは pwd・ls・cd の3コマンドを毎日15分練習するだけで、1週間後に大きな進歩を実感できる。 :::dialogue @lina: Linuxを学びたくなってきました!でも、何から始めればいいですか? @linny: 最初は3つの基本コマンドから始めてみよう。表に出てくる「ディレクトリ」は、WindowsやMacでいう「フォルダ」のことだよ。Linuxでは「ディレクトリ」と呼ぶことが多いんだ。 ::: | コマンド | 役割 | | -------- | ------------------ | | `pwd` | 今いる場所を表示 | | `ls` | ファイル一覧を表示 | | `cd` | ディレクトリを移動 | :::dialogue @linny: この3つだけで「今どこにいて、何があるか」が分かるようになるよ。まずはこれを覚えて、少しずつ増やしていこう。 @lina: 3つだけなら、なんとかなりそうです! @linny: 大事なのは、毎日少しずつ触ること。1日15分でも、1週間続ければ大きな進歩になるよ。 ::: ## リナ、さっそく試してみる {#first-try} > **結論**: Linuxのコマンドは半角の小文字で入力する。大文字で打つと「見つからない」というエラーになる。 :::dialogue @lina: さっそくPenguin Gym Linuxで試してみます! `PWD`って打ってみました! ::: ```bash $ PWD ``` ```output bash: PWD: command not found ``` :::dialogue @lina: あれ、エラーになっちゃいました…何がダメだったんでしょう? @linny: 惜しい!Linuxのコマンドは、基本的に**半角の小文字**で入力するんだ。大文字の`PWD`ではなく、小文字の`pwd`と打ってみて。 @lina: あ、ほんとだ! `pwd`って打ったらちゃんと表示されました。コマンドは大文字と小文字を区別するんですね。 @linny: そのとおり。最初はみんなつまずくポイントだから、覚えておくと安心だよ。Penguin Gym Linuxは学習用の仮想ターミナルだから、間違えてもリナの実際のパソコンには影響しない。安心していろいろ試してみてね。 ::: ## ミニ課題 {#exercises} > **結論**: 今日学んだLinuxの基本を確認するため、Penguin Gym Linuxで実際にコマンドを実行して手を動かそう。 :::dialogue @linny: 今日学んだことを確認するために、ミニ課題に挑戦してみよう。 ::: ### 課題1: 今いる場所を確認しよう {#exercise-1} **やること**: Penguin Gym Linuxで、今いる場所(ディレクトリ)を表示するコマンドを実行しよう。 :::details ヒント1(方向づけ)を見る 「今、自分がどこにいるか」を画面に表示してくれるコマンドを思い出してみよう。 ::: :::details ヒント2(コマンド名)を見る 使うのは`pwd`だよ。半角の小文字で入力しよう。 ::: :::details 答えを見る ```bash $ pwd ``` ```output /home/user ``` ::: ### 課題2: ファイル一覧を見てみよう {#exercise-2} **やること**: 今いる場所にあるファイルやフォルダを一覧表示しよう。 :::details ヒント1(方向づけ)を見る 「今いる場所に何があるか」を一覧で見せてくれるコマンドを探そう。 ::: :::details ヒント2(コマンド名)を見る 使うのは`ls`だよ。「list(一覧)」の略だね。 ::: :::details 答えを見る ```bash $ ls ``` ```output Documents Downloads Pictures ``` ::: ### 課題3: ディレクトリを移動しよう {#exercise-3} **やること**: 存在するディレクトリに移動して、移動できたかどうかを確認しよう。 :::details ヒント1(方向づけ)を見る まず移動先が本当にあるか確認して、それから移動、最後に今いる場所を確認、の3ステップだよ。 ::: :::details ヒント2(コマンド名)を見る `ls`で確認 → `cd`(Change Directory の略)で移動 → `pwd`で確認、の順だよ。 ::: :::details 答えを見る ```bash $ cd Documents $ pwd ``` ```output /home/user/Documents ``` ::: :::dialogue @lina: やってみます! ::: ## 振り返り {#review} > **結論**: LinuxはIT業界の至る所で動く基盤だ。まずは3コマンドから始め、少しずつできることを増やしていけばよい。 :::dialogue @lina: なるほど…。Linuxって、普段見えないところで活躍しているんですね。学ぶと仕事の幅が広がるし、作業も速くなる。 @linny: その通り!最初は3つのコマンドから始めて、少しずつできることを増やしていけば大丈夫だよ。 @lina: GUIで十分だと思ってたけど、サーバーにはGUIがないんですね。AIを使うにも基礎知識が必要だし…やっぱり学んでおいた方がいいですね! @linny: 「もっと早く学んでおけばよかった」と後から思うより、今から始める方がいい。一緒に頑張ろう! ::: ## 今日の3行まとめ {#summary} > **結論**: Linux学習の要点は「Webの裏側で動く基盤」「キャリア直結」「最初は3コマンドから」の3点に集約される。 1. **Linuxは見えないところで活躍している** - Webサービス、クラウド、スマホの基盤 2. **学ぶと仕事の選択肢が広がる** - IT業界ではLinuxスキルが重宝される 3. **まずは3つのコマンドから** - `pwd`、`ls`、`cd`で始めよう ## 次に読む {#next-steps} > **結論**: 次のステップは実際にターミナルでpwd・ls・cdを練習して基本を体に染み込ませることだ。 基本を理解したら、Penguin Gym Linuxの実践課題で手を動かして学習を定着させましょう。 - [Penguin Gym Linuxで実践練習](/terminal?lesson=basic-pwd) - [次の記事: ターミナルの基礎知識](/articles/guides/terminal-basics) - [基本コマンドを覚えよう](/articles/tutorials/basic-commands) # WSL2入門 - WindowsでLinuxを使う方法 Source: https://penguin-gym-linux.com/articles/guides/wsl2-introduction ## WSL2とは何か? {#what-is-wsl2} WSL2(Windows Subsystem for Linux 2)は、Windows上でLinuxを動かすための仕組みだ。仮想マシン(PCの中に別のPCを作るソフト)を自分で用意する必要はない。Windows 10(バージョン2004以降)と Windows 11 には、はじめから入っている。 WSL1と比べたWSL2の違いは、**完全なLinuxカーネル**(OSのいちばん中心にある部分)を積んでいる点だ。そのためDockerやsystemdも動作する。ふだんの開発に使える水準になっており、Windows PCでLinuxを学ぶならいちばん有力な選択肢になる。 ::: tip **WSL2でできること** - Ubuntu / Debian / Fedora 等のLinuxコマンドをWindowsで実行 - Dockerコンテナの実行 - PythonやNode.jsなどの開発環境構築 - シェルスクリプトの作成・実行 - VS CodeでLinuxファイルを直接編集 ::: ## WSL2のシステム要件は? {#requirements} WSL2を使うには、Windows 10 バージョン2004(ビルド19041)以降、またはWindows 11が必要だ。加えて、BIOS/UEFI(PCを立ち上げたときに開ける設定画面。UEFIは新しい世代のBIOS)で、仮想化機能(VT-x / AMD-V)をオンにしておく必要がある。 | 項目 | 最低要件 | | -------------- | ---------------------------------------------------------- | | OS | Windows 10 バージョン2004(ビルド 19041)以降 / Windows 11 | | アーキテクチャ | x64 または ARM64 | | 仮想化 | BIOS/UEFI で有効化済み | Windowsのバージョンは `winver` コマンドで確認できる。 ```bash winver ``` 仮想化が有効かどうかは、タスクマネージャーで確認できる。Ctrl+Shift+Esc を押し、「パフォーマンス」→「CPU」と進み、「仮想化」欄を見る。 ## WSL2のインストール方法 {#install} Windows 11 と Windows 10(2021年10月アップデート以降)なら、コマンド1行で済む。WSL2とUbuntuをまとめてインストールできる。 **1. 管理者権限でPowerShell(またはコマンドプロンプト)を開く** スタートメニューで「PowerShell」を右クリック →「管理者として実行」を選択する。 **2. インストールコマンドを実行する** ```bash wsl --install ``` ```output インストール中: 仮想マシン プラットフォーム 仮想マシン プラットフォーム はインストールされました。 インストール中: Linux 用 Windows サブシステム Linux 用 Windows サブシステム はインストールされました。 インストール中: Ubuntu Ubuntu はインストールされました。 操作を正常に完了しました。 ``` **3. PCを再起動する** 再起動後、自動的にUbuntuのセットアップが始まる。ここでユーザー名とパスワードを設定する。設定が終われば、Linux環境が使えるようになる。 ::: warning ここで設定するパスワードは、あとで`sudo`(管理者としてコマンドを動かす仕組み)を使うときに必要になる。忘れずに記録しておくこと。 ::: ### 特定のディストリビューションをインストールしたい場合 ディストリビューション(Linux本体と付属ソフトをまとめたセット。「ディストロ」とも呼ぶ)は自分で選べる。まず一覧を出し、名前を指定してインストールする。 ```bash wsl --list --online ``` ```output NAME FRIENDLY NAME Ubuntu Ubuntu Debian Debian GNU/Linux kali-linux Kali Linux Rolling Ubuntu-18.04 Ubuntu 18.04 LTS Ubuntu-20.04 Ubuntu 20.04 LTS Ubuntu-22.04 Ubuntu 22.04 LTS Ubuntu-24.04 Ubuntu 24.04 LTS ... ``` ```bash wsl --install -d Ubuntu-24.04 ``` ### インストール済みのディストリビューションを確認する ```bash wsl -l -v ``` ```output NAME STATE VERSION * Ubuntu Running 2 Ubuntu-24.04 Stopped 2 ``` 「VERSION」が「2」ならWSL2として動作している。 ## Linuxディストリビューションを選ぶには? {#distro} 初めてWSL2を使う場合は**Ubuntu**が最も適している。理由は2つある。公式ドキュメントが豊富なこと、そして`apt`(ソフトの出し入れをまとめて行うパッケージマネージャー)でほとんどのツールをすぐ入れられることだ。 | ディストリビューション | 特徴 | 推奨対象 | | ---------------------- | ------------------------- | ------------------ | | Ubuntu | ドキュメント豊富・apt管理 | 初心者・一般開発者 | | Debian | 軽量・安定重視 | サーバー運用経験者 | | Kali Linux | セキュリティツール特化 | セキュリティ学習者 | | openSUSE | エンタープライズ向け | 企業環境での学習者 | ::: tip 迷ったらUbuntuのLTS版(Long Term Support = 長期サポート版)を選ぶのが無難。サポート期間が5年と長く、日本語の情報も多い。 ::: ## 基本的なLinux操作を始めるには? {#basic-usage} WSL2を起動する方法は2つある。スタートメニューで「Ubuntu」(またはインストールしたディストリビューション名)を検索して開く方法と、PowerShellで`wsl`と入力する方法だ。 ```bash wsl ``` 起動するとLinuxのシェルが使えるようになる。基本的なコマンドを確認しよう。 ```bash # 現在のディレクトリを表示 pwd ``` ```output /home/username ``` ```bash # ファイル一覧を表示 ls -la ``` ```bash # パッケージリストを更新する(初回セットアップ後に必ず実行) sudo apt update && sudo apt upgrade -y ``` ### WSL2のシャットダウン ```bash # 特定のディストリビューションを終了 wsl --terminate Ubuntu # すべてのWSLインスタンスを終了 wsl --shutdown ``` ## WindowsとLinuxのファイルを共有するには? {#file-sharing} WSL2からWindowsのファイルへは `/mnt/c/` 以下でアクセスできる。逆にWindowsからLinuxのファイルへは、エクスプローラーで「\\wsl$\Ubuntu」を開けばアクセスできる。 ### WSL2からWindowsファイルへアクセスする ```bash # Cドライブのホームディレクトリを見る ls /mnt/c/Users/ # WindowsのDocumentsにファイルをコピーする cp myfile.txt /mnt/c/Users/username/Documents/ ``` ### LinuxファイルをWindowsのエクスプローラーで開く カレントディレクトリ(今いるディレクトリ)をWindowsエクスプローラーで開きたい場合は、次のコマンドを実行する。 ```bash explorer.exe . ``` エクスプローラーのアドレスバーに `\\wsl$\Ubuntu\home\username` と入力してもLinuxファイルシステムにアクセスできる。 ::: warning Linux上で作業するファイルは、Windowsの `/mnt/c/` ではなくLinuxのホームディレクトリ(`/home/username/`)に置くことを推奨する。OSの境をまたぐ読み書き(I/O)は遅くなるためだ。 ::: ### VS CodeでLinuxのファイルを編集する VS Codeに「Remote - WSL」拡張(あとから機能を足す部品)を入れよう。これでLinuxのファイルをWindows側から直接編集できる。 ```bash # WSL2のシェルからVS Codeを開く(Remote WSL拡張が必要) code . ``` ## よくあるエラーと対処法 {#troubleshooting} ### 「仮想化が有効になっていない」エラー 原因:BIOSで仮想化(VT-x / AMD-V)がオフになっている。 対処:PCを再起動してBIOS/UEFI設定画面を開く(通常はF2またはDELキー)。そこで仮想化オプションをオンにする。 ### `wsl --install` が失敗する(古いWindowsの場合) Windows 10 バージョン1903〜2004では、手動での有効化が必要な場合がある。 ```bash # PowerShell(管理者)で実行 dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart ``` 再起動したら、[WSL2 Linuxカーネル更新プログラムのパッケージ](https://aka.ms/wsl2kernel)をMicrosoftの公式サイトからインストールする。 ```bash # WSL2をデフォルトバージョンに設定 wsl --set-default-version 2 ``` ### 「WslRegisterDistribution failed with error: 0x80370102」 原因:Hyper-Vまたは仮想マシンプラットフォームがオフになっている。 対処: ```bash # PowerShell(管理者)で実行 dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart ``` 再起動後に再試行する。 ### パスワードを忘れた場合 ```bash # PowerShell(管理者)で rootユーザーとして起動 ubuntu config --default-user root # WSL2内でパスワードを変更 passwd username # デフォルトユーザーを元に戻す ubuntu config --default-user username ``` ::: tip 上記コマンドの `ubuntu` の部分は、使用しているディストリビューション名に合わせること(例:`ubuntu2404`)。 ::: ## 次に読む {#next} - [Linuxとは何か?](/articles/guides/what-is-linux) - [ターミナルの使い方 - コマンドライン入門](/articles/guides/terminal-basics) - [ディレクトリ構造の全体像](/articles/guides/linux-directory-structure) # alias コマンド入門 - エイリアスでコマンドを短縮する方法 Source: https://penguin-gym-linux.com/articles/tutorials/alias-basics ## エイリアスってなに? {#intro} ::: dialogue @lina: ライニー先輩、毎回 `ls -la --color=auto` って打つのが面倒で…もっと楽な方法ないですか? @linny: あるよ。「エイリアス」を使えば、長いコマンドに短い名前をつけられるんだ。`alias ll='ls -la --color=auto'` と書けば、次からは `ll` だけで済む。 @lina: えっ、それだけですか。すごく簡単そう。 @linny: そう、とても簡単だよ。ただし「ターミナルを閉じると消える」という落とし穴がある。その直し方まで知っておくと完璧だね。 ::: **エイリアス(alias)** は、長いコマンドに短い別名をつける仕組みです。「別名」「あだ名」「ショートカット」と呼ばれることもあります。この記事では「エイリアス」で統一します。 エイリアスを使うと、毎回打ち込む手間が減ります。打ち間違いも減らせます。 ::: tip **言葉の整理** - **ターミナル**:コマンドを打ち込んで、結果を文字で受け取る画面のことです。 - **シェル**:あなたが打ったコマンドを読み取って実行するプログラムのことです。この記事では `bash` を前提にします。 - ターミナル・シェル・コンソールは、ほぼ同じものを指す別の呼び方として使われます。 エイリアスはシェルが覚えている「言いかえの表」だと考えてください。 ::: ::: tip **一言でいうと** `alias 短い名前='長いコマンド'` これだけです。今すぐ使えます。 ::: ::: tip **安心してほしいこと** この記事で使う `alias` と `unalias` は、ファイルを消しません。書きかえもしません。打ち間違えても、あなたのパソコンは壊れません。 あとで出てくる `~/.bashrc` の編集だけは、設定ファイルに手を入れます。そこは [4-1 節](#persistent) で「壊れたときの戻し方」を先に説明します。 ::: ## この記事でわかること {#what-you-will-learn} - `alias 短い名前='長いコマンド'` で、長いコマンドに別名をつけられます - 設定済みのエイリアスは `alias` で一覧できます。`alias 名前` なら 1 つだけ確認できます - `type` で定義を確認できます。名前の前に `\` を付けると、その 1 回だけエイリアスを無視できます - `~/.bashrc` に書いて `source` すれば、次に開いたターミナルでも使えます - `unalias` で削除できます。`.bashrc` の行を消すまでは、開き直すと復活します ## 1. 基本の使い方 {#basic} > **結論**: `alias 名前='コマンド'` で設定すれば、すぐ使える。ただし設定は今の画面かぎりで、閉じると消える。 ### 1-1. エイリアスを設定する ```bash $ alias ll='ls -la' ``` 設定したら、すぐに使えます。 ```bash $ ll ``` ```output total 48 drwxr-xr-x 5 user user 4096 May 31 19:00 . drwxr-xr-x 3 root root 4096 May 20 10:00 .. -rw-r--r-- 1 user user 220 May 20 10:00 .bash_logout -rw-r--r-- 1 user user 3526 May 20 10:00 .bashrc ``` ::: warning `alias` コマンドで設定したエイリアスは、**今のターミナルだけ** で有効です。ターミナルを閉じると消えます。 閉じても残す方法は [4 章](#persistent) で説明します。 ::: ### 1-2. 設定済みのエイリアスを一覧表示する ```bash $ alias ``` `alias` のうしろに何も付けずに実行します。すると、今使えるエイリアスが全部表示されます。 ```output alias ll='ls -la' alias grep='grep --color=auto' alias ls='ls --color=auto' ``` ::: tip Ubuntu では、`grep` や `ls` に最初からオプションが設定されていることが多いです。これも `alias` コマンドで確認できます。 表示される中身は、お使いの環境によって変わります。上の例と違っても問題ありません。 ::: ### 1-3. 特定のエイリアスを確認する ```bash $ alias ll ``` ```output alias ll='ls -la' ``` ## 2. よく使うエイリアス例 {#examples} > **結論**: ls 系・ディレクトリ移動・消し間違い防止(`-i`)・git 系が定番。短くしすぎると意味がわからなくなるので注意する。 ### 2-1. ls 系 ```bash alias ll='ls -la' alias la='ls -A' alias l='ls -CF' ``` ::: dialogue @lina: `la` と `ll` って何が違うんですか? @linny: `ls -la` は隠しファイルも含めて詳しく表示するよ。`ls -A` も隠しファイルを表示するけれど、`.` と `..` の 2 行は出さないんだ。 @linny: 隠しファイルというのは、名前が `.` で始まるファイルのことだよ。`.bashrc` がその代表だね。用途で使い分けてみて。 ::: ### 2-2. ディレクトリ移動 ```bash alias ..='cd ..' alias ...='cd ../..' alias ~='cd ~' ``` `..` と打つだけで、1 つ上のディレクトリに移動できます。ディレクトリは、Windows や Mac でいうフォルダと同じ意味です。 ### 2-3. 削除・上書きの事故防止 ```bash alias cp='cp -i' alias mv='mv -i' alias rm='rm -i' ``` `-i` は、コマンドのうしろに付けて動きを変えるスイッチです。こうしたスイッチを「オプション」と呼びます。 `-i` を付けると、実行の前に「本当にいいですか」と確認してくれます。 ::: warning **`rm` を安全に使うために** `rm` で消したファイルは、GUI と違ってゴミ箱に行きません。その場ですぐ完全に消えます。だからこそ `alias rm='rm -i'` のような確認つきの設定が役に立ちます。 本サイトの仮想ターミナルは学習用です。何を打っても、あなたのパソコンのファイルは消えません。安心して試してください。 ::: ::: warning **シェルスクリプト内での注意点** シェルスクリプトの中でこのエイリアスが効くと、思わぬ動きになることがあります。確認待ちのまま止まってしまう場合があるからです。 スクリプトでは `\rm` や `/bin/rm` と書きます。こう書くと、エイリアスを無視してコマンド本体を呼び出せます。 ::: ### 2-4. git 系 ```bash alias gs='git status' alias ga='git add' alias gc='git commit' alias gp='git push' alias gl='git log --oneline' ``` ::: dialogue @lina: `gs` と打つだけで `git status` が動くんですか。毎日使うので助かります。 @linny: そう。ただし短くしすぎると、あとで「これ何だっけ」となるよ。困ったら `alias` で一覧を確認すればいい。 ::: ## 3. エイリアスを確認する・1 回だけ無視する {#check} > **結論**: `type` で「その名前が何なのか」がわかる。名前の前に `\` を付けると、その 1 回だけエイリアスを無視できる。 ### 3-1. エイリアスが定義されているか確認する ```bash $ type ll ``` ```output ll is aliased to `ls -la' ``` `type` は、その名前が何なのかを教えてくれるコマンドです。エイリアスなのか、コマンドなのか、シェルの組み込み機能なのかを見分けられます。 定義されていない場合は、次のように表示されます。 ```bash $ type ll ``` ```output bash: type: ll: not found ``` ### 3-2. その 1 回だけエイリアスを無視する `alias rm='rm -i'` を設定していると、毎回確認を求められます。今回だけ確認なしで動かしたいときは、名前の前に `\` を付けます。 ```bash $ \rm file.txt ``` `\` を付けると、シェルはエイリアスではなくコマンド本体を使います。設定そのものは消えません。次からはまた確認つきに戻ります。 ### 3-3. リナの失敗:`rm` が確認してくれない ::: dialogue @lina: `.bashrc` に `alias rm='rm -i'` と書きました。それなのに、スクリプトの中の `rm` は確認なしで消しました。設定が壊れたんでしょうか。 @linny: 壊れてないよ。エイリアスは、スクリプトの中では展開されないんだ。 @linny: だからスクリプトの `rm` は、エイリアスではなく本物の `rm` として動く。確認は出ないよ。 @lina: えっ、書いた設定が届かない場所があるんですか。それは驚きました。 @linny: そう。エイリアスは対話画面のための仕組みだからね。スクリプトで確認を出したいなら、`rm -i` と自分で書く。それが確実だよ。 @lina: なるほど。エイリアスは「自分が手で打つとき用のショートカット」なんですね。納得しました。 ::: ## 4. エイリアスを永続化する {#persistent} > **結論**: `~/.bashrc` に書けば、ターミナルを閉じても残る。今すぐ反映したいときは `source ~/.bashrc` を実行する。 ターミナルを閉じても残るようにするには、`~/.bashrc` に書き足します。 `~/.bashrc` は、新しいターミナルを開くたびにシェルが自動で読み込む設定ファイルです。`~` は自分のホームディレクトリを表す記号です。 ### 4-1. 編集する前にバックアップを取る ::: warning **壊れたときの戻し方を先に用意する** `~/.bashrc` は設定ファイルです。書き方を間違えると、ターミナルを開くたびにエラーが出ることがあります。 そこで、編集の前にコピーを取っておきます。 ```bash $ cp ~/.bashrc ~/.bashrc.bak ``` おかしくなったら、コピーから戻せます。 ```bash $ cp ~/.bashrc.bak ~/.bashrc $ source ~/.bashrc ``` エラーが出るだけでターミナルが使える場合は、上の 2 行で元に戻ります。 ターミナルが開いてすぐ閉じてしまう場合は、設定ファイルを読まずに起動できます。別のターミナルから次のように打ってください。 ```bash $ bash --norc --noprofile ``` この状態なら、上の `cp` で元に戻せます。 ::: ### 4-2. .bashrc を編集する ```bash $ nano ~/.bashrc ``` `nano` は初心者向けのテキストエディタです。ファイルの末尾に、次のように書き足します。 ```bash # My aliases alias ll='ls -la' alias ..='cd ..' alias gs='git status' alias cp='cp -i' alias mv='mv -i' alias rm='rm -i' ``` ### 4-3. 変更を今すぐ反映する `.bashrc` を編集しただけでは、今開いているターミナルには届きません。次のコマンドで読み直します。 ```bash $ source ~/.bashrc ``` 短縮形でも同じ意味です。 ```bash $ . ~/.bashrc ``` ::: tip **source コマンドとは** `.bashrc` を「今のシェルでもう一度読む」コマンドです。 新しいターミナルを開けば `.bashrc` は自動で読み込まれます。`source` を使えば、開き直さずに今すぐ反映できます。 ::: ::: dialogue @lina: では .bashrc に書いて source したあとは、ターミナルを閉じて開いても大丈夫ですか。 @linny: 大丈夫だよ。次にターミナルを開いたときも .bashrc は自動で読み込まれる。だからエイリアスはずっと使えるよ。 ::: ## 5. エイリアスを削除する {#remove} > **結論**: `unalias 名前` で 1 つだけ、`unalias -a` で全部を削除する。ただし `.bashrc` の行を消さないと、開き直したときに復活する。 ### 5-1. 特定のエイリアスを削除する ```bash $ unalias ll ``` ### 5-2. 全エイリアスを削除する ```bash $ unalias -a ``` ::: warning `unalias` が消すのは、今のターミナルの分だけです。ターミナルを閉じて開くと `.bashrc` が読み直されます。そのため、エイリアスは復活します。 **完全に消したいときは、`.bashrc` から該当する行も削除してください。** ::: ## 6. うまく動かないときの確認 {#debug} > **結論**: まず `type ll` で定義を確認する。保存忘れ・`source` 忘れ・`=` の前後のスペースが 3 大原因。 ::: dialogue @lina: .bashrc に書いたのに `ll` が使えません。どうしてでしょうか。 @linny: まず `type ll` を実行してみて。 ::: ```bash $ type ll ``` ```output ll is aliased to `ls -la' ``` 定義されていれば、上のように表示されます。表示されない場合は、次の 3 点を確認します。 **確認チェックリスト** 1. `.bashrc` を保存したか(nano なら `Ctrl+O` → `Enter` → `Ctrl+X`) 2. `source ~/.bashrc` を実行したか 3. エイリアスの書き方に間違いがないか ::: warning **よくあるミス:`=` の前後のスペース** ```bash # NG: = の前後にスペースを入れてはいけない alias ll = 'ls -la' # OK alias ll='ls -la' ``` `=` の前後にスペースがあると、エラーになります。シェルが `ll` をコマンド名だと読み違えるからです。 ::: ## 7. ミニ課題:実際にやってみよう {#exercise} > **結論**: 作る・確かめる・残すの 3 問で、エイリアスの基本を手で確かめる。 ::: dialogue @lina: 説明はわかりました。手を動かして確かめたいです。 @linny: いいね、3 問用意したよ。ターミナルで試してみて。 ::: **課題 1**: `la` という名前で、隠しファイルも表示する一覧コマンドを作ろう。 :::details ヒント 1(方向づけ)を見る 短い名前と、その中身になる長いコマンドを、イコールでつなぎます。中身はクォートで囲みます。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `alias` です。隠しファイルも表示するのは `ls -a` です。 ::: :::details 答えを見る ```bash $ alias la='ls -a' $ la ``` ```output . .. .bashrc .profile Documents Downloads ``` 表示される中身は、お使いの環境で変わります。`.` で始まる行が出れば成功です。 ::: **課題 2**: 作った `la` が本当にエイリアスか確かめよう。そのうえで、1 回だけエイリアスを無視して実行しよう。 :::details ヒント 1(方向づけ)を見る 「その名前が何なのか」を教えてくれるコマンドがあります。無視したいときは、名前の前に記号を 1 つ付けます。 ::: :::details ヒント 2(コマンド名)を見る 確認は `type` です。1 回だけ無視する記号は `\` です。 ::: :::details 答えを見る ```bash $ type la $ \la ``` ```output la is aliased to `ls -a' bash: la: command not found ``` `\la` はエイリアスを無視します。`la` というコマンド本体は存在しません。そのため `command not found` が出れば正解です。 ::: **課題 3**: `la` を、次に開くターミナルでも使えるようにしよう。編集の前にバックアップを取ること。 :::details ヒント 1(方向づけ)を見る 新しいターミナルを開くたびに自動で読まれる設定ファイルがあります。まずコピーを取ってから、そこに書き足します。 ::: :::details ヒント 2(コマンド名)を見る コピーは `cp`、編集は `nano`、今すぐ反映するのは `source` です。ファイルは `~/.bashrc` です。nano は `Ctrl+O` → `Enter` → `Ctrl+X` で保存して閉じます。 ::: :::details 答えを見る ```bash $ cp ~/.bashrc ~/.bashrc.bak $ nano ~/.bashrc ``` 末尾に次の 1 行を書き足して保存します。 ```bash alias la='ls -a' ``` ```bash $ source ~/.bashrc $ type la ``` ```output la is aliased to `ls -a' ``` 1 行だけ足すなら、`nano` を開かずに次のように打つ方法もあります。`>>` はファイルの末尾に書き足す記号です。`>` と書くと中身を全部消してしまうので、ここでは使いません。 ```bash $ echo "alias la='ls -a'" >> ~/.bashrc ``` ::: ## 8. 振り返り {#review} ::: dialogue @lina: つまり `alias` は今の画面だけの設定で、`.bashrc` に書くと次からも使える、ということですね。 @linny: その通り。`source` は「今すぐ読み直す」ための一手間だと考えるといいよ。 @lina: 動かないときは `type` で正体を調べる。スクリプトの中では効かない。ここまで覚えました。 @linny: 完璧だね。あとは自分がよく打つコマンドを 2 つか 3 つ、エイリアスにしてみて。 ::: ## コマンド早見表 {#summary} | やること | コマンド | | ---------------------- | ------------------------------------------------ | | エイリアスを設定 | `alias ll='ls -la'` | | 設定済み一覧を確認 | `alias` | | 特定のエイリアスを確認 | `alias ll` | | 名前の正体を調べる | `type ll` | | 1 回だけ無視する | `\ll` | | エイリアスを削除 | `unalias ll` | | 全エイリアスを削除 | `unalias -a` | | 設定を永続化 | `~/.bashrc` に追記して `source ~/.bashrc` を実行 | ## 今日の 3 行まとめ {#three-lines} 1. `alias 名前='コマンド'` で別名を作れる。ただし今のターミナルだけで有効 2. `~/.bashrc` に書いて `source` すれば、次に開いた画面でも使える 3. 動かないときは `type 名前` で正体を調べる。`=` の前後にスペースを入れない ## 次に読む {#next} - [.bashrc と .profile の読み込み順](/articles/tutorials/bashrc-profile-order) - [シェルスクリプトの書き方入門](/articles/tutorials/shell-scripting-basics) # パッケージ管理入門 - apt/yumの基本操作と使い分け Source: https://penguin-gym-linux.com/articles/tutorials/apt-yum-basics ## この記事で解決できること {#intro} - `apt` と `yum`/`dnf` の **コマンド対応関係** が頭に入ります。 - 検索・インストール・更新・削除の **実務で迷わない型** が身につきます。 - インストール前に **影響範囲を確認** して安全に実行できます。 - 「`update` と `upgrade` の違い」「`yum` と `dnf` どちらを使うか」を **根拠を持って判断** できます。 - `Unable to locate package` / `package not found` などの **定番エラーを切り分け** できます。 ::: tip **結論(実務の型)** - `apt`(Debian/Ubuntu/Mint 系)と `dnf`(RHEL 9 / Rocky / AlmaLinux / Fedora)が **現行スタンダード** - `yum` は CentOS 7 系・RHEL 7 系まで。RHEL 8 以降は `dnf` が標準(`yum` は `dnf` への互換シンボリックリンク) - 最初に必ず **一覧更新**(`apt update` / `dnf makecache`)→ 次にインストール - 何かを入れる前に **検索で正式名を確認** する。「ありそうな名前」で `install` しない ::: ::: warning **前提(対象環境)** - Debian 系:Ubuntu 20.04 / 22.04 / 24.04、Debian 11 / 12 - RHEL 系:CentOS 7(yum)、Rocky Linux 8/9・AlmaLinux 8/9・RHEL 8/9(dnf) - 一般ユーザーから `sudo` で実行する想定。root 直接ログインは想定しない ::: ## 0. 先に用語を 6 つ揃える {#terms} > **結論**: パッケージ・リポジトリ・インデックス・依存関係の 4 語が分かれば、エラー文の大半は読める。 パッケージ管理のエラーメッセージは、用語を知らないと意味が取れません。 先に最小限の 6 語を定義します。 | 用語 | 一行の意味 | 混同されやすい言い方 | | ------------------------ | --------------------------------------------------------------- | -------------------------------------------- | | パッケージ | ソフトウェア 1 本を配布用にまとめたファイル | 「.deb」「.rpm」も同じものを指します | | リポジトリ | パッケージの配布元となるサーバ | 「リポ」「repo」「配布元」とも呼ばれます | | インデックス(一覧) | リポジトリにどのパッケージがあるかを記した目録 | 「パッケージ一覧」「キャッシュ」とも言います | | 依存関係 | あるパッケージが動くために必要な別のパッケージ | 「dependency」「依存パッケージ」も同義です | | メタパッケージ | 中身は空で、複数パッケージをまとめて入れるための箱 | 「まとめパッケージ」とも呼ばれます | | GPG キー | 配布元が本物かを検証するための電子署名の鍵 | 「署名鍵」「公開鍵」も同じものを指します | ::: tip `apt update` は「**インデックスを更新する**」操作です。パッケージ本体は更新しません。 パッケージ本体を新しくするのは `apt upgrade` です。この 2 つを取り違えるのが最も多い誤解です。 ::: ## 1. apt と yum/dnf の対応表 {#mapping} > **結論**: install / remove / search は両系統で共通し、違うのは update まわりと設定ファイル削除の表現だけ。 実務で参照頻度が高い対応関係を 1 つにまとめる。 | 操作 | Debian 系 (`apt`) | RHEL 系 (`dnf` / `yum`) | | ---------------------------- | ------------------------ | ------------------------ | | パッケージ一覧(インデックス)の更新 | `sudo apt update` | `sudo dnf makecache` | | 更新可能パッケージの確認 | `apt list --upgradable` | `dnf check-update` | | インストール | `sudo apt install ` | `sudo dnf install ` | | アップグレード(全体) | `sudo apt upgrade` | `sudo dnf upgrade` | | 削除(設定残す) | `sudo apt remove ` | `sudo dnf remove ` | | 削除(設定も消す) | `sudo apt purge ` | (該当なし/手動削除) | | 検索 | `apt search ` | `dnf search ` | | 情報表示 | `apt show ` | `dnf info ` | | 導入済み一覧 | `apt list --installed` | `dnf list installed` | | 自動導入の不要パッケージ整理 | `sudo apt autoremove` | `sudo dnf autoremove` | | キャッシュクリア | `sudo apt clean` | `sudo dnf clean all` | | リポジトリ一覧 | `apt policy` | `dnf repolist` | ::: tip **覚え方のコツ**:`install` / `remove` / `search` は **共通**。違うのは `update` まわりと「設定ファイルまで消すか」の表現。 ::: ## 2. apt:最低限の流れ {#apt-basics} > **結論**: apt は `update` で一覧更新してから install・検索・upgrade・remove/purge の順で操作するのが基本。 ここからはシステムを実際に変更するコマンドです。 **何が変わるか**と**戻し方**を先に押さえてください。 | コマンド | 実行すると何が変わるか | 戻し方 | 安全に試す方法 | | ------------ | -------------------------------------------------------- | ---------------------------------------- | ----------------------------------------------- | | `update` | パッケージ一覧(インデックス)だけが新しくなる | 戻す必要なし(システムは変わらない) | そのまま実行してよい | | `install` | パッケージ本体と依存パッケージが入る | `remove` で削除 | `apt-get install -s ` で予定だけ確認 | | `upgrade` | 既存パッケージのバージョンが上がる | 個別にバージョン指定して戻す(手間大) | `apt list --upgradable` で対象を先に一覧 | | `remove` | 実行ファイルが消える(設定ファイルは残る) | `install` で入れ直す | `apt-get remove -s ` で削除範囲を確認 | | `purge` | 実行ファイルと `/etc/` 配下の設定ファイルが消える | 設定は復元不可。事前バックアップが必要 | 先に設定ファイルを控えてから実行 | | `autoremove` | 「不要」と判定された依存パッケージがまとめて消える | 個別に `install` で入れ直す | 実行前に表示される削除一覧を必ず読む | ::: danger `purge` と `autoremove` は削除範囲が広く、取り返しがつきにくい操作です。 `purge` は `/etc/` 配下の設定ファイルまで消すので、独自に書いた設定はバックアップなしでは戻せません。 `autoremove` は判定を誤ると稼働中サービスの依存パッケージまで削除します。 どちらも実行前に「これから何が消えるか」の一覧を必ず読んでから `y` を押してください。 ::: ### 2-1. 一覧更新 → インストール ```bash $ sudo apt update $ sudo apt install nginx ``` ::: warning `apt update` を **省略するとインストールに失敗する**ことがある(古いインデックスを参照して 404)。新規 VM や久々のサーバでは必ず先に実行する。 ::: ### 2-2. 検索(名前があやふやなとき) ```bash $ apt search nginx $ apt show nginx ``` `apt search` は **説明文も含めて** 全文検索される。「nginx で検索したのに大量に出てくる」のはこのため。**正式名だけに絞る**には `apt list 'nginx*'` のようにパターンで絞る方が確実。 ```bash $ apt list 'nginx*' ``` ```output Listing... Done nginx/jammy-updates 1.18.0-6ubuntu14.4 amd64 nginx-common/jammy-updates 1.18.0-6ubuntu14.4 all nginx-core/jammy-updates 1.18.0-6ubuntu14.4 amd64 ``` `パッケージ名/配布元 バージョン アーキテクチャ` の順に並びます。 導入済みのものだけを見たい場合は次のようにします。 ```bash $ apt list --installed 2>/dev/null | grep nginx ``` ### 2-3. アップグレード ```bash $ sudo apt update $ sudo apt upgrade # 既存パッケージのバージョン更新 $ sudo apt full-upgrade # 依存解決のため削除も許容(カーネル更新等) ``` ::: warning `apt upgrade` と `apt full-upgrade`(旧 `dist-upgrade`)は別物。**カーネルや systemd の大規模更新**では `full-upgrade` が必要なケースがある。本番サーバでは事前に変更点を確認すること。 ::: ### 2-4. 削除 ```bash $ sudo apt remove nginx # バイナリだけ削除(設定ファイルは残る) $ sudo apt purge nginx # 設定ファイルも削除 $ sudo apt autoremove # 依存で入った不要パッケージを掃除 ``` ::: tip `apt remove` は **/etc/ 配下の設定を残す**。再インストール時に「前の設定が生きていて挙動が変」と感じたら `purge` で消す。 ::: ## 3. yum / dnf:最低限の流れ {#dnf-basics} > **結論**: RHEL 8 以降は dnf が標準で yum は互換リンク。スクリプトでは dnf を直接書く方が将来も安全。 ### 3-1. yum と dnf の関係 - **CentOS 7 / RHEL 7**:`yum` がネイティブ - **CentOS 8 / Rocky 8 以降・RHEL 8 以降・Fedora**:`dnf` がネイティブ。`yum` コマンドは `dnf` への互換シンボリックリンク(実体は `/usr/bin/dnf-3` などにリンク) ::: warning RHEL 8+ で `yum install ...` と打っても動くが、**実行されているのは `dnf`**。スクリプトを書くときは `dnf` を直接書く方が将来も安全。 ::: ### 3-2. 基本操作 ```bash $ sudo dnf check-update # 更新可能パッケージの確認 $ sudo dnf install nginx $ sudo dnf upgrade # 全体アップグレード $ sudo dnf remove nginx $ dnf search nginx $ dnf info nginx $ dnf list installed | grep nginx ``` ::: warning `dnf remove ` は、そのパッケージに依存している別のパッケージも一緒に削除対象へ入れます。 実行前に表示される削除一覧を読み、想定外のものが含まれていないか確認してください。 RHEL 系は `dnf history undo ` で直前のトランザクションを巻き戻せます(手順は後述)。 ::: ### 3-3. グループインストール(dnf 特有の便利機能) 開発ツール一式など、関連パッケージをまとめて入れる。 ```bash $ dnf grouplist $ sudo dnf groupinstall "Development Tools" ``` Debian 系では同等の概念は薄く、`build-essential` のような **メタパッケージ** に依存させる方式が一般的。 ```bash $ sudo apt install build-essential ``` ## 4. 検索の現実的な使い方 {#search} > **結論**: 名前で絞る・ファイルから逆引きする・公式名を確認する、の 3 手で大半の検索は片付く。 「パッケージ名を覚えていない」が一番多いトラブル。次の 3 手で大半は片付く。 ### 4-1. 名前で絞る ```bash # Debian 系 $ apt list 'php*' 2>/dev/null # RHEL 系 $ dnf list 'php*' ``` ### 4-2. 何のファイルか分かっているとき(コマンドが先に分かっている) `apt-file` / `dnf provides` で **ファイルパスからパッケージを逆引き**できる。 ```bash # Debian 系(apt-file は別途インストールが必要) $ sudo apt install apt-file $ sudo apt-file update $ apt-file search /usr/bin/htop # RHEL 系(標準で使える) $ dnf provides /usr/bin/htop $ dnf provides '*/sshd_config' ``` ::: tip `command not found` で詰まったときは、まず `dnf provides` / `apt-file search` でどのパッケージに入っているかを特定するのが最短。 ::: ### 4-3. 公式名を確認 ```bash $ apt show nginx $ dnf info nginx ``` `Version` / `Source` / `Homepage` を確認すれば、**目的のソフトウェアか同名の別物か** が分かる。 ## 5. リポジトリの確認と追加 {#repo} > **結論**: 有効リポジトリを確認し、追加は公式ベンダーのみ、GPG キーのフィンガープリント照合を必ず行う。 ### 5-1. 現在有効なリポジトリ ```bash # Debian 系 $ apt policy # 各パッケージのソース優先度 $ ls /etc/apt/sources.list.d/ # 追加リポジトリの設定ファイル $ cat /etc/apt/sources.list # RHEL 系 $ dnf repolist # 有効なリポジトリ一覧 $ dnf repolist --all # 無効リポジトリも含めて表示 $ ls /etc/yum.repos.d/ ``` ### 5-2. 公式ではないリポジトリを追加する場合の注意 ::: warning **サードパーティリポジトリは攻撃面が広がる**。 - 信頼できるベンダー公式(Docker、PostgreSQL、Node.js NodeSource 等)のみ追加する - 追加時は **GPG キーのフィンガープリント** を公式手順で照合する - 「とりあえず `curl ... | sudo bash`」は本番サーバでは避ける ::: ### 5-3. リポジトリ追加の現代的な手順(Debian 系) Ubuntu 22.04 以降は `apt-key` が非推奨。`/etc/apt/keyrings/` 配下に GPG キーを置き、`signed-by=` でリポジトリ定義から参照する方式が推奨。 ```bash # キー配置(公式手順に従う) $ sudo install -d -m 0755 /etc/apt/keyrings $ curl -fsSL https://download.example.com/key.gpg \ | sudo tee /etc/apt/keyrings/example.gpg > /dev/null # リポジトリ定義 $ echo "deb [signed-by=/etc/apt/keyrings/example.gpg] https://download.example.com/apt stable main" \ | sudo tee /etc/apt/sources.list.d/example.list $ sudo apt update ``` ### 5-4. フィンガープリントの照合方法 フィンガープリント(指紋)とは、鍵を短い文字列に要約した識別子である。同じ鍵なら必ず同じ値になるため、これを突き合わせれば「配布元が用意した本物の鍵か」を確認できる。 配置した鍵の指紋を表示し、ベンダー公式サイトに記載された値と 1 文字ずつ突き合わせる。表示するだけのコマンドなので何度でも実行してよい。 ```bash $ gpg --show-keys --fingerprint /etc/apt/keyrings/example.gpg ``` 一致しない場合、その鍵は公式のものではない。追加を中止し、`/etc/apt/keyrings/` と `/etc/apt/sources.list.d/` に置いたファイルを削除する。 ## 6. 定番トラブルの切り分け {#troubleshooting} > **結論**: パッケージ未検出・ロック・GPG エラー・ディスク不足が定番で、それぞれ決まった切り分け手順がある。 ### 6-1. `Unable to locate package `(apt) 原因の優先度: 1. `apt update` を実行していない 2. パッケージ名が違う(`python` ではなく `python3` 等) 3. リポジトリ自体が無効(`universe` / `multiverse` が無効化されている等) 切り分け: ```bash $ sudo apt update $ apt search <近い単語> $ apt-cache policy ``` ### 6-2. `No match for argument`(dnf) ほぼ同じ。`dnf clean all && sudo dnf makecache` で **メタデータを作り直す**と治ることがある。 ```bash $ sudo dnf clean all $ sudo dnf makecache $ dnf search <近い単語> ``` ### 6-3. `Could not get lock /var/lib/dpkg/lock` 別の `apt` プロセスが動作中、または前回異常終了した残骸。 ::: warning ロックファイルの**中身は空**である。apt / dpkg は「ファイルが存在するか」ではなく `flock`(ファイルに掛ける排他ロック)で排他している。 つまり「ファイルがあるから消す」という発想自体が誤りで、削除は**誰も掴んでいないことを確認した後の最終手段**である。 ::: まず、4 つのロックファイルを誰かが掴んでいないかを確認する。`lsof` は表示するだけで何も変更しない。 ```bash $ sudo lsof /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock \ /var/cache/apt/archives/lock /var/lib/apt/lists/lock ``` 出力が空であれば、掴んでいるプロセスはない。その場合に限り削除してよい。 ```bash $ sudo rm /var/lib/dpkg/lock-frontend $ sudo rm /var/lib/dpkg/lock $ sudo rm /var/cache/apt/archives/lock $ sudo rm /var/lib/apt/lists/lock $ sudo dpkg --configure -a ``` ::: danger `lock` ファイルを **動作中のプロセスがある状態で削除すると dpkg のデータベースが壊れる**。必ず `lsof` で空を確認してから実行すること。 詳細な切り分けは [dpkg のロックが解除されないとき](/articles/troubleshooting/dpkg-lock-held) を参照。 ::: ### 6-4. GPG エラー(`NO_PUBKEY` / `Signature couldn't be verified`) サードパーティリポジトリのキー期限切れ、または不正に追加されたリポジトリ。 ```bash $ sudo apt update # → NO_PUBKEY ABCD1234... が表示される ``` 対処は **公式手順で正規のキーを再取得して `/etc/apt/keyrings/` 配下に置き直す**。期限切れと判明していない `apt-key` 削除や `--allow-unauthenticated` は使わない。 ### 6-5. ディスク不足でインストール失敗 ```bash $ df -h /var /usr $ sudo apt clean # ダウンロード済み .deb を削除 $ sudo dnf clean all # 同様にキャッシュを削除 ``` `/var/cache/apt/archives/` や `/var/cache/dnf/` が肥大化していることが多い。 ## 7. 依存関係を壊さないための実務ルール {#rules} > **結論**: 本番では事前確認なしの upgrade や `curl | bash` を避け、更新前の確認とロールバック手段を用意する。 ::: warning **やってはいけないこと** - バージョン指定なしで本番サーバに `apt upgrade` をかける(直前に変更点を確認しないまま実行) - 公式手順を読まずに `sudo` で `curl ... | bash` - `--force-yes` / `--allow-downgrades` を反射的に付ける - 複数のソースから同一パッケージを入れる(リポジトリ優先度の混乱) ::: ::: tip **安全に運用するチェックリスト** 1. インストール前に **`apt update` / `dnf makecache`** 2. **`-s`(apt のシミュレーション)** で予定変更を確認できる:`apt-get install -s ` 3. RHEL 系は **トランザクション履歴** で巻き戻せる: ```bash $ dnf history list $ sudo dnf history undo ``` 4. **`apt list --upgradable`** で更新対象を一覧してから `upgrade` 5. リポジトリ追加は **GPG キーのフィンガープリント照合** を必ず実施 ::: ## 8. コピペ用テンプレート {#templates} > **結論**: Debian 系と RHEL 系それぞれに初回設定から検索・更新・削除・巻き戻しまでの定型コマンドを用意した。 ::: tip **Debian / Ubuntu** ```bash # 初回セットアップ sudo apt update && sudo apt upgrade -y # 検索 → 情報確認 → インストール apt list 'nginx*' apt show nginx sudo apt install nginx # 更新対象だけ確認してから上げる apt list --upgradable sudo apt upgrade # 削除(設定も含めて) sudo apt purge nginx && sudo apt autoremove ``` ::: ::: tip **RHEL / Rocky / AlmaLinux** ```bash # 初回セットアップ sudo dnf upgrade -y # 検索 → 情報確認 → インストール dnf search nginx dnf info nginx sudo dnf install nginx # 更新対象だけ確認 dnf check-update # 履歴で巻き戻し dnf history list sudo dnf history undo ``` ::: ## 次に読む {#next} - [「command not found」の対処法](/articles/tutorials/command-not-found) - [journalctl の使い方:ログ調査の基本](/articles/tutorials/journalctl-basics) - [ディスクがいっぱいになったときの調べ方](/articles/troubleshooting/no-space-left-on-device) # bash 配列入門 - 配列(array)と連想配列の使い方 Source: https://penguin-gym-linux.com/articles/tutorials/arrays-in-bash ## この記事で解決できること {#intro} - bash の **インデックス配列** と **連想配列** の違いと使い分けが分かる - 宣言・参照・ループ・要素追加/削除の **基本操作** を一通り押さえられる - `"${arr[@]}"` のクォートや sparse 配列など **定番のハマり** を回避できる ::: tip **結論(先に要点)** - 順番付きの値の集まり → **インデックス配列**(`arr=(a b c)`) - キーと値の対応表 → **連想配列**(`declare -A map` が必須) - ループ・展開では **必ず `"${arr[@]}"`** とダブルクォートする ::: ::: warning **前提(対象環境)** - シェル: **bash 4.0 以降**(連想配列は 4.0+、負の添字は 4.3+) - `sh` / `dash` では配列は使えない。`#!/bin/bash` を明示すること ::: ## 配列とは何か? {#what} > **結論**: 配列は複数の値を 1 つの変数にまとめる仕組み。bash には順序を持つインデックス配列と、キーで引く連想配列の 2 種類がある。 通常のシェル変数は値を 1 つしか持てない。複数のファイル名やユーザー名をまとめて扱いたいとき、空白区切りの 1 文字列で済ませると空白を含む値で破綻する。配列なら各要素を独立した値として安全に保持できる。 bash の配列は 2 種類ある。 - **インデックス配列(indexed array)**: `0` から始まる整数の添字で要素を管理する - **連想配列(associative array)**: 任意の文字列をキーにして値を引く(ハッシュ/辞書に相当) ## インデックス配列はどう使うのか? {#indexed} > **結論**: `arr=(a b c)` で宣言し、`${arr[0]}` で個別要素、`"${arr[@]}"` で全要素を参照する。添字は省略すると先頭要素を指す。 ### 宣言と代入 ```bash # まとめて宣言 fruits=(apple banana cherry) # 添字を指定して代入 fruits[3]=durian # 末尾に追加(+= 演算子) fruits+=(elderberry fig) ``` ### 参照 ```bash echo "${fruits[0]}" # apple(最初の要素) echo "${fruits[-1]}" # fig(末尾、bash 4.3+) echo "${fruits[@]}" # 全要素をスペース区切りで echo "${#fruits[@]}" # 要素数(6) echo "${!fruits[@]}" # 全インデックス(0 1 2 3 4 5) ``` ::: warning `$fruits` のように添字なしで参照すると **`${fruits[0]}` と同じ**(先頭要素のみ)になる。全要素を扱うつもりの取り違えに注意する。 ::: ### スライス(部分取り出し) ```bash echo "${fruits[@]:1:2}" # banana cherry(添字1から2個) ``` ## 連想配列はどう使うのか? {#assoc} > **結論**: 連想配列は使用前に `declare -A` で必ず宣言する。宣言を忘れるとインデックス配列扱いになり、キーが 0 に丸められて壊れる。 連想配列は任意の文字列をキーにできる。**先に `declare -A` で宣言することが必須**である点がインデックス配列との最大の違い。 ```bash declare -A capital # これを忘れると壊れる capital[japan]=Tokyo capital[france]=Paris capital["united states"]=Washington echo "${capital[japan]}" # Tokyo echo "${!capital[@]}" # 全キー(順序は不定) echo "${capital[@]}" # 全値(順序は不定) echo "${#capital[@]}" # 要素数(3) ``` ::: danger `declare -A` を省略して `capital[japan]=Tokyo` とすると、bash は `japan` を **算術評価して 0** と解釈し、インデックス配列の添字 0 に代入してしまう。連想配列は宣言が前提。 ::: 連想配列は **順序を保持しない**。挿入順やキー順での出力は保証されないため、整列したいなら `sort` に通す。 ## 配列をループで回すには? {#loop} > **結論**: 値を回すなら `for x in "${arr[@]}"`、キー/添字を回すなら `for k in "${!arr[@]}"`。どちらもダブルクォートが必須。 ```bash # 値を順に処理する for f in "${fruits[@]}"; do echo "fruit: $f" done # キー(連想配列)を回して値を引く for country in "${!capital[@]}"; do echo "$country -> ${capital[$country]}" done ``` クォートを外して `for f in ${fruits[@]}` と書くと、**空白を含む要素が単語分割される**。`apple pie` という 1 要素が `apple` と `pie` の 2 つに割れる。値の安全性のため常に `"${arr[@]}"` とする。 ## `"${arr[@]}"` と `"${arr[*]}"` の違いは? {#at-vs-star} > **結論**: `[@]` は各要素を別々の単語として展開し、`[*]` は IFS の先頭文字で連結した 1 つの文字列にする。ループには `[@]` を使う。 | 記法 | 展開結果 | 主な用途 | | ------------- | ------------------------------- | ---------------- | | `"${arr[@]}"` | 要素ごとに独立した単語 | ループ・引数渡し | | `"${arr[*]}"` | IFS 先頭文字で連結した 1 文字列 | 表示・文字列化 | ```bash arr=(one two three) printf '[%s]\n' "${arr[@]}" # [one] [two] [three] が別行 printf '[%s]\n' "${arr[*]}" # [one two three] が1行 # 区切り文字を変えて連結する IFS=, echo "${arr[*]}" # one,two,three unset IFS ``` ## 要素の追加・削除はどうするのか? {#modify} > **結論**: 追加は `arr+=(...)`、削除は `unset 'arr[2]'`。ただし unset したインデックス配列は添字が詰まらず sparse(穴あき)になる。 ```bash arr=(a b c d) arr+=(e) # 末尾に追加 -> a b c d e unset 'arr[1]' # 添字1(b)を削除 echo "${arr[@]}" # a c d e echo "${!arr[@]}" # 0 2 3 4 ← 添字1が欠ける(詰まらない) ``` ::: warning `unset` 後の配列は **連番でなくなる**(sparse 配列)。`${#arr[@]}` の要素数と最大添字が一致しなくなるため、添字を `0..N-1` で決め打ちするループは壊れる。詰め直すには再代入する。 ```bash arr=("${arr[@]}") # 添字を 0,1,2... に振り直す ``` ::: ## つまずきやすいポイントは? {#pitfalls} > **結論**: 配列は bash 専用、クォート必須、連想配列は declare -A 必須。この 3 点を外すと典型的な事故になる。 ::: danger **やってはいけない / 事故りやすい** - `#!/bin/sh` で配列を使う → `dash` 等では構文エラー。`#!/bin/bash` を使う - `for x in ${arr[@]}`(クォートなし)→ 空白入り要素が分割される - `declare -A` を忘れて連想配列を使う → キーが 0 に丸まり全要素が上書きされる - `arr[0]` を `$arr` と書く → 全要素のつもりが先頭だけ ::: コマンド出力を配列に取り込むときは、行単位で安全に読む `mapfile`(`readarray`)が便利。 ```bash mapfile -t lines < <(ls -1) # 各行を1要素として lines に格納 echo "${#lines[@]}" # 行数 ``` `-t` は各行末尾の改行を取り除くオプション。これを付けないと要素に `\n` が残る。 ## まとめ {#summary} - インデックス配列 `arr=(a b c)` は順序付き、連想配列 `declare -A map` はキー引き - 全要素は `"${arr[@]}"`、要素数は `${#arr[@]}`、添字/キーは `${!arr[@]}` - ループ・展開では **常にダブルクォート**し、単語分割事故を防ぐ - 連想配列は **`declare -A` が必須**、`unset` 後の配列は sparse になる点に注意 次に読む記事: - [シェルスクリプト入門](/articles/tutorials/shell-scripting-basics) - [パラメータ展開の使い方](/articles/tutorials/parameter-expansion) - [コマンド置換でコマンド結果を変数に取り込む](/articles/tutorials/command-substitution) # at コマンド入門 - 一度きりのジョブを予約実行する Source: https://penguin-gym-linux.com/articles/tutorials/at-command-scheduling ## この記事で解決できること {#intro} - `at` で **一度きりのジョブを予約** する方法が分かる - `atq` / `atrm` で **予約済みジョブを確認・取り消し** できる - `cron` と `at` の **使い分け** がはっきりする ::: tip **結論(実務の型)** - **1回だけ・指定時刻に実行** → `at` - **繰り返し・定期実行** → `cron` / systemd timer - まず `atd` が動いているか確認、ジョブ管理は `atq` と `atrm` の2つで足りる ::: ::: warning **前提(対象環境)** - OS: Ubuntu / Debian 系(RHEL 系も基本同じ) - `at` パッケージと `atd` デーモンが必要(既定では未インストールのことが多い) ::: ## at コマンドとは? {#what} > **結論**: `at` は指定した時刻に **一度だけ** コマンドを実行する予約ツール。繰り返さない単発処理に向く。 `cron` が「毎日・毎週」のような繰り返し実行を担うのに対し、`at` は「今夜23時に1回だけ」「5分後に1回だけ」という **単発のスケジュール実行** を担う。 予約されたジョブは `atd`(at デーモン)がキューを監視し、時刻が来たら実行する。ログインを切っても、シェルを閉じても予約は残る。 ::: tip 似た用途に `batch` がある。`batch` は時刻ではなく **システム負荷が下がったタイミング** でジョブを実行する。重いバッチ処理を混雑時間帯から外したいときに使う。 ::: ## at は使える状態か? atd の確認とインストール {#setup} > **結論**: `at` 本体だけでなく `atd` が起動していないとジョブは実行されない。まず動作状態を確認する。 多くの Ubuntu / Debian 環境では `at` は初期状態で入っていない。次の手順で導入・確認する。 ```bash # インストール(Debian / Ubuntu) $ sudo apt install at # RHEL / CentOS / Rocky の場合 $ sudo dnf install at ``` デーモンの起動状態を確認する。 ```bash $ systemctl status atd ``` ```output ● atd.service - Deferred execution scheduler Loaded: loaded (/lib/systemd/system/atd.service; enabled; preset: enabled) Active: active (running) since ... ``` `active (running)` でなければ起動・自動起動を有効化する。 ```bash $ sudo systemctl enable --now atd ``` ::: warning 予約したのに実行されない事故の多くは「`atd` が起動していない」が原因。`at` でジョブを登録できても、`atd` が止まっていれば時刻が来ても何も起きない。 ::: ## どうやってジョブを予約するのか? {#basic-usage} > **結論**: `at 時刻` を実行し、対話プロンプトにコマンドを入力して `Ctrl+D` で確定する。スクリプトは `-f` で渡す。 ### 対話で登録する ```bash $ at now + 5 minutes warning: commands will be executed using /bin/sh at> echo "hello" >> /tmp/at-test.log at> job 3 at Fri Jun 5 23:10:00 2026 ``` `at>` プロンプトでコマンドを入力し、最後に `Ctrl+D` を押す(表示上は ``)。登録されるとジョブ番号と実行予定時刻が表示される。 ### ファイルから登録する(推奨) 対話入力はタイプミスに弱い。実務ではスクリプトを `-f` で渡すほうが安全。 ```bash $ at -f /path/to/job.sh 23:00 ``` ```bash # ワンライナーをパイプで渡す $ echo 'tar czf /backup/data.tgz /var/www' | at 02:00 tomorrow ``` ::: tip 予約時の **カレントディレクトリ・環境変数・umask は登録した時点の状態が保存** され、実行時に復元される。ただし `PATH` に依存する書き方は避け、コマンドは絶対パスで書くのが安全。 ::: ## 時刻指定はどう書くのか? {#timespec} > **結論**: `HH:MM`、`now + N units`、`midnight` / `noon` / `teatime`、日付指定など柔軟に書ける。相対指定が最も実用的。 `at` の時刻表現は柔軟。代表的なパターンを挙げる。 | 書き方 | 意味 | | --------------------- | ------------------------------ | | `at 23:00` | 今日の23時(過ぎていれば翌日) | | `at 10:00 AM` | 午前10時 | | `at now + 30 minutes` | 30分後 | | `at now + 2 hours` | 2時間後 | | `at midnight` | 今夜0時 | | `at noon` | 正午 | | `at teatime` | 16時(ティータイム) | | `at 02:00 tomorrow` | 明日の2時 | | `at 10:00 next week` | 来週の同曜日10時 | | `at 2026-12-31 23:59` | 日付+時刻を明示 | ```bash # 例: 明日の午前3時にログをローテート $ echo '/usr/local/bin/rotate-logs.sh' | at 3:00 tomorrow ``` ::: warning 時刻を指定せず過ぎた時刻を書くと、`at` は **翌日の同時刻** として解釈する。「すぐ実行されるはず」が翌日にずれる事故に注意。確実に近い未来へ入れたいときは `now + N minutes` が安全。 ::: ## 予約したジョブを確認・取り消すには? {#atq-atrm} > **結論**: 一覧は `atq`、削除は `atrm ジョブ番号`、中身の確認は `at -c ジョブ番号`。この3つで管理は足りる。 ### 予約一覧を見る(atq) ```bash $ atq ``` ```output 3 Fri Jun 5 23:10:00 2026 a user 5 Sat Jun 6 02:00:00 2026 a user ``` 左から **ジョブ番号 / 実行予定時刻 / キュー名 / 所有者**。`a` は通常キュー、`b` は `batch` キューを表す。 ### ジョブを取り消す(atrm) ```bash $ atrm 3 ``` 番号を指定して削除する。複数まとめて消すこともできる。 ```bash $ atrm 3 5 ``` ### 中身を確認する(at -c) 実行前に「何が登録されているか」を確認したいときは `-c`。 ```bash $ at -c 5 ``` 保存された環境変数の復元処理と、実際に走るコマンド本体が表示される。 ::: tip `atq` は `at -l`、`atrm` は `at -d` と同じ。スクリプト内では明示的な `atq` / `atrm` のほうが読みやすい。 ::: ## 実行結果はどこに出るのか? {#output} > **結論**: ジョブの標準出力・標準エラーは **メール** でユーザーに送られる。メールが無い環境では出力が消えるため、ファイルへリダイレクトする。 `at` で実行したコマンドの出力は、端末ではなく **登録ユーザー宛のローカルメール** に届く。`mail` コマンドや `/var/mail/` で確認できる。 ただしサーバーに MTA(メール配送)が無い構成では出力が捨てられる。結果を確実に残すには、ジョブ側でリダイレクトする。 ```bash $ echo '/usr/local/bin/backup.sh > /var/log/backup.log 2>&1' | at 02:00 ``` ::: warning 「実行されたはずなのに結果が見当たらない」ときは、メールに飛んでいるか、出力先を指定し忘れている可能性が高い。本番ジョブは必ずログファイルへ `> ... 2>&1` でリダイレクトする。 ::: ## cron と at はどう使い分けるのか? {#vs-cron} > **結論**: 繰り返すなら `cron` / systemd timer、1回だけなら `at`。「今夜だけ」「メンテ後に1回」のような単発処理は `at` が最適。 | 用途 | 推奨 | | ------------------------------------ | -------------------- | | 毎日・毎週など定期実行 | cron / systemd timer | | 指定時刻に1回だけ | at | | 負荷が下がったら実行 | batch | | 高信頼な定期ジョブ(ログ・依存管理) | systemd timer | 単発作業を `cron` に書いて「実行後に消し忘れて翌日も動く」事故は珍しくない。1回限りと分かっているなら `at` のほうが後始末が要らず安全。 定期ジョブ全般の組み方は [cron の基本](/articles/tutorials/cron-basics) と [systemd timer と cron の使い分け](/articles/tutorials/systemd-timer-vs-cron) を参照。 ## アクセス制御と注意点 {#access} > **結論**: `at` の利用可否は `/etc/at.allow` と `/etc/at.deny` で制御する。`at.allow` があればそれが優先される。 - `/etc/at.allow` が存在する場合: 記載されたユーザーのみ `at` を使える - `at.allow` が無く `/etc/at.deny` がある場合: 記載ユーザー以外が使える - 両方無い場合: 多くのディストロでは root のみ(ディストロにより挙動差あり) ```bash # 特定ユーザーのみ許可したいとき $ echo 'deploy' | sudo tee -a /etc/at.allow ``` ::: warning 共有サーバーでは `at` がジョブ実行の踏み台になり得る。不要なら `at.deny` で絞るか `atd` を止める。逆に運用で使うなら `at.allow` で明示許可する設計が安全。 ::: ## まとめ {#summary} - `at` は **一度きりのジョブを予約** するためのコマンド。繰り返しは `cron` の担当 - 実行には `atd` の起動が必須。動かないときはまずデーモン状態を確認 - 時刻は `now + N minutes` などの相対指定が安全。過去時刻は翌日扱いになる - 管理は `atq`(一覧)/ `atrm`(削除)/ `at -c`(内容確認)の3つ - 出力はメールに届く。本番ジョブはログへリダイレクトする ## 次に読む {#next} - [cron の基本](/articles/tutorials/cron-basics) - [systemd timer と cron の使い分け](/articles/tutorials/systemd-timer-vs-cron) - [systemctl の使い方](/articles/tutorials/systemctl-basics) # awk ワンライナー集 - 実務で使う 30 パターン Source: https://penguin-gym-linux.com/articles/tutorials/awk-oneliners ## この記事でできること {#intro} - `awk` で**フィールド抽出・集計・置換**の実務パターンを即戦力として使える - CSV/TSV・ログ・数値データなど用途別に 30 パターンを整理 - コピペして `awk` を先頭に置くだけで動くワンライナーを習得できる ::: tip **結論:awk の 3 つの使い道** 1. **フィールド抽出** — `awk '{print $N}'` で列を切り出す(`cut` より強力) 2. **集計・計算** — END ブロックで合計・平均・最大値を出す 3. **変換・整形** — `sub` / `gsub` で正規表現置換、`printf` でフォーマット整形 ::: ::: warning **前提** - GNU awk(`gawk`)を想定。macOS 標準の `awk` は POSIX 準拠で機能が一部異なる - サンプルは stdin をパイプで受け取るワンライナーとしても動く ::: ## awk の基本構文はどう書くのか? {#syntax} `awk 'パターン { アクション }' ファイル` の形式で動く。パターンが空なら全行に、アクションが空なら `{print}` が適用される。 ```bash # 基本形 awk '{ アクション }' ファイル awk 'パターン' ファイル awk 'パターン { アクション }' ファイル ``` **よく使う組み込み変数:** | 変数 | 意味 | | ----------- | ------------------------------------------- | | `$0` | 行全体 | | `$1, $2...` | 1列目、2列目... | | `NF` | フィールド数(列数) | | `NR` | 現在の行番号(全ファイル通算) | | `FNR` | 現ファイル内の行番号 | | `FS` | 入力区切り文字(デフォルト: スペース/タブ) | | `OFS` | 出力区切り文字(デフォルト: スペース) | ```bash # -F で区切り文字を指定 awk -F: '{print $1}' /etc/passwd # コロン区切りで1列目 awk -F, '{print $2}' data.csv # CSV の 2 列目 ``` ## フィールド操作 — どうやって列を抽出・変換するのか? {#fields} 特定フィールドの抽出・組み合わせ・並び替えが awk の最頻出用途だ。 ### パターン 1:特定の列を出力 ```bash awk '{print $2}' file.txt # 2列目 awk '{print $1, $3}' file.txt # 1列目と3列目をスペース区切りで awk '{print $1 "\t" $3}' file.txt # タブ区切りで出力 ``` ### パターン 2:フィールドの並び替え ```bash # 1列目と2列目を入れ替えて出力 awk '{print $2, $1}' file.txt ``` ### パターン 3:最後の列と末尾から N 番目 ```bash awk '{print $NF}' file.txt # 最後の列 awk '{print $(NF-1)}' file.txt # 末尾から2列目 ``` ### パターン 4:区切り文字を変換して出力 ```bash # コロン区切りをカンマ区切りに変換 awk -F: '{OFS=","; $1=$1; print}' file.txt # スペース区切りをタブ区切りに変換 awk '{OFS="\t"; $1=$1; print}' file.txt ``` ::: tip `$1=$1` の代入は awk に OFS を使って行全体を再構築させる慣用句。値は変わらないが内部的に `$0` が OFS で再生成される。 ::: ### パターン 5:フィールド数が N 列の行だけ出力 ```bash awk 'NF==5' file.txt # ちょうど 5 列の行 awk 'NF>=3' file.txt # 3 列以上の行 ``` ### パターン 6:空白を詰めて複数の区切りに対応 ```bash # 複数スペース・タブが混在する行を整形 awk '{$1=$1; print}' file.txt ``` ## パターンマッチ・フィルタ — 条件に合う行だけ抽出するには? {#filter} `awk` は `grep` より条件を細かく指定できる。列を指定した絞り込みが強み。 ### パターン 7:文字列を含む行を抽出 ```bash awk '/ERROR/' logfile # ERROR を含む行(grep と同等) awk '/^2026/' access.log # 2026 で始まる行 awk '/ERROR|WARN/' logfile # ERROR または WARN を含む行 ``` ### パターン 8:特定の列で絞り込み ```bash awk '$3 == "200"' access.log # 3列目が 200 の行 awk '$2 > 100' data.txt # 2列目が 100 より大きい行 awk '$1 ~ /^user/' data.txt # 1列目が user で始まる行 awk '$1 !~ /^#/' config.txt # 1列目が # で始まらない行(コメント除外) ``` ### パターン 9:AND / OR 条件 ```bash awk '$1 == "GET" && $9 >= 500' access.log # AND awk '$3 == "404" || $3 == "500"' access.log # OR ``` ### パターン 10:空行をスキップ ```bash awk 'NF > 0' file.txt # フィールドがある行だけ(空行除外) awk '!/^$/' file.txt # 空行を除外(正規表現版) ``` ## 行番号・範囲 — 特定の行を取り出すには? {#lines} ### パターン 11:特定行番号を出力 ```bash awk 'NR==3' file.txt # 3行目だけ awk 'NR>=5 && NR<=10' file.txt # 5〜10行目 ``` ### パターン 12:ヘッダ行をスキップ ```bash awk 'NR>1' data.csv # 1行目(ヘッダ)をスキップ ``` ### パターン 13:奇数行・偶数行を抽出 ```bash awk 'NR%2==1' file.txt # 奇数行 awk 'NR%2==0' file.txt # 偶数行 ``` ### パターン 14:連番を付けて出力 ```bash awk '{print NR ": " $0}' file.txt # 行番号 + コロンで番号付け awk '{printf "%04d %s\n", NR, $0}' file.txt # ゼロパディング ``` ### パターン 15:パターン間の範囲を出力 ```bash # START から END が現れる行まで出力(両端を含む) awk '/START/,/END/' file.txt # ヘッダの次の行から最初の空行まで出力 awk '/^---$/,/^$/' file.txt ``` ## 集計・計算 — 数値データをどう集計するのか? {#aggregate} `BEGIN` / `END` ブロックを使うと全行を処理した後に集計結果を出力できる。 ### パターン 16:列の合計 ```bash awk '{sum += $1} END {print sum}' data.txt awk -F, '{sum += $3} END {print sum}' data.csv # CSV の 3 列目 ``` ### パターン 17:平均 ```bash awk '{sum += $1; count++} END {print sum/count}' data.txt ``` ### パターン 18:最大値・最小値 ```bash awk 'NR==1 {max=$1; min=$1} {if($1>max) max=$1; if($1 10' file.txt # 1列目が 10 文字より長い行 awk 'length > 80' file.txt # 行全体が 80 文字より長い行 ``` ## printf で整形出力 — どうやって揃えた表を作るのか? {#printf} `print` では列幅が揃わないケースに `printf` を使うと表形式にできる。 ### パターン 26:列を揃えて出力 ```bash # 左揃え 20 文字幅 + 右揃え 8 文字幅 awk '{printf "%-20s %8s\n", $1, $2}' file.txt # 数値を小数点以下 2 桁で出力 awk '{printf "%.2f\n", $1}' data.txt ``` ### パターン 27:CSV から表形式に変換 ```bash awk -F, '{printf "%-15s %-10s %-8s\n", $1, $2, $3}' data.csv ``` ## 複数ファイル・ファイル間の比較 {#multifile} ### パターン 28:ファイルごとにヘッダをスキップしてファイル名付き出力 ```bash # FNR は現ファイル内の行番号、NR は全体の行番号 awk 'FNR>1 {print FILENAME, $0}' *.csv ``` ### パターン 29:2 ファイルを比較して差分を出力 ```bash # file1 にあって file2 にない行(簡易 diff) awk 'NR==FNR {a[$0]=1; next} !a[$0]' file1.txt file2.txt ``` ::: tip `NR==FNR` は「最初のファイルを処理中」を意味する定番イディオム。`next` で次の行へスキップし、2ファイル目では `a` の存在チェックを行う。 ::: ### パターン 30:BEGIN ブロックで区切り文字を事前設定 ```bash # BEGIN で変数を初期化、END で集計結果を出力 awk 'BEGIN {FS=","; OFS="\t"} {print $1, $2, $3}' data.csv awk 'BEGIN {print "name\tscore"} {print $1, $2} END {print "---"}' data.txt ``` ## チートシート — よく使うパターン早見表 {#cheatsheet} ::: highlight **フィールド操作** ```bash awk '{print $1}' # 1列目 awk '{print $NF}' # 最後の列 awk '{print $(NF-1)}' # 末尾から2列目 awk -F, '{print $2}' # CSV の 2 列目 awk -F: '{OFS=","; $1=$1; print}' # コロン→カンマ変換 ``` **フィルタ** ```bash awk 'NR>1' # ヘッダ行をスキップ awk '/ERROR/' # ERROR を含む行 awk '$3 >= 500' # 3列目が 500 以上 awk '!seen[$0]++' # 重複行を除外 awk 'NF>0' # 空行を除外 ``` **集計** ```bash awk '{sum+=$1} END{print sum}' # 合計 awk 'END{print NR}' # 行数(wc -l 相当) awk '{c[$1]++} END{for(k in c) print k, c[k]}' # 値ごと件数 ``` ::: ## 次に読む {#next} - [grep・awkの応用テクニック](/articles/tutorials/find-grep-awk-advanced) - [sed 入門](/articles/tutorials/sed-basics) - [cut/paste/tr 入門](/articles/tutorials/cut-paste-tr-basics) # base64 コマンド入門 - エンコード・デコードの基本 Source: https://penguin-gym-linux.com/articles/tutorials/base64-encoding ## この記事でわかること {#intro-list} - `base64` で **文字列やファイルをエンコード / デコード** できます - **エンコードと暗号化とハッシュ** を言い分けられます - `echo` の **改行が混ざる落とし穴** を避けられます - `-w`(折り返し)や `-d`(デコード)など **よく使うオプション** がわかります **対象読者**:Linux 入門者、API トークンやメール添付で `base64` 文字列を見て「これは何か」と思った方 ::: tip **言葉の整理(ここが一番混同されます)** - **エンコード**:データの **書き方を変える** ことです。鍵はいりません。誰でも元に戻せます。`base64` はこれです。 - **暗号化**:鍵を持つ人だけが元に戻せるようにすることです。鍵がなければ中身は読めません。`openssl` などが行います。 - **ハッシュ**:中身から短い値を作ることです。**元に戻せません**。同じかどうかの確認に使います。`sha256sum` などが行います。 3 つとも「見た目が変な文字列になる」点は同じです。しかし **戻せるか / 鍵がいるか** が違います。この記事の `base64` は「鍵なしで誰でも戻せる」種類です。 ::: ## 導入:リナの謎の文字列事件 {#intro} ::: dialogue @lina: ライニー先輩、設定ファイルに `SGVsbG8gV29ybGQ=` みたいな謎の文字列がありました。これは何ですか。パスワードを暗号化したものですか。 @linny: いいところに気づいたね。それは `base64`(ベースろくじゅうよん)でエンコードされた文字列だよ。でも大事なことを先に言っておく。それは **暗号化ではない** んだ。 @lina: 暗号化ではない。では中身は読めてしまうのですか。 @linny: そう。`base64 -d` を通すだけで誰でも元に戻せる。だから秘密を守る用途には使えない。今日はその `base64` の基本と、なぜ存在するのかを見ていこう。 ::: ::: tip **結論を先に** - `base64` = **バイナリを 64 種類の ASCII 文字だけで表現** する変換です(`A-Z` `a-z` `0-9` `+` `/`)。末尾に付く `=` は長さを合わせるための詰め物です - エンコードは `base64`、デコードは `base64 -d` です - **暗号化ではありません**。誰でもデコードできます。秘密の保護には使いません ::: ## 1. base64 とは何か? {#what} > **結論**: base64 はバイナリデータを 64 種類の ASCII 文字に変換する方式。暗号化ではなく「運搬用の包装」。 ::: dialogue @lina: そもそも、なぜこの変換が必要なのですか。 @linny: いい質問。メールや一部のプロトコルは **テキスト(ASCII 文字)しか安全に運べない** ことがあるんだ。画像のようなバイナリをそのまま流すと、途中で壊れることがある。 @lina: 文字に変換しておけば安全に運べる、ということですね。 @linny: その通り。`base64` は **どんなデータでも 64 種類の文字だけで表現** する。だから「暗号」ではなく「**運搬用の包装**」だと思うといい。中身は隠れないけれど、安全に運べる形になる。 ::: ::: highlight **base64 が使われる場所** - メールの添付ファイル(MIME) - HTTP の Basic 認証ヘッダー - 設定ファイル・JSON に埋め込むバイナリ(画像・証明書など) - `data:` URL(HTML に画像を直接埋め込む) ::: ## 2. 文字列をエンコードしてみる {#encode} > **結論**: `echo -n` でパイプして `base64` に渡す。`-n` を付けないと末尾の改行まで一緒にエンコードされる。 ::: dialogue @linny: まずは文字列を実際にエンコードしてみよう。`Hello World` を変換するよ。 @lina: パイプ(`|`)で `base64` に渡すのですね。 ::: ```bash $ echo -n "Hello World" | base64 ``` ```output SGVsbG8gV29ybGQ= ``` ::: warning **`echo -n` の `-n` を忘れない** `echo` はデフォルトで **末尾に改行を付けます**。`-n` なしだと改行 1 文字(`\n`)まで一緒にエンコードされます。その結果、出てくる文字列が変わります。 ::: ::: dialogue @lina: 先輩、API のトークンを `base64` にして送ったのですが、認証が通りませんでした。文字列はコピーしたはずです。 @linny: `echo` に `-n` を付けたかな。 @lina: 付けていません。改行が 1 文字だけ増えると、そんなに変わるものですか。 @linny: 変わるよ。実際に見比べてみよう。最後の数文字が別物になっているはずだ。 ::: ```bash $ echo "Hello World" | base64 ``` ```output SGVsbG8gV29ybGQK ``` ::: dialogue @lina: 本当だ。末尾が `=` から `K` に変わっています。目に見えない改行が原因だったんですね。 @linny: そう。**見えない 1 文字が別のデータを作る**。だからトークンを扱うときは `echo -n` か `printf` を使う、と決めておくといいよ。 ::: ::: tip **`SGVsbG8gV29ybGQ=` と `SGVsbG8gV29ybGQK` の違い** 末尾が `=`(改行なし)か `K`(改行 `\n` を含む)かで変わります。トークンやパスワードを `base64` 化するとき、この改行混入は **トラブルの定番** です。必ず `echo -n` か `printf` を使ってください。 ::: ```bash # printf は改行を付けないので -n の付け忘れを防げる $ printf '%s' "Hello World" | base64 ``` ## 3. デコードしてみる {#decode} > **結論**: `base64 -d`(または `--decode`)で元に戻す。誰でも実行できるので秘密保護にはならない。 ::: dialogue @linny: 次は逆。さっきの `SGVsbG8gV29ybGQ=` を元に戻してみよう。`-d` を付けるだけだよ。 @lina: デコードには特別な鍵はいらないのですか。 @linny: いらない。だからこそ「暗号化ではない」と最初に言ったんだ。手元にコマンドさえあれば、誰でも中身が見える。 ::: ```bash $ echo "SGVsbG8gV29ybGQ=" | base64 -d ``` ```output Hello World ``` ::: tip **`-d` と `--decode` は同じ** `base64 -d` でも `base64 --decode` でも動きます。短い `-d` が一般的です。 ::: ::: dialogue @lina: 冒頭の設定ファイルの謎の文字列も、これで中身が読めるということですか。 @linny: そういうこと。だから **API キーやパスワードを base64 にしただけで「隠した」と思ってはいけない**。隠したいなら暗号化が必要だよ。鍵を使う `openssl` などの出番だ。 ::: ## 4. ファイルをエンコード・デコードする {#file} > **結論**: 引数にファイル名を渡せばエンコード、`-d` で出力をファイルにリダイレクトすれば復元できる。 ::: dialogue @linny: 文字列だけでなく、ファイルもそのまま渡せる。まずは練習用のファイルを 1 つ作ろう。 ::: ```bash # 練習用ファイルを作る $ echo "practice" > sample.txt # ファイルをエンコードして .b64 に保存 $ base64 sample.txt > sample.txt.b64 # .b64 をデコードして別名で復元する $ base64 -d sample.txt.b64 > restored.txt ``` 画像や証明書のようなバイナリも、まったく同じ手順で変換できます。 ::: warning **どこに作られるか・上書きの危険** - 新しいファイルは **いま自分がいるディレクトリ** に作られます。`pwd` で確認できます - `>` は **リダイレクト** です。同じ名前のファイルがあれば、**中身を消して上書きします** - 確認は聞かれません。GUI と違い、上書きされた中身はゴミ箱にも残りません - 上書きが怖いときは、先に `ls restored.txt` で同じ名前がないか確認してください - `base64` は本サイトの仮想ターミナルでは動きません。実行は自分のパソコンのターミナルで行います - 練習は「7. ミニ課題」で作る、からっぽのディレクトリの中だけで行ってください。そこなら、もとからあるファイルを壊しません ::: ::: dialogue @lina: 戻したファイルは、本当に元と同じですか。 @linny: いい着眼点。`diff` や `sha256sum` で確認できるよ。一致すれば完全に復元できている証拠だ。 ::: ```bash # 元ファイルと復元ファイルが同一か確認 $ diff sample.txt restored.txt && echo "OK: 完全一致" ``` ```output OK: 完全一致 ``` ::: tip **サイズは約 1.33 倍に増えます** base64 は 3 バイトを 4 文字に変換します。そのためエンコード後は **元の約 4/3(約 33% 増)** のサイズになります。容量に余裕がない場所で大きいファイルを base64 化するときは注意してください。 ::: ## 5. 折り返し(改行)を制御する -w {#wrap} > **結論**: `base64` は既定で 76 文字ごとに改行を入れる。1 行にしたいなら `-w 0` を使う。 ::: dialogue @lina: 長いファイルをエンコードしたら、途中で何回も改行されていました。これは普通ですか。 @linny: 普通だよ。`base64` は **デフォルトで 76 文字ごとに改行(折り返し)** を入れる。メールの MIME 規格に合わせた仕様なんだ。 @lina: でも 1 行で欲しいときもあります。トークンを 1 行で扱いたいときとか。 @linny: そのときは `-w 0`。`-w` は wrap(折り返し)で、`0` を指定すると **折り返しなし** になるよ。 ::: ```bash # 折り返しなし(1 行で出力) $ base64 -w 0 image.png > oneline.b64 # 40 文字ごとに折り返す $ echo -n "Hello World, this is a longer text" | base64 -w 40 ``` ::: warning **macOS の base64 には `-w` がありません** `-w` は GNU coreutils(Linux 標準)のオプションです。macOS(BSD 版)の `base64` には `-w` がなく、折り返しの指定方法が異なります。この記事は **Linux(GNU coreutils)** を前提にしています。 ::: ## 6. よくある落とし穴 {#pitfalls} > **結論**: 「暗号化と勘違い」「echo の改行混入」「デコード時の不正文字」が三大つまずきポイント。 ::: dialogue @linny: 最後に、初心者がハマりやすいポイントを 3 つまとめておくね。 ::: ::: danger **落とし穴 1:base64 を暗号化だと思い込む** これが一番危険です。`base64` は **誰でもデコードできます**。秘密情報を base64 にしただけで GitHub に上げる、ログに出す、といった事故が後を絶ちません。隠したいデータは必ず暗号化してください。 ::: ::: warning **落とし穴 2:echo の改行混入** [2 章](#encode) で見た通り、`echo -n` を忘れると改行までエンコードされます。トークンを base64 化したのに認証が通らないときは、まずこれを疑ってください。 ::: ::: warning **落とし穴 3:デコード時の `invalid input`** コピペでスペースや余計な文字が混ざると `base64: invalid input` が出ます。`base64` が使う文字は `A-Z` `a-z` `0-9` `+` `/` の 64 種類だけと決まっており、それ以外が入るとエラーになるからです。 `-i`(`--ignore-garbage`)を付けると、**この 64 種類以外の文字を読み飛ばして** デコードを続けます。 ::: ```bash # 改行やスペースが混ざっていても無視してデコード $ base64 -d -i messy.b64 ``` ::: tip **安全テンプレ(コピペ用)** ```bash # 文字列をエンコード(改行を入れない) printf '%s' "テキスト" | base64 # 1 行でエンコード(折り返しなし) base64 -w 0 file.bin # デコード echo "SGVsbG8=" | base64 -d ``` ::: ## 7. ミニ課題:自分の環境で試そう {#exercise} > **結論**: エンコード・往復確認・改行の違いの 3 問で base64 の挙動を体に覚えさせる。 ::: dialogue @linny: 知識を定着させるために、自分の環境で次の課題をやってみよう。 ::: 練習用のからっぽのディレクトリを作り、その中で試します。こうすれば、もとからあるファイルを上書きしません。 ```bash # 練習用ディレクトリを準備して移動する $ mkdir -p ~/base64-practice && cd ~/base64-practice ``` **課題 1**:自分の名前を `base64` でエンコードしよう。末尾に改行を入れないこと。 :::details ヒント 1(方向づけ)を見る 文字を画面に出すコマンドの出力を、パイプで渡します。改行を付けない書き方を選んでください。 ::: :::details ヒント 2(コマンド名)を見る `printf '%s'` または `echo -n` を使い、`base64` にパイプします。 ::: :::details 答えを見る ```bash $ printf '%s' "Lina" | base64 ``` ```output TGluYQ== ``` `printf '%s'` か `echo -n` を使えば、末尾の改行が混ざりません。 ::: **課題 2**:課題 1 の結果をデコードし、元に戻ることを確認しよう。 :::details ヒント 1(方向づけ)を見る エンコードと同じコマンドに、逆向きを指示するオプションを 1 つ足します。 ::: :::details ヒント 2(コマンド名)を見る `base64 -d` を使います。 ::: :::details 答えを見る ```bash $ echo "TGluYQ==" | base64 -d ``` ```output Lina ``` 元の名前がそのまま表示されれば往復成功です。 ::: **課題 3**:`echo -n "test"` と `echo "test"` をそれぞれエンコードし、結果が違う理由を 1 行で説明しよう。 :::details ヒント 1(方向づけ)を見る 見た目には出ませんが、片方だけ最後に 1 文字ぶん多く送られています。 ::: :::details ヒント 2(コマンド名)を見る `echo -n "test" | base64` と `echo "test" | base64` を実行し、出力を見比べます。 ::: :::details 答えを見る ```bash $ echo -n "test" | base64 $ echo "test" | base64 ``` ```output dGVzdA== dGVzdAo= ``` `echo` はデフォルトで末尾に改行(`\n`)を付けます。`echo "test"` は `test\n`(5 バイト)、`echo -n "test"` は `test`(4 バイト)をエンコードします。入力が違うので、結果の base64 文字列も違います。 ::: ## 振り返り {#review} > **結論**: base64 は隠す道具ではなく、安全に運ぶための包み方である。鍵の有無と戻せるかで暗号化・ハッシュと分ける。 ::: dialogue @lina: やっと整理できました。`base64` は隠す道具ではなくて、運ぶための包み方なんですね。 @linny: その通り。鍵がいるのが暗号化、戻せないのがハッシュ、誰でも戻せるのがエンコード。この 3 つを言い分けられれば十分だよ。 @lina: 設定ファイルの謎の文字列も、もう怖くありません。あれをパスワード置き場にしてはいけない理由もわかりました。 ::: ## 今日の 3 行まとめ {#summary} > **結論**: base64 は鍵のいらない変換であり、暗号化でもハッシュでもない。誰でも元に戻せる。 1. `base64` はエンコード(書き方の変換)です。`base64 -d` で誰でも元に戻せます 2. 暗号化は鍵がいります。ハッシュは元に戻せません。base64 はそのどちらでもありません 3. 文字列を渡すときは `echo -n` か `printf`、ファイルに書くときは `>` の上書きに注意します ## 次に読む {#next} - [パイプとリダイレクト入門 - データの流れを理解する](/articles/tutorials/pipe-redirect-basics) - [md5sum・sha256sum でファイルの同一性を確認する](/articles/tutorials/checksum-md5-sha256) - [openssl コマンド入門 - 本当に暗号化したいとき](/articles/tutorials/openssl-basics) # bash ヒストリ活用術 - 過去のコマンドを使い倒す Source: https://penguin-gym-linux.com/articles/tutorials/bash-history-tips ## この記事でできるようになること {#intro} - `history` コマンドで過去のコマンドを一覧・再実行できる - `Ctrl+R` でコマンド履歴をさかのぼり検索できる - `!!` / `!$` のショートカットで素早く作業できる - `HISTSIZE` / `HISTCONTROL` 設定でヒストリを快適にカスタマイズできる ::: dialogue @lina: ライニー先輩!さっき長いコマンドを打ったのに、また最初から打ち直さないといけないんですか? @linny: それはつらいね。実は bash には「コマンド履歴(ヒストリ)」機能があって、過去に打ったコマンドをサクサク呼び出せるんだよ。 ::: ## 1. history コマンドで履歴を確認する {#history-basics} > **結論**: `history` コマンドで過去のコマンドを履歴番号付きで一覧表示でき、`grep` や件数指定で絞り込める。 ::: dialogue @lina: コマンド履歴って、どうやって見るんですか? @linny: `history` コマンドを打つと、過去に実行したコマンドが番号付きで一覧表示されるよ。 ::: ```bash $ history 498 ls -la /var/log 499 sudo apt update 500 grep -r "error" /var/log/syslog 501 cd /home/ubuntu 502 history ``` 左の番号は「履歴番号」といって、後で特定のコマンドを再実行するときに使えます。 ### 特定のコマンドだけ絞り込む ```bash $ history | grep apt 499 sudo apt update 485 sudo apt install vim 470 apt search nginx ``` `history` の出力を `grep` でフィルタリングすると、目的のコマンドをすぐに見つけられます。 ### 直近 N 件だけ表示する ```bash $ history 10 ``` 引数に数字を指定すると、最新 N 件だけ表示されます。 ## 2. 矢印キーでコマンドをたどる {#arrow-keys} > **結論**: 上下矢印キーで直前のコマンドを順にたどれ、さかのぼりすぎても下矢印や Ctrl+C で戻せる。 ::: dialogue @lina: 一番簡単な呼び出し方って何ですか? @linny: キーボードの上矢印キー(↑)を押すだけ。一個前のコマンドが出てくるよ。 ::: | キー操作 | 動作 | | -------- | ---------------------- | | `↑` | 一つ前のコマンドに移動 | | `↓` | 一つ後のコマンドに移動 | | `Enter` | 現在のコマンドを実行 | | `Ctrl+A` | 行頭に移動 | | `Ctrl+E` | 行末に移動 | ::: tip 矢印キーでさかのぼりすぎたら `↓` で戻れます。`Ctrl+C` で入力をキャンセルして元の状態に戻せます。 ::: ## 3. Ctrl+R でインクリメンタルサーチ {#ctrl-r} > **結論**: `Ctrl+R` はキーワードを打つだけで過去のコマンドを逆方向にリアルタイム検索できる最強の呼び出し手段。 ::: dialogue @lina: でも 100 個以上前のコマンドを矢印キーで探すのは大変ですよね...。 @linny: そのときは `Ctrl+R` が最強だよ。検索キーワードを入力するだけで、過去のコマンドをリアルタイムで探してくれるんだ。 ::: `Ctrl+R` を押すと、プロンプトが次のように変わります: ```output (reverse-i-search)`': ``` ここでキーワードを入力すると、一致するコマンドがリアルタイムで表示されます: ```output (reverse-i-search)`apt': sudo apt update ``` ### Ctrl+R の操作まとめ | キー操作 | 動作 | | --------- | -------------------------------------------------- | | `Ctrl+R` | 逆方向検索を開始(押すたびにさらに古い一致へ) | | `Ctrl+S` | 順方向検索(端末設定によっては使えないことがある) | | `Enter` | 表示されているコマンドを実行 | | `←` / `→` | 表示されているコマンドを編集モードに入る | | `Ctrl+G` | 検索をキャンセルして元のプロンプトに戻る | ::: warning `Ctrl+S` はターミナルの「フロー制御(XON/XOFF)」と衝突することがあります。効かない場合は `.bashrc` に `stty -ixon` を追加してください。 ::: ## 4. ヒストリ展開:!! と !$ を使いこなす {#history-expansion} > **結論**: `!!` で直前コマンド全体、`!$` で最後の引数、`!^` で最初の引数、`!n` で履歴番号のコマンドを再実行できる。 ::: dialogue @lina: `!!` って何ですか?たまに見かけるんですが。 @linny: `!!` は「直前に実行したコマンド全体」を表す特別な記法だよ。例えば `sudo` を付け忘れたときにとても便利! ::: ### !! — 直前のコマンドを再実行 ```bash $ apt update E: Could not open lock file /var/lib/dpkg/lock-frontend... $ sudo !! sudo apt update ``` `sudo !!` と打つと、自動的に `sudo apt update` として再実行されます。 ::: tip `!!` を実行すると、展開後のコマンドが一度表示されてから実行されます。何が実行されるか確認できるので安心です。 ::: ### !$ — 直前コマンドの最後の引数 ```bash $ mkdir -p /var/log/myapp $ cd !$ cd /var/log/myapp ``` `!$` は直前のコマンドの「最後の引数」を表します。長いパスを再度タイプしなくて済みます。 ### !^ — 直前コマンドの最初の引数 ```bash $ diff file1.txt file2.txt $ vim !^ vim file1.txt ``` `!^` は直前のコマンドの「最初の引数」を表します。 ### !n — 履歴番号で実行 ```bash $ history | grep grep 500 grep -r "error" /var/log/syslog $ !500 grep -r "error" /var/log/syslog ``` `!` のあとに履歴番号を入力すると、そのコマンドを再実行できます。 ::: warning ヒストリ展開は確認なしに即実行されます。`rm -rf` 系のコマンドを `!!` で再実行するときは特に慎重に。 ::: ## 5. HISTSIZE と HISTFILESIZE でヒストリを増やす {#histsize} > **結論**: `~/.bashrc` に `HISTSIZE` と `HISTFILESIZE` を設定すれば、保持・保存する履歴件数を増やせる。 ::: dialogue @lina: デフォルトだと履歴はどのくらい保存されるんですか? @linny: 多くの環境でデフォルトは 1000 件程度だけど、設定で増やせるよ。長く使うマシンでは増やしておくと便利。 ::: `~/.bashrc` に以下を追記します: ```bash HISTSIZE=10000 # メモリ上に保持する件数(現在のセッション中) HISTFILESIZE=20000 # ~/.bash_history ファイルに保存する件数 ``` 設定後は `source ~/.bashrc` で反映します。 ```bash $ source ~/.bashrc ``` ### ~/.bash_history ファイルとは bash を終了すると、セッション中のコマンドが `~/.bash_history` に書き込まれます。`HISTFILESIZE` がこのファイルの最大行数を制御します。 ```bash $ cat ~/.bash_history | wc -l 1247 ``` ## 6. HISTCONTROL で履歴をクリーンに保つ {#histcontrol} > **結論**: `HISTCONTROL=ignoreboth` で重複やスペース始まりのコマンドを除外し、履歴をクリーンに保てる。 ::: dialogue @lina: 同じコマンドを何度も打つと履歴が重複してしまうのが嫌なんですが...。 @linny: `HISTCONTROL` の設定で解決できるよ! ::: ```bash # ~/.bashrc に追記 HISTCONTROL=ignoreboth ``` | 値 | 動作 | | ------------- | ------------------------------------------ | | `ignorespace` | スペースで始まるコマンドを履歴に記録しない | | `ignoredups` | 直前と同じコマンドの連続重複を記録しない | | `ignoreboth` | `ignorespace` と `ignoredups` の両方 | | `erasedups` | 履歴全体から重複をすべて削除 | ### スペースで始めると履歴に残らない ```bash $ secret-command --password=mypassword ``` 先頭にスペースを付けると(`HISTCONTROL=ignorespace` または `ignoreboth` の設定時)、履歴に記録されません。コマンドラインでパスワードを入力せざるを得ないときのテクニックです。 ## 7. HISTTIMEFORMAT でタイムスタンプを記録する {#histtimeformat} > **結論**: `HISTTIMEFORMAT` を設定すると履歴に実行日時が付き、障害調査や作業記録の証跡になる。 ::: dialogue @lina: あのコマンド、いつ打ったっけって調べたいときはどうすればいいですか? @linny: `HISTTIMEFORMAT` を設定すると、履歴にタイムスタンプが付くよ。障害対応の証跡にもなって便利。 ::: ```bash # ~/.bashrc に追記 HISTTIMEFORMAT="%Y-%m-%d %T " ``` ```bash $ history 5 998 2026-06-01 10:23:45 ls -la 999 2026-06-01 10:24:12 cd /var/log 1000 2026-06-01 10:25:30 sudo apt update 1001 2026-06-01 10:26:01 tail -f /var/log/syslog 1002 2026-06-01 10:28:44 history 5 ``` ::: tip タイムスタンプがあると「あの操作はいつやったか?」という障害調査や作業記録に役立ちます。 ::: ## 次に読む {#next} - [.bashrc と .profile の読み込み順](/articles/tutorials/bashrc-profile-order) - [シェルスクリプトの書き方入門](/articles/tutorials/shell-scripting-basics) - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) # bash strict mode 入門 - set -euo pipefail で安全なスクリプトを書く Source: https://penguin-gym-linux.com/articles/tutorials/bash-strict-mode ## bash strict mode とは? {#intro} > **結論**: スクリプト先頭に `set -euo pipefail` を置き、エラー・未定義変数・パイプ失敗を即座に検出する書き方。隠れたバグの早期発見が目的。 bash の既定動作は寛容すぎる。コマンドが失敗しても、変数が未定義でも、パイプの途中でエラーが出ても、スクリプトはそのまま走り続ける。結果として「途中で失敗したのに最後まで実行され、壊れたデータを残す」事故が起きる。 **bash strict mode** は、スクリプト冒頭で 3 つのオプションを有効化し、bash を「失敗したら止まる」挙動に切り替える定番イディオムである。 ```bash #!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' ``` ::: tip **この記事で扱う 4 要素** - `set -e`(errexit): コマンド失敗で即終了 - `set -u`(nounset): 未定義変数の参照をエラーに - `set -o pipefail`: パイプライン途中の失敗を検出 - `IFS=$'\n\t'`: 単語分割の事故を防ぐ(任意だが推奨) ::: ::: warning **前提(対象環境)** - シェル: bash(`#!/usr/bin/env bash`) - `pipefail` は bash 拡張。POSIX `sh`(dash 等)では動かない - 対話シェルではなく**スクリプト**での利用を想定 ::: ## なぜ既定の bash は危険なのか? {#why} > **結論**: 既定の bash はコマンドが失敗しても続行し、タイプミスした変数は空文字として扱う。失敗が無視されたまま処理が進む点が事故の温床。 次のスクリプトを見てほしい。バックアップ用ディレクトリへ移動してから古いファイルを消す、よくある処理である。 ```bash #!/usr/bin/env bash cd "$BACKUP_DIR" rm -rf ./* ``` `BACKUP_DIR` の設定を忘れると、既定の bash では `cd ""`(何もしない、カレントディレクトリのまま)となり、`rm -rf ./*` が**今いるディレクトリ**で実行される。`cd ""` はエラーにならず成功扱いのため、意図しない場所を消してしまう。 strict mode を有効にすると、この事故は 2 重に防がれる。 - `set -u` が未定義の `$BACKUP_DIR` 参照でスクリプトを止める - `$BACKUP_DIR` が存在しないパスを指していた場合は `set -e` が `cd` の失敗で即終了する(空文字の `cd ""` は成功扱いのため、空文字対策は `set -u` が担う) ::: danger 既定の bash では「失敗しても次の行へ進む」のが標準動作。strict mode はこの暗黙の続行を断ち切るための保険である。 ::: ## set -e(errexit)の効果と落とし穴 {#errexit} > **結論**: `set -e` はコマンドが非ゼロ終了したら即スクリプトを終了する。ただし `if` 条件・`&&`・関数内など効かない文脈が多く、過信は禁物。 `set -e` は、コマンドが 0 以外の終了ステータスを返した時点でスクリプトを終了させる。 ```bash set -e cp important.conf /etc/myapp/ # 失敗したらここで停止 systemctl restart myapp # 上が成功した時だけ実行される ``` ### errexit が効かない主なケース `set -e` には「効かない文脈」が仕様として存在する。これを知らないと「止まるはずが止まらない」事故になる。 - `if cmd; then ...` の**条件部**(失敗が判定に使われるため) - `cmd && ...` / `cmd || ...` の左辺(最後のコマンド以外) - `!` で否定したコマンド - パイプラインの**最後以外**のコマンド(`pipefail` で補う) ```bash set -e # NG: grep が失敗しても止まらない(条件部だから) if grep -q pattern file.txt; then echo "found" fi # 意図的に失敗を許容したい場合は || true を明示 risky_command || true ``` ::: warning `set -e` だけに頼らず、**重要な処理は終了ステータスを明示的にチェック**するのが堅実。`set -e` は「うっかり見落とした失敗」を拾う保険と捉える。 ::: ## set -u と pipefail の役割 {#nounset-pipefail} > **結論**: `set -u` は未定義変数の参照をエラーにしてタイプミスを検出。`set -o pipefail` はパイプ途中の失敗を拾い、`grep | sort` のような連結の成否を正しく判定する。 ### set -u(nounset) 未定義の変数を参照するとエラーになり、スクリプトが停止する。変数名のタイプミスを即座に発見できる。 ```bash set -u name="penguin" echo "$nmae" # タイポ → "unbound variable" でエラー終了 ``` 意図的に「未定義なら既定値」を使いたい場合は、parameter expansion で明示する。 ```bash # 未定義でもエラーにせず既定値を使う echo "${OPTIONAL_VAR:-default}" # 位置パラメータも同様($1 が無い場合の保険) target="${1:-/tmp}" ``` ### set -o pipefail 既定では、パイプラインの終了ステータスは**最後のコマンド**のものになる。途中のコマンドが失敗しても、最後が成功すればパイプ全体は成功扱いになってしまう。 ```bash # pipefail なし: curl が失敗しても grep が成功すれば $? は 0 curl -s https://example.com/data | grep "key" set -o pipefail # pipefail あり: curl の失敗が pipe 全体の失敗として伝わる curl -s https://example.com/data | grep "key" ``` ::: tip `pipefail` はパイプラインのうち**最も右側で失敗したコマンド**の終了ステータスを返す。すべて成功なら 0。データ取得とフィルタを連結する処理で特に効く。 ::: ## IFS の設定はなぜ推奨されるのか? {#ifs} > **結論**: `IFS=$'\n\t'` は単語分割の区切りを改行とタブだけに限定する設定。ファイル名やパスに含まれる**空白**での意図しない分割事故を防ぐ。 `IFS`(Internal Field Separator)は、bash が文字列を単語に分割する際の区切り文字。既定値は**スペース・タブ・改行**の 3 つ。このうちスペースが、空白入りファイル名で事故を起こす。 ```bash # 既定 IFS: "my file.txt" がスペースで 2 語に割れる for f in $(ls); do echo "$f" done # IFS=$'\n\t': 改行・タブのみを区切りにする IFS=$'\n\t' ``` Aaron Maxwell が提唱した「unofficial bash strict mode」では、`set -euo pipefail` に加えてこの `IFS` 設定をセットで推奨している。スペースを区切りから外すことで、forループや変数展開での分割が直感的になる。 ::: warning `IFS` 変更は副作用がある。スペース区切りの単語分割に依存する処理(`read -a` での配列読み込み等)がある場合は、その箇所だけ局所的に `IFS` を戻すか、設定を見送る。 ::: ## strict mode の実践テンプレート {#template} > **結論**: shebang・strict mode・エラートラップをまとめた定型を雛形として持っておくと、毎回安全なスクリプトを最短で書き始められる。 実務でそのまま使えるテンプレート。`trap` でエラー発生行を表示すると、デバッグが一気に楽になる。 ```bash #!/usr/bin/env bash # # 安全なスクリプトの雛形 # set -euo pipefail IFS=$'\n\t' # エラー発生時に行番号を表示 trap 'echo "Error on line $LINENO" >&2' ERR main() { local target="${1:-/tmp}" echo "処理対象: $target" # ここに本処理を書く } main "$@" ``` ::: tip **コピペ用: 最小構成** ```bash #!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' ``` ::: ::: details なぜ `#!/usr/bin/env bash` を使うのか `#!/bin/bash` と直接書くと、bash が `/bin` 以外(例: `/usr/local/bin`)にインストールされた環境で動かない。`env` を経由すると `PATH` 上の bash を探すため、可搬性が高まる。`pipefail` は bash 固有のため、`#!/bin/sh` ではなく bash を明示することも重要。 ::: ## strict mode の注意点まとめ {#caveats} > **結論**: strict mode は万能ではない。`set -e` の効かない文脈・`IFS` の副作用・既存スクリプトへの後付けリスクを理解した上で使う。 | 項目 | 注意点 | | ------------- | -------------------------------------------------------- | | `set -e` | `if` 条件・`&&` 左辺・関数内など効かない文脈がある | | `set -u` | 既定値が要る変数は `${VAR:-default}` で明示する | | `pipefail` | bash 専用。POSIX `sh` では使えない | | `IFS=$'\n\t'` | スペース区切り依存の処理がある場合は副作用に注意 | | 後付け | 既存スクリプトに足すと、隠れていた失敗が一斉に顕在化する | ::: warning **やってはいけないこと** - `pipefail` を `#!/bin/sh` スクリプトで使う - `set -u` 環境で `$1` を未チェックのまま参照する - 巨大な既存スクリプトへ無検証で strict mode を後付けする ::: ## 次に読む {#next} - [シェルスクリプトの書き方入門](/articles/tutorials/shell-scripting-basics) - [bash parameter expansion チートシート](/articles/tutorials/parameter-expansion) - [heredoc(ヒアドキュメント)入門](/articles/tutorials/heredoc-basics) # .bashrc が反映されない - 再読み込み(source)と .profile の読み込み順 Source: https://penguin-gym-linux.com/articles/tutorials/bashrc-profile-order ## この記事でわかること {#what-you-will-learn} - `.bashrc` と `.profile` の違いがわかります。どちらがいつ読まれるかもわかります - ログインシェルとインタラクティブシェルの違いがわかります - SSH 接続でエイリアスが使えない原因と、その直し方がわかります - `source` コマンドで、設定を今すぐ反映できるようになります - 環境変数・エイリアス・関数を、どのファイルに書けばよいか判断できます ## ライニー先輩、設定が反映されません {#intro} > **結論**: .bashrc と .profile は読み込まれるタイミングが違う。SSH でエイリアスが使えない原因はここにある。 ::: dialogue @lina: ライニー先輩、`.bashrc` にエイリアスを追加しました。でもターミナルを開き直しても使えません。 @linny: それはよくある悩みだね。`.bashrc` と `.profile` の 2 つが、どちらがいつ読み込まれるか。それを理解すれば解決するよ。 ::: この記事では次の疑問に答えます。 - `.bashrc` と `.profile` は何が違うのでしょうか - 設定を書いたのに反映されないのはなぜでしょうか - SSH 接続でエイリアスが使えないのはなぜでしょうか - 再起動せずに設定を反映する方法はあるのでしょうか ::: tip **言葉の整理** - **シェル**:あなたが打ったコマンドを読み取って実行するプログラムです。この記事では `bash` を前提にします。 - **設定ファイル**:シェルが起動時に自動で読む、命令を並べたファイルです。`.bashrc` や `.profile` がこれにあたります。 - **エイリアス**:長いコマンドに付けた短い別名です。「別名」「ショートカット」と呼ばれることもあります。 なお、名前が `.` で始まるファイルは「隠しファイル」と呼ばれます。`ls` では表示されません。`ls -a` を付けたときだけ表示されます。 ::: ::: warning **先に、壊れたときの戻し方** この記事では設定ファイルを編集します。書き方を間違えると、ターミナルを開くたびにエラーが出ることがあります。 そこで、編集の前にコピーを取ってください。 ```bash $ cp ~/.bashrc ~/.bashrc.bak $ cp ~/.profile ~/.profile.bak ``` `~/.bash_profile` が存在する環境では、そのファイルのコピーも取ってください。 おかしくなったら、コピーから戻せます。 ```bash $ cp ~/.bashrc.bak ~/.bashrc $ source ~/.bashrc ``` エラーが出るだけでターミナルが使える場合は、上の 2 行で元に戻ります。 ターミナルが開いてすぐ閉じてしまう場合は、設定ファイルを読まずに起動できます。別のターミナルから次のように打ってください。 ```bash $ bash --norc --noprofile ``` この状態なら、上の `cp` で元に戻せます。 ::: ## `.bashrc` と `.profile` の役割の違い {#whatis} > **結論**: `~/.bashrc` はターミナルを開いたときに読まれる。`~/.profile` は SSH 接続などのログイン時に読まれる。 ::: dialogue @lina: `.bashrc` と `.profile` は名前が似ています。どちらに書けばよいのかわかりません。 @linny: まずは「それぞれいつ読み込まれるか」を知るのが先だよ。 ::: | ファイル | 読み込まれるタイミング | | ----------------- | ---------------------------------------------------------- | | `~/.bashrc` | **インタラクティブシェル**(通常のターミナルを開いたとき) | | `~/.profile` | **ログインシェル**(SSH 接続時・ログイン時) | | `~/.bash_profile` | **ログインシェル**(`.profile` より優先される) | `~` は自分のホームディレクトリを表す記号です。`~/.bashrc` は「ホームディレクトリの中の .bashrc」という意味になります。 ::: tip `~/.bash_profile` が存在する場合、`~/.profile` は読み込まれません。どちらか一方だけが使われます。 ::: ## シェルの2種類を理解しよう {#shell-types} > **結論**: ログインシェルは SSH 接続時に起動する。そして `/etc/profile` や `~/.bash_profile` を読む。 ::: dialogue @lina: 「ログインシェル」と「インタラクティブシェル」は何が違うのでしょうか。 @linny: 「どうやって bash が起動したか」で種類が変わるんだ。デスクトップのターミナルと SSH 接続では、起動するシェルの種類が違うんだよ。 ::: ::: tip **2 種類の呼び名を先に整理します** - **ログインシェル**:ユーザー名とパスワードで入ったときに始まるシェルです。SSH 接続がその代表です。 - **インタラクティブシェル**:あなたがコマンドを打ち込むためのシェルです。「対話シェル」とも呼ばれます。 - ふだんデスクトップで開くターミナルは、「ログインではないインタラクティブシェル」にあたります。 ::: ### ログインシェル 次の状況で起動します。 - SSH でサーバに接続したとき(`ssh user@host`) - `sudo -i` でルートシェルに切り替えたとき - `su -` でユーザーを切り替えたとき 読み込まれるファイルは次の順番です。 1. `/etc/profile` 2. `~/.bash_profile`(存在する場合)または `~/.profile` ### インタラクティブ非ログインシェル 次の状況で起動します。 - GNOME Terminal などのターミナルアプリを開いたとき - `bash` コマンドで新しいシェルを起動したとき 読み込まれるファイルは次の順番です。 1. `/etc/bash.bashrc` 2. `~/.bashrc` ::: highlight **覚え方**: デスクトップのターミナル → `~/.bashrc`、SSH 接続 → `~/.profile`(または `~/.bash_profile`) ::: ## 読み込み順の全体図 {#load-order} > **結論**: SSH ログイン時は `~/.bashrc` が自動で読まれない。そのため `~/.bash_profile` から自分で呼び出す必要がある。 ::: dialogue @lina: SSH で接続すると `.bashrc` のエイリアスが使えません。ログインシェルが `.bashrc` を読まないからですか。 @linny: そのとおり。SSH はログインシェルを使う。だから `~/.bashrc` は自動では読み込まれないんだ。 ::: ```output ログインシェル(SSH 接続時): /etc/profile └─ ~/.bash_profile(存在する場合) └─ ~/.bashrc(bash_profile の中から呼び出す記述がある場合のみ) または └─ ~/.profile(bash_profile がない場合) └─ ~/.bashrc(profile の中から呼び出す記述がある場合のみ) インタラクティブシェル(ターミナル起動時): /etc/bash.bashrc └─ ~/.bashrc ``` ::: warning ログインシェルは `~/.bashrc` を**自動では読み込みません**。 SSH でもエイリアスや関数を使いたい場合は、ひと手間が必要です。`~/.bash_profile` または `~/.profile` の中から、`~/.bashrc` を自分で呼び出してください。 ::: ## よくある失敗パターンと解決策 {#common-problems} > **結論**: SSH でエイリアスが使えないときは `~/.bash_profile` から `~/.bashrc` を呼び出す。編集したら `source ~/.bashrc` で今すぐ反映する。 ### パターン1:SSH 接続後にエイリアスが使えない ::: dialogue @lina: エイリアスを `.bashrc` に書きました。それなのに SSH で接続すると「command not found」になります。 @lina: 書き方を間違えたのでしょうか。ファイルは何度も見直しました。 @linny: 書き方は合っているよ。原因はログインシェルが `.bashrc` を読まないことなんだ。 @lina: えっ、ファイル自体が読まれていなかったんですか。それは思いつきませんでした。 @linny: そう。だから `.bash_profile` に「`.bashrc` を読み込む」処理を足そう。1 回書けば、次からは自動で読まれるよ。 @lina: 場所の問題だったんですね。納得しました。 ::: **解決策**:まず `~/.bash_profile` があるかを確認します。 ```bash $ ls -a ~ | grep bash_profile ``` - **何も出ない場合**(Ubuntu の初期状態):`~/.profile` に書き足してください。**`~/.bash_profile` を新しく作らないでください。** 作ると `~/.profile` が読まれなくなります。その結果、Ubuntu が既定で設定している `PATH` が失われます。 - **`~/.bash_profile` が出た場合**:そちらに書き足してください。 書き足す内容は次の 3 行です。 ```bash if [ -f ~/.bashrc ]; then . ~/.bashrc fi ``` この 3 行は「`~/.bashrc` というファイルがあれば、それを読み込む」という意味です。`if` は条件つきで実行するための書き方です。`-f` は「ファイルが存在するか」を確かめます。 ::: tip Ubuntu では、`~/.profile` にこの記述が最初から入っていることが多いです。まず確認しましょう。 ::: ```bash cat ~/.profile ``` ```output # if running bash if [ -n "$BASH_VERSION" ]; then # include .bashrc if it exists if [ -f "$HOME/.bashrc" ]; then . "$HOME/.bashrc" fi fi ``` この記述があれば、SSH ログイン時にも `~/.bashrc` が自動で読み込まれます。 ### パターン2:`.bashrc` を編集したのにターミナルに反映されない ::: dialogue @lina: `.bashrc` を編集しました。でも、開いたままのターミナルでは新しいエイリアスが使えません。 @linny: ターミナルを新しく開けば反映されるよ。ただし、すでに開いているターミナルには届かないんだ。 @linny: そこで使うのが `source` コマンドだよ。 ::: `source` コマンドを使うと、今すぐ反映できます。 ```bash source ~/.bashrc ``` 短縮形でも同じ意味です。 ```bash . ~/.bashrc ``` ::: highlight `source`(またはドット `.`)は、指定したファイルを今のシェルの中で実行します。 ターミナルを開き直さなくても、設定の変更がすぐに反映されます。 ::: ## 今のシェルの種類を確認する {#check} > **結論**: `echo $0` でシェルの種類がわかる。先頭にハイフンが付いていれば、ログインシェルで起動している。 ::: dialogue @lina: 自分が今ログインシェルで動いているかどうか、どうすれば確認できますか。 @linny: `$0` という変数を見ればわかるよ。`$0` には、今動いているシェルの名前が入っているんだ。 ::: ```bash echo $0 ``` ```output # ログインシェルなら先頭に - (ハイフン) がつく -bash # 非ログインシェルなら - がつかない bash ``` どのファイルが読み込まれているかを調べたいときは、次のコマンドを使います。 ```bash $ bash --login -x -c 'exit' 2>&1 | head -30 ``` `-c 'exit'` は「起動したらすぐ終わる」という指示です。これを付けないと、入力待ちのまま止まって見えます。 実行された処理が `+` 付きで表示されます。そのうち `. /etc/profile` や `. /home/user/.bashrc` のように `.`(ドット)で始まる行が、読み込まれた設定ファイルです。 ## 何をどこに書くか:判断の基準 {#where-to-write} > **結論**: エイリアスや関数は `~/.bashrc` に書く。`PATH` などの環境変数は `~/.profile` に書く。これが基本ルール。 ::: dialogue @lina: 結局のところ、何をどこに書けばよいのでしょうか。 @linny: 用途で分けると整理しやすいよ。 ::: | 設定の種類 | 書く場所 | 理由 | | -------------------------------------- | ------------ | -------------------------------- | | エイリアス(`alias ll='ls -la'` など) | `~/.bashrc` | インタラクティブシェルでのみ使う | | シェル関数 | `~/.bashrc` | 同上 | | 環境変数(`PATH` など) | `~/.profile` | ログインシェルでも必要 | | プロンプト設定(`PS1`) | `~/.bashrc` | 表示設定はターミナル向け | ::: tip `PATH` などの環境変数を `~/.bashrc` に書いても、日常の作業では動きます。 ただし cron のように、ログインシェルを経由せずに実行される場面があります。そこでは反映されないことがあります。大事な環境変数は `~/.profile` に書くほうが安全です。 ::: ## ミニ課題:実際に手を動かして確認しよう {#practice} > **結論**: シェルの種類・読み込みの有無・即時反映の 3 問で、この記事の内容を手で確かめる。 ::: dialogue @lina: 理屈はわかってきました。実際に試してみたいです。 @linny: いいね、3 問用意したよ。ターミナルで試してみて。 ::: **課題 1**: 今使っているシェルが、ログインシェルかどうかを確かめよう。 :::details ヒント 1(方向づけ)を見る 今動いているシェルの名前が入っている変数があります。その中身を画面に出します。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `echo` です。変数は `$0` です。 ::: :::details 答えを見る ```bash $ echo $0 ``` ```output bash ``` 先頭に `-` が付いていなければ、ログインシェルではありません。デスクトップのターミナルでは、こちらが普通です。SSH で接続すると `-bash` と表示されます。 ::: **課題 2**: `~/.profile` の中に `.bashrc` を読み込む記述があるか確かめよう。 :::details ヒント 1(方向づけ)を見る ファイルの中から、特定の文字を含む行だけを探し出すコマンドがあります。行番号も一緒に出しましょう。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `grep` です。行番号を出すオプションは `-n` です。探す文字は `bashrc` です。 ::: :::details 答えを見る ```bash $ grep -n "bashrc" ~/.profile ``` ```output 6: if [ -f "$HOME/.bashrc" ]; then 7: . "$HOME/.bashrc" 8: fi ``` このような行が出れば、SSH でログインしたときも `.bashrc` が読み込まれます。何も出なければ、[パターン1](#common-problems) の 3 行を書き足してください。 ::: **課題 3**: テスト用のエイリアスを追加し、ターミナルを開き直さずに使えるようにしよう。 :::details ヒント 1(方向づけ)を見る 設定ファイルの末尾に 1 行足します。そのあと、そのファイルを今のシェルで読み直します。 ::: :::details ヒント 2(コマンド名)を見る 書き足すのは `echo` と `>>` です。読み直すのは `source` です。ファイルは `~/.bashrc` です。 ::: :::details 答えを見る ```bash $ echo "alias hello='echo Hello Linux World!'" >> ~/.bashrc $ source ~/.bashrc $ hello ``` ```output Hello Linux World! ``` `>>` はファイルの末尾に 1 行を書き足します。`>` と書くと中身を全部消してしまうので、ここでは使いません。 試したエイリアスを消したいときは、`~/.bashrc` の最終行を削除してから `source ~/.bashrc` を実行します。 ::: :::details 反映されない場合のヒント ファイルの末尾を確認して、エイリアスが正しく追記されているか見てみましょう。 ```bash tail -5 ~/.bashrc ``` 追記されていても動かない場合があります。そのときは `.bashrc` の中でエラーが起きている可能性があります。 ```bash bash -n ~/.bashrc ``` このコマンドは、実行せずに書き方だけを調べます。何も出なければ書き方に問題はありません。 ::: ## 振り返り {#review} ::: dialogue @lina: つまり、設定が効かないときは「書き方」より先に「読まれているか」を疑うんですね。 @linny: そのとおり。`.bashrc` はターミナルを開いたとき、`.profile` はログインしたとき。この 2 つを分けて考えるといい。 @lina: 開いたままのターミナルに反映したいときは `source` ですね。覚えました。 @linny: 完璧だね。迷ったら `echo $0` で、今どちらのシェルにいるかを確かめよう。 ::: ## まとめ {#summary} ::: highlight **覚えておくべきポイント** - `~/.bashrc` は、ターミナルを開いたとき(インタラクティブシェル)に読み込まれます - `~/.profile` は、SSH 接続やログインのとき(ログインシェル)に読み込まれます - `~/.bash_profile` が存在すると、`~/.profile` より優先されます - ログインシェルでも `.bashrc` を使いたいときは、`~/.profile` から呼び出します - 設定を今すぐ反映するには `source ~/.bashrc` を実行します ::: ## 今日の 3 行まとめ {#three-lines} 1. `~/.bashrc` はターミナルを開いたとき、`~/.profile` はログインしたときに読まれる 2. SSH でエイリアスが使えないときは、`~/.profile` から `~/.bashrc` を呼び出す 3. 開いたままのターミナルに反映したいときは `source ~/.bashrc` を実行する ## 次に読む {#next} - [環境変数の使い方](/articles/tutorials/environment-variables) - [シェルスクリプトの書き方入門](/articles/tutorials/shell-scripting-basics) - [command not found の対処法](/articles/tutorials/command-not-found) # pwd・cd・lsの使い方 - Linux基本コマンド入門 Source: https://penguin-gym-linux.com/articles/tutorials/basic-commands ## この記事でできるようになること {#intro-list} > **結論**: pwd・ls・cdの3コマンドでターミナルの現在地・中身・移動の基本操作を習得できる。 - `pwd` で「今どこにいるか」を確認できる - `ls` で「何があるか」を確認できる - `cd` で好きな場所に移動できる - 「No such file or directory」エラーを自力で解決できる **対象読者**:Linuxコマンドを初めて触る方、ターミナルに不安がある方 **この記事で使う3つのコマンドは、どれも表示と移動だけを行います。ファイルを消したり書きかえたりはしません。何度打ち間違えても、あなたのパソコンは壊れません。** ## 導入:リナの最初のつまずき {#intro} > **結論**: ターミナルで最初につまずく場面からpwd・ls・cdの3コマンドの必要性を理解する。 ::: dialogue @lina: ライニー先輩、Linuxのコマンドって何から始めればいいんですか?いきなりターミナル開いても、何したらいいか全然わからなくて... @linny: わかるわかる。最初はみんなそうだよ。まずは「今どこにいるか」「何があるか」「どう移動するか」の3つだけ覚えればOK。 @lina: 3つだけですか? @linny: そう。`pwd`(現在地)、`ls`(一覧)、`cd`(移動)の3つ。これができれば、ターミナルで迷子にならなくなるよ。 ::: ::: tip **言葉の整理**:ターミナル・シェル・コンソールは、ほぼ同じものを指す別の呼び方です。この記事では「ターミナル」で統一します。ターミナルとは、コマンドを打ち込んで結果を文字で受け取る画面のことです。 ::: ## pwd - 今どこにいるか確認しよう {#pwd} > **結論**: pwdで今いるディレクトリを常に確認する癖をつけることが誤操作防止の最初の一歩だ。 ::: dialogue @linny: まず最初は `pwd` コマンド。これは「今どこにいるか」を教えてくれるよ。 @lina: なんで必要なんですか? @linny: ターミナルでは「自分がどのディレクトリにいるか」がすごく大事なんだ。ディレクトリというのは、Windows や Mac でいう「フォルダ」と同じ意味だよ。 @linny: 間違った場所でファイルを消したり作ったりすると大変だからね。`pwd` で現在地を確認する癖をつけると事故が減るよ。 ::: ### 実際に試してみよう {#try-it} ```bash $ pwd ``` ```output /home/user ``` ::: tip **ここがポイント**:`/home/user` という出力は「あなたが今、ホームディレクトリにいる」という意味です。`/` はLinuxのルート(一番上のディレクトリ)です。そこから `home` → `user` と進んだ場所にいることを示しています。 ::: ::: dialogue @lina: `/home/user` って出ました!これが私の現在地ってことですね? @linny: 正解。迷ったらいつでも `pwd` で確認すればOK。 ::: ## ls - 何があるか見てみよう {#ls} > **結論**: lsで中身を確認しls -laで詳細表示と隠しファイルを含む全情報を一覧で把握する。 ::: dialogue @linny: 次は `ls` コマンド。現在地に何があるか一覧で見られるよ。 @lina: 現在地の中身が見えるってことですか? @linny: その通り。まず基本の `ls` を試してみて。 ::: ### 基本の ls {#basic-ls} ```bash $ ls ``` ```output documents pictures downloads ``` ::: dialogue @lina: 3つのディレクトリが見えました!でも、これだけだと詳しい情報がわからないですね... @linny: いいところに気づいたね。慣れてくると `ls -la` を使うことが多くなるよ。 @linny: コマンドのうしろに付ける `-l` や `-a` は「オプション」といって、動きを変えるスイッチのことだよ。`-l` で詳細表示、`-a` で隠しファイルも表示してくれる。 ::: ### 詳細表示(ls -la) {#detailed-view} ```bash $ ls -la ``` ```output total 12 drwxr-xr-x 5 user user 4096 Feb 2 10:00 . drwxr-xr-x 3 user user 4096 Feb 2 10:00 .. drwxr-xr-x 2 user user 4096 Feb 2 10:01 documents drwxr-xr-x 2 user user 4096 Feb 2 10:01 pictures drwxr-xr-x 2 user user 4096 Feb 2 10:01 downloads ``` ::: tip **ここがポイント**:最初は全部理解しなくて大丈夫です。まずは2つだけ覚えましょう。 - **先頭の文字**:`d` ならディレクトリ、`-` ならファイルです。 - **最後の名前**:`documents` や `pictures` がディレクトリ名です。 - **`.` と `..` の行**:`.` は「今いるディレクトリ自身」、`..` は「1つ上のディレクトリ」を表す特別な名前です。あとで出てくる `cd ..` の `..` と同じものです。 **隠しファイル**とは、名前が `.` で始まるファイルのことです。`ls` では表示されず、`-a` を付けたときだけ表示されます。 1行目の `total 12` はブロック数の合計です。今は気にしなくて大丈夫です。 `rwxr-xr-x` は権限といって「誰が読み書きできるか」を表します。これは後で学べば大丈夫です。 ::: ::: dialogue @lina: なるほど!詳しい情報が見えるようになりましたね。 @linny: そう。困ったらまず `pwd` で現在地確認、次に `ls -la` で中身確認。これが基本の型だよ。 ::: ## cd - 移動してみよう {#cd} > **結論**: cdでディレクトリに入り..で1つ上~でホームへ-で直前の場所にそれぞれ移動できる。 ::: dialogue @linny: 最後は `cd` コマンド。ディレクトリを移動できるよ。 @lina: さっき見えた documents ディレクトリに移動してみたいです! @linny: OK、じゃあ `cd documents` と入力してみて。 ::: ### ディレクトリに移動 {#move-to-dir} ```bash $ cd documents $ pwd ``` ```output /home/user/documents ``` ::: dialogue @lina: 移動できました!`pwd` で確認したら `/home/user/documents` になってますね。 @linny: 完璧。移動したら必ず `pwd` で確認する癖をつけるといいよ。 ::: ### よく使うショートカット {#shortcuts} ```bash $ cd ~ # ホームディレクトリへ $ cd .. # 1つ上のディレクトリへ $ cd - # 直前にいた場所へ戻る ``` ::: dialogue @lina: あっ、`..` って便利ですね。1つ上に戻れるんですか? @linny: そう。`cd ..` で1つ上、`cd ~` で一気にホームへ戻れるよ。覚えておくと移動がすごく楽になる。 ::: ### つまずきポイント:No such file or directory {#pitfall-no-such-file} ::: dialogue @lina: あれ?`cd dowloads` って入力したら「No such file or directory」ってエラーが出ちゃいました... @linny: よくあるパターンだね。「dowloads」じゃなくて「downloads」だよ。原因のほとんどはスペルミスなんだ。 @linny: まず `ls -la` で正確なディレクトリ名を確認して、そこからコピペするといいよ。 @lina: なるほど!確認してからコピペすればミスらないですね。 ::: ::: warning **復旧の型**: 1. `pwd` で現在地を確認します。 2. `ls -la` で候補を確認します。 3. 正確なディレクトリ名をコピーして `cd` を実行します。 ::: ## ミニ課題で手を動かそう {#practice} > **結論**: pwd・ls-la・cdの3ステップを使ったミニ課題でターミナル操作の基本フローを体で覚える。 ::: dialogue @linny: じゃあ、今までの3つのコマンドを使って課題をやってみよう。答えを見る前に、まず自分で考えてみてね。 ::: ### 課題1:現在地を確認して、一覧を表示しよう {#challenge-1} **やること**:今いる場所を表示し、続けてその場所にある全ファイルを詳しく表示してください。 :::details ヒント1(方向づけ)を見る 「今どこにいるか」を出すコマンドと、「そこに何があるか」を出すコマンドを、この順に1つずつ使います。 ::: :::details ヒント2(コマンド名)を見る 使うのは `pwd` と `ls` です。隠しファイルも詳しく見るには `ls` にオプションを2つ付けます。 ::: :::details 答えを見る ```bash $ pwd ``` ```output /home/user ``` ```bash $ ls -la ``` ```output total 12 drwxr-xr-x 5 user user 4096 Feb 2 10:00 . drwxr-xr-x 3 user user 4096 Feb 2 10:00 .. drwxr-xr-x 2 user user 4096 Feb 2 10:01 documents drwxr-xr-x 2 user user 4096 Feb 2 10:01 pictures drwxr-xr-x 2 user user 4096 Feb 2 10:01 downloads ``` ::: ::: dialogue @lina: できました!現在地と中身が見えました。 ::: ### 課題2:documents ディレクトリに移動して、また元に戻ろう {#challenge-2} **やること**:`documents` に移動し、移動できたことを確認してから、1つ上のディレクトリに戻ってください。 :::details ヒント1(方向づけ)を見る 移動するコマンドと、移動できたか確かめるコマンドを交互に使います。「1つ上」を表す記号がありましたね。 ::: :::details ヒント2(コマンド名)を見る 使うのは `cd` と `pwd` です。1つ上に戻るときは `cd` のうしろに `..` を付けます。 ::: :::details 答えを見る ```bash $ cd documents $ pwd ``` ```output /home/user/documents ``` ```bash $ cd .. $ pwd ``` ```output /home/user ``` ::: ::: dialogue @lina: 移動して、`cd ..` で戻れました! ::: ### 課題3:ホームディレクトリに一気に戻ろう {#challenge-3} **やること**:今どこにいても、一度でホームディレクトリに戻ってください。 :::details ヒント1(方向づけ)を見る `cd ..` を何回も打つ必要はありません。ホームを表す記号が1つあります。 ::: :::details ヒント2(コマンド名)を見る 使うのは `cd` です。ホームディレクトリは `~`(チルダ)で表します。 ::: :::details 答えを見る ```bash $ cd ~ $ pwd ``` ```output /home/user ``` ::: ::: dialogue @linny: 完璧。この3つのコマンドができれば、ターミナルで迷子にならなくなるよ。 @lina: やった!ターミナルが怖くなくなってきました! ::: ## 今日の3行まとめ {#summary} > **結論**: pwd・ls・cdの3コマンドの役割を3行で整理しターミナル入門の基礎を定着させる。 - `pwd` で「今どこにいるか」を確認(迷子防止) - `ls -la` で「何があるか」を詳しく確認(隠しファイルも見える) - `cd` で移動、`cd ..` で1つ上、`cd ~` でホームへ ## 次に学ぶこと {#next} > **結論**: 次はファイル作成・コピー・削除のコマンドを学んでファイル操作の基本を完成させよう。 ::: dialogue @lina: この3つのコマンドができるようになったら、次は何を学べばいいですか? @linny: ファイル操作だね。ファイルを作ったり、コピーしたり、削除したり。[ファイル操作の基礎編](/articles/tutorials/file-operations-basics)でまた会話形式で教えるから、楽しみにしててね。 ::: # bat 入門 - シンタックスハイライト付きの cat 代替 Source: https://penguin-gym-linux.com/articles/tutorials/bat-cat-alternative ## この記事でわかること {#intro-list} - `cat` の代わりに `bat` を使い、**色付き・行番号付きでファイルを表示** できます - Ubuntu / Debian の **`batcat` 問題**(コマンド名が `bat` でない)を直せます - パイプやスクリプトで使うとき、**装飾を消して `cat` と同じ出力** にできます - `man` ページの色付けなど、**毎日きく応用** が身につきます **対象読者**:Linux 入門者、`cat` のそっけない出力に物足りなさを感じている方 ::: tip **言葉の整理(先に取りちがえを防ぎます)** - **シンタックスハイライト**:文法にそって、色を分けて見せることです。「構文の色分け」も同じ意味です。 - **ページャ**:長いファイルを 1 画面ずつ見せてくれる道具です。`less` が代表です。 - **標準出力**:コマンドの結果が出ていく先です。ふだんは画面です。 - **パイプ**:`|` のことです。左のコマンドの結果を、右のコマンドに渡します。 - **シンボリックリンク**:べつの名前で同じものを指す「近道」です。Windows のショートカットに近いものです。 - **`PATH`(パス)**:コマンドを探しに行く場所の一覧です。ここに入っていない場所のコマンドは、名前だけでは呼び出せません。 ::: ## 導入:リナの「cat が読みにくい」問題 {#intro} ::: dialogue @lina: ライニー先輩、設定ファイルを `cat config.json` で開きました。でも全部おなじ色で、どこが何だか目が滑ってしまいます。エディタで開きなおすのも面倒です。 @linny: `cat` は「中身をそのまま出す」だけだからね。色も行番号も付かない。それ、`bat` という `cat` の進化版を使うと一気に読みやすくなるよ。 @lina: バット。コウモリですか。 @linny: つづりは bat だけど「バット」でいい。`cat` をもじった名前なんだ。シンタックスハイライト・行番号・Git の差分表示まで、最初から全部ついてくるよ。 ::: ::: tip **結論を先に** - `bat` = `cat` の代わりです。**色付き・行番号・Git 差分・自動ページング** を最初から備えます - Ubuntu / Debian では `sudo apt install bat` で入ります。ただしコマンド名は **`batcat`** です - パイプやスクリプトでは `bat -p`(装飾なし)や `bat -pp`(ページャも無効)で `cat` と同じ感覚に戻せます ::: ## 1. bat とは何か? {#what} > **結論**: `bat` は `cat` の代替コマンド。中身を表示する点は同じだが、シンタックスハイライト・行番号・Git 差分マーカー・自動ページングが付く。 ::: dialogue @lina: そもそも `bat` は `cat` と何が違うのですか。 @linny: やることは同じ「ファイルの中身を表示する」だよ。違うのは **見やすさ**。`cat` が白黒のまま流すのに対して、`bat` は色を分け、行番号を振り、Git 管理下なら変わった行に印まで付ける。 @lina: 至れり尽くせりですね。`cat` の置きかえになりますか。 @linny: 日ごろの「ちょっと中身を見たい」は、ほぼ `bat` で済む。ただし完全な互換ではない。スクリプトやパイプでは少し気をつける点がある。後半で説明するよ。 ::: `bat` は [sharkdp/bat](https://github.com/sharkdp/bat) が開発する Rust 製のツールです。`cat` との主な違いは次のとおりです。 | 観点 | cat | bat | | ---------------- | ---------------- | ----------------------------- | | 構文の色分け | なし | あり(言語を自動判別) | | 行番号 | `-n` で付く | デフォルトで付く | | Git 差分表示 | なし | あり(変更行に印) | | 長いファイル | 一気に流れる | 自動で `less`(ページャ)起動 | | 不可視文字の表示 | `-A` で記号化 | `-A` で記号化 | | パイプ時の挙動 | 常に素のテキスト | 自動で装飾オフ(後述) | ## 2. インストールと batcat 問題 {#install} > **結論**: Ubuntu/Debian では `apt install bat` で入るが、名前衝突を避けるためコマンド名が `batcat` になる。シンボリックリンクで `bat` に揃えるのが定番。 ::: dialogue @lina: さっそく入れたいです。どうやってインストールしますか。 @linny: ディストリビューションごとにパッケージ名が少し違うんだ。まずはここから。 ::: ```bash # Ubuntu / Debian 系 $ sudo apt install bat # Fedora / RHEL 系 $ sudo dnf install bat # Arch Linux $ sudo pacman -S bat # macOS (Homebrew) $ brew install bat ``` ::: warning **Ubuntu/Debian の罠:コマンド名が `batcat`** Debian では、ほかのパッケージとの名前の重なりを避けています。そのためコマンド名が `bat` ではなく **`batcat`** になっています。`bat` と打つと `command not found` になります。 ::: ::: dialogue @lina: 先輩、`sudo apt install bat` は成功しました。でも `bat config.json` と打つと `bat: command not found` と出ます。インストールに失敗したのでしょうか。 @linny: 失敗していないよ。入っている。名前が `batcat` になっているだけ。`batcat --version` と打ってみて。 @lina: あっ、動きました。入っていないのではなく、名前が違っただけだったのですね。 @linny: そう。ここは多くの人が一度つまずくところ。`fd` の `fdfind` 問題と同じパターンだよ。シンボリックリンクを張れば `bat` で呼べるようになる。 ::: `bat` という名前で使いたい場合は、`~/.local/bin` にシンボリックリンクを張ります。 ```bash mkdir -p ~/.local/bin ln -sf "$(which batcat)" ~/.local/bin/bat ``` `-f` を付けているのは、すでに同じ名前のリンクがあっても張りなおせるようにするためです。 まず `~/.local/bin` が `PATH` に入っているかをたしかめます。 ```bash $ echo $PATH ``` 出力の中に `/home/ユーザー名/.local/bin` が見つかれば、この先の追記は要りません。見つからない場合だけ次に進みます。 ```bash echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc source ~/.bashrc ``` ::: warning **`>>` と `>` を間違えない** - `>>` は **末尾に足す** 記号です。もとの中身は残ります - `>` は **中身を消して書きかえる** 記号です。`>` を使うと `~/.bashrc` の中身がすべて消えます - 消えた中身は元に戻せません。ゴミ箱にも残りません - この操作はあなたのパソコンの設定ファイルを変えます。`bat` は本サイトの仮想ターミナルでは動かないため、実行は自分のパソコンのターミナルで行います - **実行前に必ず控えを取ってください** ```bash $ cp ~/.bashrc ~/.bashrc.bak ``` - 間違えて `>` を打ってしまったら、`cp ~/.bashrc.bak ~/.bashrc` で戻せます ::: ::: tip **`source` は何をしているか** `~/.bashrc` は「新しくターミナルを開いたとき」に読まれるファイルです。追記しただけでは、いま開いているターミナルには反映されません。`source ~/.bashrc` は「今すぐ読みなおして」という指示です。ターミナルを開きなおしても同じ効果になります。 ::: インストールとバージョンを確認します。 ```bash $ bat --version ``` ```output bat 0.24.0 ``` 数字は環境によって違います。バージョンが表示されれば、インストールは成功しています。 ::: tip **Ubuntu でリンクを張らない場合** リンクが面倒なら、この記事のコマンド例の `bat` を `batcat` に読みかえても動きます。ただしリンクを張っておく方が、ほかの記事やドキュメントの `bat` をそのままコピペできて楽です。 ::: ## 3. bat の基本的な使い方は? {#basic} > **結論**: `bat ファイル名` で、色付き・行番号付きで中身を表示する。複数ファイルを並べると、見出し付きで区切って表示してくれる。 ::: dialogue @linny: 使い方は `cat` とまったく同じ。表示したいファイル名を渡すだけだよ。まずは色分けを試すファイルを作ろう。 ::: ```bash $ mkdir -p ~/bat-practice $ cd ~/bat-practice $ printf '{\n "name": "penguin",\n "version": "1.0.0",\n "debug": false\n}\n' > config.json ``` ```bash $ bat config.json ``` ```output ───────┬──────────────────────────────────────── │ File: config.json ───────┼──────────────────────────────────────── 1 │ { 2 │ "name": "penguin", 3 │ "version": "1.0.0", 4 │ "debug": false 5 │ } ───────┴──────────────────────────────────────── ``` ::: tip **画面の読み方** - 左端の数字:**行番号** です(`cat -n` にあたるものが標準で付きます) - 上部の `File: config.json`:表示中のファイル名の **ヘッダー** です - 中身の色:拡張子から **言語を自動で見分けて** 色を分けます(上は JSON) ::: 複数ファイルを渡すと、それぞれにヘッダーが付いて区切られます。 ```bash $ bat a.txt b.txt ``` ::: dialogue @lina: `cat a.txt b.txt` だと中身がくっついて、どこからが b.txt かわからなくなっていました。 @linny: そう、そこが効いてくる。`bat` ならファイルごとに見出しが入るから、どこからが次のファイルかが一目でわかる。 ::: ::: tip **長いファイルは自動でページャになります** 画面に収まらない長いファイルを `bat` で開くと、自動で `less`(ページャ)が起動します。矢印キーや `Space` でスクロールし、`q` で終わります。`cat` のように画面を一気に流れて見失うことがありません。 ::: ## 4. cat のように装飾を消すには? {#plain} > **結論**: 装飾(行番号・ヘッダー・罫線)を消すには `-p`(`--plain`)。ページャも止めてほぼ `cat` と同じ出力にするには `-pp` を使う。 ::: dialogue @lina: 色や行番号はうれしいです。でも中身をそのままコピーしたいとき、罫線や行番号がじゃまになります。 @linny: いい気づき。そういうときは **装飾を外す** オプションがある。用途に応じて 2 段階あるよ。 ::: ```bash # 行番号・ヘッダー・罫線を消す(色付きのまま) $ bat -p config.json # 装飾もページャも両方消す(ほぼ cat と同じ挙動) $ bat -pp config.json ``` ::: highlight **装飾オフの 2 段階** | オプション | 消えるもの | 残るもの | | ---------- | --------------------------- | ---------------------- | | `-p` | 行番号・ヘッダー・罫線 | 色付け・自動ページング | | `-pp` | 上記すべて + 自動ページング | 色付け(端末出力時) | ::: ::: dialogue @lina: `-p` を 2 回で `-pp` ですね。覚えやすいです。 @linny: そう。`-pp` は「装飾なし・ページャなし」だから、`cat` の代わりにそのまま差しかえやすい。中身だけをさっと見たいときの定番だよ。 ::: 不可視文字(タブ・改行コード・末尾の空白)を見たいときは `-A`(`--show-all`)です。 ```bash $ bat -A messy.txt ``` ::: tip `-A` はタブを `├──▶`、改行を `␊` のような記号で見せます。「見えない空白でスクリプトが動かない」たぐいの調査にきく機能です。`cat -A` と同じ用途ですが、色が付く分わかりやすくなります。 ::: ## 5. 表示をカスタマイズするには? {#style} > **結論**: 表示する部品は `--style`(numbers / header / grid / changes など)で選び、配色は `--theme` で変える。テーマ一覧は `--list-themes` で確認できる。 ::: dialogue @lina: 行番号はほしいけれど罫線はいらない、のように細かく選べますか。 @linny: できるよ。`--style` で表示する部品をカンマ区切りで指定するんだ。 ::: ```bash # 行番号だけ表示(罫線・ヘッダーなし) $ bat --style=numbers config.json # 行番号と Git 変更マーカーだけ $ bat --style=numbers,changes config.json # すべての装飾を消す(-p と同じ) $ bat --style=plain config.json ``` ::: highlight **`--style` で指定できる主な部品** | 値 | 意味 | | --------- | -------------------------- | | `numbers` | 行番号 | | `header` | ファイル名ヘッダー | | `grid` | 区切りの罫線 | | `changes` | Git の変更マーカー | | `full` | 全部入り | | `plain` | 全部なし(`-p` 相当) | ::: 配色テーマは `--theme` で変えます。使えるテーマは `--list-themes` で一覧できます。 ```bash # テーマ一覧を見る $ bat --list-themes # テーマを指定して表示 $ bat --theme=ansi config.json ``` ::: tip **設定を残しておく** 毎回オプションを打つのが面倒なら、設定ファイルに既定値を書けます。`bat --generate-config-file` で雛形を作り、`--theme` や `--style` の既定値を書いておきます。以降は素の `bat` だけでその設定がきくようになります。 ::: ## 6. 特定の行だけ見る・強調するには? {#range} > **結論**: 行範囲は `-r`(`--line-range`)で `開始:終了` を指定する。特定行を目立たせたいときは `-H`(`--highlight-line`)。言語を手動指定したいときは `-l`(`--language`)。 ::: dialogue @lina: 何千行もあるログの、特定の範囲だけ見たいときはどうしますか。 @linny: `-r` で行の範囲を指定できる。`tail` や `sed` を使わなくても、`bat` だけで切り出せるよ。 ::: ```bash # 100 行目から 120 行目までを表示 $ bat -r 100:120 app.log # 50 行目以降を最後まで $ bat -r 50: app.log # 先頭から 30 行目まで $ bat -r :30 app.log ``` 特定の行を色で目立たせたいときは `-H` です。 ```bash # 42 行目を目立たせながら全体を表示 $ bat -H 42 app.log ``` 拡張子のないファイルなどで言語が自動で決まらないときは、`-l` で伝えます。 ```bash # 拡張子なしのファイルを JSON として色付け $ bat -l json mydata ``` ::: dialogue @lina: 拡張子がないと色が付かなかったのは、言語がわからなかったからなのですね。 @linny: そう。`bat` は拡張子や中身から言語を見当づけるけれど、外れることもある。そのときは `-l` で「これは JSON だよ」と教えてあげればいい。対応する言語の一覧は `bat --list-languages` で見られるよ。 ::: ## 7. パイプやスクリプトで使うときの注意は? {#pipe} > **結論**: `bat` は出力先がパイプやファイルのときは自動で装飾を外し、素のテキストを流す。それでも `cat` の完全な代替ではないので、スクリプトでは `cat` を使うのが無難。 ::: dialogue @lina: `bat config.json | grep debug` とやったら、色の記号が混ざらずに `grep` の結果が出ました。 @linny: いいところに気づいたね。`bat` は **出力先が画面かどうか** を見ている。パイプやファイルに渡すときは、自動で装飾と色を外すんだ。だから `grep` や `wc` に渡しても壊れないよ。 ::: ```bash # パイプに渡すと自動で素のテキストになる $ bat config.json | grep debug # ファイルにリダイレクトしても色コードは入らない $ bat config.json > copy.json ``` ::: warning **スクリプトでは `cat` を使う** `bat` は便利ですが、**すべての環境に入っているわけではありません**。シェルスクリプトの中で `bat` を前提にすると、`bat` が無いマシンで動かなくなります。配布・共有するスクリプトでは、装飾の要らない処理は素直に `cat` を使ってください。`bat` は「人間が目で見るとき」の道具と割り切るのが安全です。 ::: ::: dialogue @lina: `alias cat=bat` にして `cat` を完全に置きかえるのはどうですか。 @linny: 気持ちはわかるけれど、おすすめしない。スクリプトや他人の手順書が `cat` の素の動きを前提にしていることがある。エイリアスで上書きすると思わぬ事故になるよ。使うなら `alias bat='batcat'` のように **べつの名前で足す** 方向がいい。 ::: ## 8. 実務で効く応用は? {#advanced} > **結論**: `bat` は `man` ページの色付けや、`fzf` などのプレビュー表示にも使える。「色付きで中身を見せる」場面ならどこでも差し込める。 ::: dialogue @linny: せっかくなので、`bat` を単体で使う以外の使い道も紹介しておくね。一度設定すれば、あとはずっと効く設定だよ。 ::: `man` ページを `bat` で色付けするには、環境変数 `MANPAGER` を設定します。`~/.bashrc` の末尾に次の行を足し、`source ~/.bashrc` で読みなおします。 ```bash export MANPAGER="sh -c 'col -bx | bat -l man -p'" ``` ::: warning シンボリックリンクを張っていない Ubuntu / Debian では、上の `bat` を `batcat` に書きかえてください。書きかえ忘れると、`man` を開いたときに何も表示されなくなります。次の `fzf` の例も同じです。 ::: ::: tip **読み方** - `col -bx`:`man` の出力に含まれる制御文字を整えます(重ね打ちによる太字など) - `bat -l man -p`:それを `man` 用の言語ルールで色付けし、装飾なし(`-p`)で表示します - 設定後に `man ls` などを開くと、見出しやオプションが色分けされて読みやすくなります ::: `fzf`(あいまい検索)のプレビューに使うと、候補ファイルの中身を色付きで見られます。 ```bash # ファイルを選びながら中身を色付きプレビュー $ fzf --preview 'bat --color=always {}' ``` ::: dialogue @lina: `--color=always` を付けているのはなぜですか。 @linny: `fzf` のプレビュー窓は、画面ではないと判定されることがある。そのままだと `bat` が色を外してしまうんだ。だから「いつも色を付けて」と明示している。ふだんのパイプでは付けないのが正解、という使い分けだよ。 ::: ## 9. ミニ課題:自分の環境で試してみよう {#exercise} > **結論**: 表示・装飾オフ・行範囲指定の 3 問で、`bat` の基本操作を定着させる。 ::: dialogue @linny: 覚えたことを手になじませるために、自分の環境で次の課題をやってみよう。 ::: これから使う `~/.bashrc` は、見るだけで書きかえません。安心して試してください。 **課題 1**:`~/.bashrc` を `bat` で開き、**行番号と色** が付くことを確かめよう。 :::details ヒント 1(方向づけ)を見る `cat` と同じ使い方です。見たいファイルの名前を渡すだけで、オプションは要りません。 ::: :::details ヒント 2(コマンド名)を見る `bat ~/.bashrc` を実行します。Ubuntu でリンクを張っていない場合は `batcat` に読みかえます。 ::: :::details 答えを見る ```bash $ bat ~/.bashrc ``` 左端に行番号、上部にファイル名のヘッダーが出ます。シェルスクリプトとして色分けされていれば成功です。長い場合はページャが開くので、`q` で終わります。 ::: **課題 2**:同じファイルを開き、**装飾もページャも消えて `cat` と同じ見た目** になることを確かめよう。 :::details ヒント 1(方向づけ)を見る 装飾を外すオプションを 2 回重ねます。1 回だと色とページャが残ります。 ::: :::details ヒント 2(コマンド名)を見る `bat -pp ~/.bashrc` を実行します。 ::: :::details 答えを見る ```bash $ bat -pp ~/.bashrc ``` 行番号・ヘッダー・罫線が消えます。ページャも開かず、一気に出力されます。`cat ~/.bashrc` とほぼ同じ見た目です(画面では色だけ残ります)。 ::: **課題 3**:`~/.bashrc` の **先頭 10 行だけ** を表示しよう。 :::details ヒント 1(方向づけ)を見る 行の範囲を指定するオプションがあります。`開始:終了` の形で書きます。 ::: :::details ヒント 2(コマンド名)を見る `bat -r` を使います。先頭からなら、開始を省略できます。 ::: :::details 答えを見る ```bash $ bat -r 1:10 ~/.bashrc ``` `bat -r :10 ~/.bashrc` でも同じ結果になります。`bat -r 5: ~/.bashrc`(5 行目以降)も試すと、範囲指定の感覚がつかめます。 ::: ## 振り返り {#review} > **結論**: 人が目で見るときは bat、スクリプトで渡すときは cat、という使い分けに落ち着く。 ::: dialogue @lina: 整理します。まず `bat` で見る、コピーしたいときは `-pp`、範囲をしぼるなら `-r` ですね。 @linny: いい整理だね。ただ、スクリプトの中では `cat` を使う。`bat` は「人が目で見るとき」の道具、と覚えておけば迷わないよ。 @lina: Ubuntu で `command not found` が出ても、もうあわてません。名前が `batcat` なだけですね。 ::: ## 今日の 3 行まとめ {#summary} > **結論**: bat は cat の読みやすい代わりであり、装飾は `-pp` で外せる。中身の表示は同じ。 1. `bat ファイル名` で、色付き・行番号付きに中身を見られます 2. Ubuntu で `bat: command not found` が出たら、名前が `batcat` になっているだけです 3. パイプやコピー用途では `-pp` を付けます。スクリプトでは素直に `cat` を使います ## 次に読む {#next} - [less / more / tail でファイルを見る](/articles/tutorials/less-more-tail) - [fd 入門 - find より直感的なファイル検索ツール](/articles/tutorials/fd-find-alternative) - [ripgrep(rg)入門 - grep より速いテキスト検索](/articles/tutorials/ripgrep-basics) # ブレース展開(brace expansion)入門 - {1..10} や {a,b,c} で楽にコマンドを書く Source: https://penguin-gym-linux.com/articles/tutorials/brace-expansion ## この記事でわかること {#intro} - **ブレース展開(brace expansion)** が何をする機能なのか分かる - `{1..10}`(連番)と `{a,b,c}`(リスト)の書き方が分かる - `mkdir` や `cp` と組み合わせて **同じ作業の繰り返しを 1 行に短縮** できる - ブレース展開と `*`(ワイルドカード)の **決定的な違い** が分かる ::: tip **結論(先に覚えるべき型)** - 連番がほしい → `{1..10}` - 決まった単語を並べたい → `{a,b,c}` - バックアップを作る → `cp file.txt{,.bak}` ::: ## 1. ブレース展開って何? {#what} > **結論**: ブレース展開は `{ }` の中身を展開して複数の文字列を一気に生成する、bash の機能。 ::: dialogue @lina: 先輩、`file1.txt` から `file5.txt` まで作りたいんですけど、`touch file1.txt` を 5 回打つしかないですか? @linny: いや、もっと楽な方法があるよ。これを見て。 @linny: `touch file{1..5}.txt` @lina: え、たった 1 行?! `{1..5}` って何ですか? @linny: それが今日のテーマ、**ブレース展開** だよ。`{ }`(波括弧、ブレース)の中身をシェルが展開して、`file1.txt file2.txt file3.txt file4.txt file5.txt` という 5 個の文字列に化けるんだ。 ::: 実際に何が起きているか、`echo` で確かめてみよう。`echo` は **渡された文字列をそのまま表示する** コマンドなので、展開の結果を見るのにぴったり。 ```bash $ echo file{1..5}.txt ``` ```output file1.txt file2.txt file3.txt file4.txt file5.txt ``` ::: highlight **ブレース展開の正体** `{ }` の中身を、シェルが **コマンドを実行する前に** 複数の文字列へ展開する。 だから `touch file{1..5}.txt` は、実際には `touch file1.txt file2.txt file3.txt file4.txt file5.txt` を実行しているのと同じ。 ::: ## 2. 連番を作る:{開始..終了} {#sequence} > **結論**: `{1..10}` は 1 から 10 の連番を生成する。アルファベットや降順、逆順も同じ書き方でできる。 ### 2-1. 数字の連番 ```bash $ echo {1..10} ``` ```output 1 2 3 4 5 6 7 8 9 10 ``` ::: dialogue @lina: `..`(ドット 2 つ)が「ここからここまで」って意味なんですね。 @linny: そう。`{開始..終了}` で連番。カンマじゃなくて **ドット 2 つ** なのがポイントだよ。 ::: ### 2-2. アルファベットの連番 ```bash $ echo {a..e} ``` ```output a b c d e ``` ### 2-3. 降順もできる 開始より終了を小さくすれば、逆順に並ぶ。 ```bash $ echo {5..1} ``` ```output 5 4 3 2 1 ``` ## 3. ゼロ埋めと飛ばし {#padding} > **結論**: `{01..10}` でゼロ埋め、`{1..10..2}` で増分(ステップ)を指定できる。 ### 3-1. ゼロ埋め(桁をそろえる) ファイル名の桁をそろえたいとき、開始の数字を `0` 始まりにするとゼロ埋めになる。 ```bash $ echo {01..10} ``` ```output 01 02 03 04 05 06 07 08 09 10 ``` ::: tip ゼロ埋めはファイルの並び順を安定させるのに重要。`file1` `file2` ... `file10` は名前順だと `file1, file10, file2...` の順になってしまうが、`file01` `file02` ... `file10` なら見た目どおりに並ぶ。 ::: ### 3-2. 増分(ステップ)を指定する `{開始..終了..増分}` で、いくつ飛ばしにするか指定できる。 ```bash $ echo {0..20..5} ``` ```output 0 5 10 15 20 ``` ```bash $ echo {1..10..2} ``` ```output 1 3 5 7 9 ``` ## 4. リストを作る:{a,b,c} {#list} > **結論**: `{a,b,c}` はカンマ区切りの単語をそのまま並べる。連番にならない単語を列挙したいときに使う。 連番ではなく、**決まった単語を並べたい** ときはカンマ区切りを使う。 ```bash $ echo {apple,banana,cherry} ``` ```output apple banana cherry ``` ::: warning **カンマの前後にスペースを入れない** `{a, b, c}` のようにスペースを入れると、ブレース展開として認識されず、そのまま文字として扱われる。`{a,b,c}` と **詰めて書く** こと。 ::: ::: dialogue @lina: `..` が連番で、`,` がリスト。使い分けは分かりました! @linny: いいね。`{1..100}` を `{1,2,3,...,100}` と全部書くのは大変だから連番、決まった数語を並べるならリスト、と覚えておくといいよ。 ::: ## 5. 前後にくっつける・組み合わせる {#combine} > **結論**: ブレースの前後に文字を付けると各要素に配られる。複数のブレースを並べると全組み合わせが生成される。 ### 5-1. 接頭辞・接尾辞(prefix / suffix) ブレースの前後に文字を付けると、その文字が **各要素すべてに配られる**。 ```bash $ echo image_{1..3}.png ``` ```output image_1.png image_2.png image_3.png ``` ### 5-2. 複数のブレースを並べる(直積) ブレースを 2 つ以上並べると、**全部の組み合わせ** が生成される。 ```bash $ echo {A,B}{1,2} ``` ```output A1 A2 B1 B2 ``` ::: dialogue @lina: 2 × 2 で 4 通り全部出てきた! @linny: そう、これを「直積」っていうんだ。座席表(`{A,B,C}{1,2,3,4}`)みたいに、行 × 列の組み合わせを一気に作りたいときに便利だよ。 ::: ## 6. 実務で効くテクニック {#practical} > **結論**: ディレクトリ一括作成・バックアップ作成・リネームは、ブレース展開で 1 行に短縮できる定番パターン。 ### 6-1. ディレクトリをまとめて作る ```bash $ mkdir -p project/{src,test,docs} ``` `project` の下に `src` `test` `docs` を一度に作れる。`-p` は「親ディレクトリがなければ一緒に作る」オプション。 ### 6-2. バックアップを一瞬で作る ブレースの片方を **空** にできる。`{,.bak}` は「何も付けない」と「`.bak` を付ける」の 2 つを生成する。 ```bash $ cp config.yml{,.bak} ``` ::: highlight **`cp config.yml{,.bak}` の中身** これは次のコマンドと同じ。 ```bash $ cp config.yml config.yml.bak ``` `{,.bak}` が `config.yml` と `config.yml.bak` の 2 つに展開され、`cp 元 先` の形になる。 ::: ### 6-3. リネームにも使える ```bash $ mv report.txt{,_old} ``` `report.txt` を `report.txt_old` にリネーム。同じファイル名を 2 回打たなくて済む。 ## 7. ブレース展開と `*`(ワイルドカード)の違い {#vs-glob} > **結論**: ブレース展開は文字列を「生成」する。`*` は既存ファイルに「マッチ」する。存在しないものを作れるのがブレース展開。 ::: dialogue @lina: `*` も複数のファイルをまとめて扱いますよね。何が違うんですか? @linny: 大事な違いがあるんだ。**`*` は今あるファイルを探してマッチする**。でも **ブレース展開はファイルが存在しなくても文字列を作る**。だから「これから作るファイル名」を書けるんだよ。 ::: ::: highlight **生成(brace)か、マッチ(glob)か** | 記法 | 動き | ファイルが無いとき | | ------------ | ------------------------------ | ------------------------ | | `{1..3}.txt` | 文字列を **生成** する | それでも文字列が作られる | | `*.txt` | 既存ファイルに **マッチ** する | 何にもマッチしない | `touch file{1..3}.txt`(これから作る)は OK。`touch file*.txt`(まだ無い)は意図通りにならない。 ::: ## 8. よくあるつまずき {#pitfalls} > **結論**: スペースの混入・変数の直接利用・ドットの数が定番のミス。`$変数` を範囲に使うときは `seq` か `eval` を検討する。 ### 8-1. 中にスペースを入れてしまう ```bash $ echo { 1..3 } ``` ```output { 1..3 } ``` スペースが入るとブレース展開と認識されず、そのまま表示される。`{1..3}` と詰めて書く。 ### 8-2. 変数をそのまま範囲に使えない ```bash $ n=5 $ echo {1..$n} ``` ```output {1..5} ``` ::: warning ブレース展開は **変数展開より先** に行われるため、`{1..$n}` の `$n` はまだ数字になっていない。変数で範囲を回したいときは `seq` を使う。 ```bash $ echo $(seq 1 $n) ``` ```output 1 2 3 4 5 ``` ::: ### 8-3. ドットが 1 つだと連番にならない `{1.5}` のようにドットが 1 つだと連番にならない。連番は **必ずドット 2 つ**(`{1..5}`)。 ## 9. ミニ課題:実際にやってみよう {#exercise} > **結論**: 連番ファイル作成・ディレクトリ一括作成・バックアップの 3 問で、ブレース展開の基本を手で確かめる。 ::: dialogue @lina: 手を動かして覚えたいです! @linny: いいね、3 問用意したよ。本番の前に `echo` を付けて結果を確認してから実行するのがおすすめ。 ::: **課題1**: `day01.log` から `day07.log` まで、7 個のログファイルを一度に作ろう。 :::details ヒントを見る ゼロ埋めの連番 `{01..07}` と `touch` を組み合わせる。 ::: :::details 解答例 ```bash $ touch day{01..07}.log ``` ::: **課題2**: `myapp` ディレクトリの下に `bin` `lib` `conf` `log` の 4 つを一度に作ろう。 :::details ヒントを見る リスト `{bin,lib,conf,log}` と `mkdir -p` を使う。 ::: :::details 解答例 ```bash $ mkdir -p myapp/{bin,lib,conf,log} ``` ::: **課題3**: `notes.md` のバックアップ `notes.md.bak` を 1 つのコマンドで作ろう。 :::details ヒントを見る 片方を空にしたブレース `{,.bak}` を使う。 ::: :::details 解答例 ```bash $ cp notes.md{,.bak} ``` ::: ## 10. コピペ用テンプレート {#templates} > **結論**: 連番・リスト・組み合わせ・バックアップのよく使う型をまとめて手元に置いておく。 ::: tip **よく使う型をまとめておく** ```bash # 連番ファイルを作る touch file{1..10}.txt # ゼロ埋めの連番 touch log{01..12}.txt # 飛ばし(2 ずつ) echo {0..100..10} # 決まった単語のリスト mkdir -p project/{src,test,docs} # 全組み合わせ(直積) echo {2025,2026}-{01..12} # バックアップを作る cp config.yml{,.bak} # 安全確認:echo を付けて結果を見てから実行 echo touch file{1..10}.txt ``` ::: ## 次に読む {#next} - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [pwd・cd・lsの使い方](/articles/tutorials/basic-commands) - [mkdir・touch・echoの使い方](/articles/tutorials/file-creation-basics) - [仮想ターミナルでコマンドを試す](/terminal) # md5sum / sha256sum 入門 - チェックサムで改ざん・破損を検証する Source: https://penguin-gym-linux.com/articles/tutorials/checksum-md5-sha256 ## この記事で学べること {#intro} - **チェックサム(ハッシュ)** が何のためにあるのかがわかります - `sha256sum` で **ファイルの指紋を計算** できます - `-c` を使って **壊れていないか・すり替えられていないか** を照合できます - **ハッシュ・暗号化・エンコード** の違いを言い分けられます ::: tip **言葉の整理(先に取りちがえを防ぎます)** - **チェックサム / ハッシュ値 / 指紋**:ほぼ同じものを指す呼び方です。この記事では「指紋」を説明用の言い換えとして使います。 - **ハッシュ**:中身から短い値を作ることです。**元のファイルには戻せません**。「同じか違うか」を確かめるための道具です。 - **暗号化**:鍵を持つ人だけが元に戻せるようにすることです。中身を隠すのが目的です。ハッシュとは目的が違います。 - **エンコード**:書き方を変えるだけの変換です。鍵なしで誰でも戻せます。`base64` がこれにあたります。 ハッシュは **戻せません**。暗号化は **鍵があれば戻せます**。エンコードは **誰でも戻せます**。この 3 つを混ぜないでください。 ::: ::: tip **結論(先に覚える型)** - とりあえず指紋を見たい → `sha256sum file` - 公式サイトの値と一致するか確認したい → `sha256sum -c SHA256SUMS` - 改ざん対策なら **SHA-256** です。MD5 / SHA-1 は **偶発的な破損検出にしか使いません** ::: ::: warning **前提(対象環境)** - OS:Ubuntu / 一般的な Linux - `md5sum` / `sha256sum` は GNU coreutils に含まれ **標準搭載** です(追加インストール不要) - 同じ仲間に `sha1sum` / `sha512sum` / `b2sum` などがあります ::: ## 1. チェックサムって何? {#what} > **結論**: チェックサムはファイルの中身から計算する「指紋」。中身が1ビットでも変われば値が大きく変わる。 ::: dialogue @lina: 先輩、ダウンロードページに「SHA256: a1b2c3...」みたいな長い文字列が載っていることがあります。あれは何ですか。 @linny: それが **チェックサム**、別名 **ハッシュ値** だよ。ファイルの中身を全部読んで計算する「指紋」のようなものだ。同じ中身なら必ず同じ値になる。 @lina: 指紋。ファイルごとに違うということですか。 @linny: ほぼそう考えていい。中身が違えば、指紋もまず別の値になる。「まず」と言ったのには理由がある。あとで話すよ。 @lina: わかりました。覚えておきます。 @linny: しかも中身が **たった 1 ビット** 変わるだけで、指紋の値はまったく別物になる。だから「落としたファイルが本物と同じか」を一瞬で確認できるんだ。 ::: ::: highlight **チェックサムでできること** - ダウンロードしたファイルが **途中で壊れていないか**(破損検出) - 配布元と **中身が同じか**(すり替え・改ざんの検出) - 2 つのファイルが **完全に同一か** の比較 ::: ::: tip **指紋からは中身を作れません** ハッシュは一方通行です。指紋の値から元のファイルを復元することはできません。だから「隠す」用途には使えません。隠したいなら暗号化を使ってください。 ::: ## 2. sha256sum で指紋を計算する {#sha256sum} > **結論**: `sha256sum file` でハッシュを計算する。出力は「ハッシュ+スペース2つ+ファイル名」の形式。 ### 2-1. 基本:1 ファイルの指紋を出す ```bash $ sha256sum ubuntu.iso ``` ```output e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ubuntu.iso ``` ::: dialogue @lina: とても長い文字列が出ました。後ろにファイル名も付いていますね。 @linny: それが SHA-256 の指紋。64 桁の 16 進数だよ。これが一致すれば中身は同じとみなせる。間の **スペースは 2 つ**。ここが後で照合に効いてくる。 ::: ::: highlight **出力の読み方** ``` e3b0c4...b855 ubuntu.iso └─ ハッシュ値 ─┘└┘└ ファイル名 2つのスペース ``` - 1 つ目のスペースは区切りです - 2 つ目の文字は **入力モード** を表します(` ` = テキスト / `*` = バイナリ) - GNU 版では両モードに中身の差はありません(歴史的な互換のためです) ::: ### 2-2. md5sum も使い方は同じ ```bash $ md5sum ubuntu.iso ``` ```output d41d8cd98f00b204e9800998ecf8427e ubuntu.iso ``` `md5sum` は MD5、`sha256sum` は SHA-256 を計算します。違いはそこだけで、**使い方は同じ** です。`sha1sum` / `sha512sum` も同様です。 ## 3. 複数ファイルと一覧の保存 {#multiple} > **結論**: 複数ファイルをまとめて計算でき、`>` で一覧ファイルに保存しておけば後で `-c` 照合に使える。 ### 3-1. 複数ファイルを一括で ```bash $ sha256sum *.iso ``` ```output e3b0c4...b855 ubuntu.iso 9f86d0...0a08 debian.iso ``` ### 3-2. 一覧をファイルに保存 ```bash $ sha256sum *.iso > SHA256SUMS ``` `SHA256SUMS` という名前は、配布サイトでよく使われる慣習です。中身は「ハッシュ+ファイル名」が並んだ、ただのテキストです。 ::: warning **どこに作られるか・上書きの危険** - `SHA256SUMS` は **いま自分がいるディレクトリ** に作られます。`pwd` で確認できます - `>` は同じ名前のファイルがあると、**中身を消して上書きします**。確認は聞かれません(`>>` と 2 つ重ねると、上書きではなく末尾への追記になります) - 大事な一覧を消さないよう、先に `ls SHA256SUMS` で存在を確かめてください - `sha256sum` は本サイトの仮想ターミナルでは動きません。実行は自分のパソコンのターミナルで行います - 練習は「8. ミニ課題」で作る、からっぽのディレクトリの中だけで行ってください。そこなら、もとからあるファイルを壊しません ::: ::: dialogue @lina: この一覧は、あとで何に使うのですか。 @linny: 次の `-c` で「保存しておいた指紋と、今のファイルが一致するか」をまとめて照合できる。バックアップが壊れていないかの定期チェックにも便利だよ。 ::: ## 4. -c で照合する(ここが本番) {#check} > **結論**: `sha256sum -c 一覧ファイル` で、記録済みハッシュと現在のファイルを照合する。一致なら `OK`、不一致なら `FAILED`。 ### 4-1. 一覧と照らし合わせる ```bash $ sha256sum -c SHA256SUMS ``` ```output ubuntu.iso: OK debian.iso: OK ``` 中身が変わっていたら、こうなります。 ```output ubuntu.iso: OK debian.iso: FAILED sha256sum: WARNING: 1 computed checksum did NOT match ``` ::: dialogue @lina: `FAILED` が出たら、それは壊れているということですか。 @linny: 「保存した指紋と今の中身が違う」という意味だよ。原因はダウンロードの破損か、誰かによるすり替えのどちらか。どちらにせよ **そのファイルは使わない** のが正解だ。 ::: ### 4-2. 配布元の値 1 つと照合する ダウンロードページに「SHA256: 公式の値」が 1 行だけ載っている場合があります。その 1 行を一覧ファイルにすれば照合できます。 ```bash # 公式の値とファイル名を 1 行で書く(スペースは2つ) $ echo "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ubuntu.iso" > check.txt $ sha256sum -c check.txt ``` ```output ubuntu.iso: OK ``` ::: warning **区切りは自分で打ち直さない** 「ハッシュ」と「ファイル名」の間は、`sha256sum` の出力どおり **半角スペース 2 つ** にします。スペースを 3 つ以上にすると、余分な空白までファイル名の一部として扱われ、`No such file or directory` になります。配布元の値をコピーするときは、余計な空白を足さないでください。 ::: ### 4-3. 検証で便利なオプション ```bash # OK 行を出さず、失敗だけ表示 $ sha256sum -c --quiet SHA256SUMS # 一覧に無いファイルを無視(一部だけ確認したいとき) $ sha256sum -c --ignore-missing SHA256SUMS # 何も表示せず終了コードだけで判定(スクリプト向け) $ sha256sum -c --status SHA256SUMS && echo "全て一致" ``` `--status` は結果を画面に出しません。**終了コード**(成功 0 / 失敗 1)だけで判断します。終了コードは直前のコマンドが成功したかを表す数字で、`echo $?` で確認できます。シェルスクリプトでの自動チェックに向きます。 ## 5. 破損検出と改ざん検出は別物 {#corruption-vs-tamper} > **結論**: 偶発的な破損の検出はどのハッシュでもできるが、悪意ある改ざんへの防御は SHA-256 を使う必要がある。 ::: dialogue @lina: 破損の検出と改ざんの検出は、同じではないのですか。 @linny: 似ているけれど違う。**破損** は通信エラーやディスク不良で「勝手に壊れる」こと。**改ざん** は攻撃者が「指紋を一致させたまま中身をすり替える」ことを狙ってくる。後者に耐えるには、指紋をわざと衝突させられないハッシュが必要なんだ。 ::: ::: highlight **2 つの目的** | 目的 | 何を防ぐ | 使えるハッシュ | | ---------- | ---------------------------- | ---------------------- | | 破損検出 | 通信・ディスク由来の偶発破損 | MD5 / SHA-1 でも可 | | 改ざん検出 | 攻撃者による意図的なすり替え | **SHA-256 以上を推奨** | ::: ## 6. なぜ今 MD5 は非推奨なのか {#md5-deprecated} > **結論**: MD5 と SHA-1 は「異なる中身で同じ指紋」を作る衝突攻撃が現実的になったため、セキュリティ用途では非推奨。 ::: dialogue @linny: 1 章で「まず別の値になる」と言ったのを覚えているかな。その「まず」の部分が、今日の本題だよ。 @lina: MD5 はよく見かけます。使ってはいけないのですか。 @linny: 「絶対だめ」ではなく **用途次第**。MD5 と SHA-1 は **衝突攻撃** が現実的になっている。攻撃者が「中身は違うのに指紋は同じ」という 2 つのファイルを、わざと作れてしまうということだよ。 @lina: それでは、指紋が一致しても安心できないですね。 @linny: そう。だから **改ざん対策には MD5 / SHA-1 を使わない**。通信途中の偶発的な破損を見つけるだけなら、今でも十分使える。迷ったら SHA-256 を選べば間違いない。 ::: ::: danger **MD5 / SHA-1 を使ってはいけない場面** - ダウンロードファイルの **真正性(本物か)** の確認 - パスワードのハッシュ化 - 署名・証明書など **セキュリティが絡む** 用途 これらは **SHA-256 以上**(`sha256sum` / `sha512sum`)を使ってください。 ::: ## 7. よくある初心者のつまずき {#pitfalls} > **結論**: 大文字小文字の見間違い、スペースの数、改行コード混入が照合失敗の典型原因。 ### 7-1. 目視で比較して見落とす ::: dialogue @lina: 先輩、公式サイトの値とダウンロードしたファイルの指紋を、画面に並べて見比べました。一致していたのでインストールしました。 @linny: 64 桁を目で比べたのかな。途中の 1 文字が違っても気づきにくいよ。 @lina: えっ。全部そろっていると思ったのですが…。真ん中あたりは正直、流し読みでした。 @linny: 人間の目はそういうふうにできている。だから **`-c` で機械に照合させる**。これなら 1 文字の違いも必ず見つかるよ。 @lina: なるほど。見比べるのではなく、コマンドに判定させるんですね。 ::: ```bash # NG: 目で見比べる(見落とす) # OK: 一覧にして -c で照合 $ sha256sum -c SHA256SUMS ``` ### 7-2. 区切りに余分な空白が入っている 一覧ファイルの区切りに **空白を 3 つ以上** 入れると、こうなります。 ```output sha256sum: ' ubuntu.iso': No such file or directory ubuntu.iso: FAILED open or read sha256sum: WARNING: 1 listed file could not be read ``` `FAILED` ではなく `FAILED open or read` が出たときは、中身の不一致ではありません。「ファイル名が読めていない」という意味です。`sha256sum file > SHA256SUMS` で作った一覧をそのまま使えば、この形にはなりません。 ### 7-3. Windows で作った一覧がうまく照合できない Windows 由来のファイルは改行が `\r\n`(CRLF)です。末尾の `\r` が悪さをすることがあります。 ```bash # CRLF を LF に直してから照合 $ tr -d '\r' < SHA256SUMS.txt | sha256sum -c - ``` 末尾の `-` は「ファイルではなく、パイプで流れてきた内容を読む」という意味です。`sha256sum -c SHA256SUMS` のファイル名の位置に `-` を置く、と覚えてください。 ### 7-4. ハッシュが一致するのに「違うファイル」に見える ファイル名が違うだけで **中身が同じ** なら、ハッシュは一致します。ハッシュは **中身だけ** を見ます。ファイル名や更新日時は見ません。 ## 8. ミニ課題:手を動かして確かめよう {#exercise} > **結論**: 自分でファイルを作り、ハッシュ計算・一覧保存・照合・破損検出の流れを実際に体験して定着させる。 ::: dialogue @lina: 知識は入りました。実際に試したいです。 @linny: テスト用のファイルを作って試すのが一番。3 問用意したよ。 ::: 練習用のからっぽのディレクトリを作り、その中で試します。こうすれば、もとからあるファイルを上書きしません。 ```bash # 練習用ディレクトリを準備して移動する $ mkdir -p ~/checksum-practice && cd ~/checksum-practice ``` **課題 1**:`echo hello > a.txt` でファイルを作り、その SHA-256 ハッシュを表示しよう。 :::details ヒント 1(方向づけ)を見る 指紋を計算するコマンドに、ファイル名を渡すだけです。オプションはいりません。 ::: :::details ヒント 2(コマンド名)を見る `sha256sum` を使います。 ::: :::details 答えを見る ```bash $ echo hello > a.txt $ sha256sum a.txt ``` ```output 5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03 a.txt ``` 64 桁の指紋とファイル名が並びます。間はスペース 2 つです。 ::: **課題 2**:`a.txt` のハッシュ一覧を `SUMS` に保存し、照合して `OK` を確認しよう。 :::details ヒント 1(方向づけ)を見る 計算した結果をファイルに書き出します。そのあと、書き出した一覧を読ませて確かめます。 ::: :::details ヒント 2(コマンド名)を見る 使うコマンドは `sha256sum` です。保存にはリダイレクトの `>`、照合には `-c` を使います。 ::: :::details 答えを見る ```bash $ sha256sum a.txt > SUMS $ sha256sum -c SUMS ``` ```output a.txt: OK ``` `OK` は「保存した指紋と今の中身が同じ」という意味です。 ::: **課題 3**:`a.txt` の中身を書きかえてから再度照合し、`FAILED` が出ることを確認しよう。 :::details ヒント 1(方向づけ)を見る ファイルに 1 行だけ追記します。そのあと、課題 2 と同じ照合をもう一度行います。 ::: :::details ヒント 2(コマンド名)を見る 追記には `>>` を使います(`>` は上書きです)。照合は課題 2 と同じコマンドです。 ::: :::details 答えを見る ```bash $ echo world >> a.txt $ sha256sum -c SUMS ``` ```output a.txt: FAILED sha256sum: WARNING: 1 computed checksum did NOT match ``` 中身が変われば指紋も変わります。照合が `FAILED` になることを体感できます。 ::: ## 9. コピペ用テンプレート {#templates} > **結論**: 計算・一覧保存・照合・配布元1行との突き合わせの定番形をコピペで使える。 ::: tip **チェックサム テンプレ** ```bash # 1 ファイルの指紋を計算 sha256sum file # 複数ファイルをまとめて sha256sum *.iso # 一覧を保存(同名ファイルは上書き) sha256sum *.iso > SHA256SUMS # 保存した一覧と照合 sha256sum -c SHA256SUMS # 失敗だけ表示 sha256sum -c --quiet SHA256SUMS # スクリプト向け(終了コードで判定) sha256sum -c --status SHA256SUMS && echo OK # 配布元の値 1 行と照合(末尾の - はパイプの内容を読む指定) echo "<公式ハッシュ> file.iso" | sha256sum -c - # より強いハッシュが欲しいとき sha512sum file ``` ::: ::: warning **やってはいけないこと** - 改ざん対策に MD5 / SHA-1 を使う - 64 桁を目視で比較する(`-c` で機械照合する) - 区切りを手で打ち直す(`sha256sum` の出力をそのままコピーする) ::: ## 振り返り {#review} > **結論**: 指紋は元に戻せない値であり、照合は目ではなく `-c` に任せる。改ざん対策は SHA-256 を選ぶ。 ::: dialogue @lina: 整理します。ハッシュは中身から作る指紋で、そこから元のファイルには戻せないんですね。 @linny: その通り。戻せないから隠す道具にはならない。「同じか違うか」を確かめる道具だよ。 @lina: そして照合は目で見比べない。`-c` に判定させる。私の失敗はそこでした。 @linny: よく言語化できたね。あとは改ざん対策に SHA-256 を選ぶ。それだけ持ち帰れば十分だよ。 ::: ## 今日の 3 行まとめ {#summary} > **結論**: 指紋は `sha256sum` で計算し、目ではなく `-c` で照合し、改ざん対策には SHA-256 を選ぶ。 1. `sha256sum file` でファイルの指紋を計算できます。ハッシュは元に戻せません 2. 照合は目で見比べず、`sha256sum -c 一覧ファイル` に判定させます 3. 改ざん対策には SHA-256 以上を使います。MD5 / SHA-1 は偶発的な破損検出だけに使います ## まとめ:次に読む {#next} - [ファイル転送の基本 scp / rsync](/articles/tutorials/scp-rsync-basics) - [curl/wget入門 - コマンドラインでのHTTP通信](/articles/tutorials/curl-wget-basics) - [tar 入門 - アーカイブと圧縮](/articles/tutorials/tar-basics) # chmod 入門 - 数値モードとシンボリックモードを使い分ける Source: https://penguin-gym-linux.com/articles/tutorials/chmod-numeric-symbolic ## chmod の「2つの書き方」ってなに? {#intro} ::: dialogue @lina: ライニー先輩、`chmod` を調べたら `chmod 644` と書いてる記事がありました。`chmod u+x` と書いてる記事もあります。どっちが正解なんですか? @linny: どっちも正解だよ。`chmod` には書き方が 2 種類あるんだ。 @linny: 数字で一気に指定する「数値モード」と、`u+x` のように記号で指定する「シンボリックモード」だね。同じことを別の言い方で書いているだけだよ。 @lina: えっ、じゃあ覚えるのは 2 倍ですか。 @linny: 大丈夫。仕組みが分かれば「使い分けると楽」と気づくよ。今日はその使い分けを見ていこう。 ::: **chmod** はファイルやディレクトリの権限を変えるコマンドです。指定方法は 2 つあります。**数値モード**(`644` のような数字)と **シンボリックモード**(`u+x` のような記号)です。どちらでも同じ権限を設定できます。 ::: tip **言葉の整理** - **パーミッション**:そのファイルに誰が何をしてよいかを決めた設定です。「権限」「アクセス権」も同じものを指す別の呼び方です。 - **数値モード**:`644` のように数字 3 桁で指定する書き方です。「8 進数モード」「絶対モード」と呼ばれることもあります。 - **シンボリックモード**:`u+x` のように記号で指定する書き方です。「記号モード」「相対モード」と呼ばれることもあります。 **一言でいうと** - **数値モード** → 権限を「丸ごと一発」で決めたいとき(`chmod 644 file`) - **シンボリックモード** → 「今ある権限に少しだけ足し引き」したいとき(`chmod u+x file`) ::: ::: warning **先に安全な試し方を決めておきましょう** `chmod` はファイルの設定を書きかえるコマンドです。指定を間違えると、次のことが起こります。 - 権限を減らしすぎた場合:自分でも読めなくなります。`Permission denied` と出ます - 権限を増やしすぎた場合:他の人にも書きかえられる状態になります どちらも `chmod` をもう一度実行すれば戻せます。ファイルの中身は消えません。それでも、練習は次の型で進めてください。 1. 練習用のディレクトリを作り、その中だけで試す(例:`mkdir -p ~/chmod-practice && cd ~/chmod-practice`) 2. `touch demo.txt` のように、消えて困らないテスト用ファイルで試す 3. 変更の前後に `ls -l` を実行し、何が変わったかを目で確かめる 本サイトの仮想ターミナルでも `chmod` を試せます。実機の設定は変わりません。安心して試してください。 なお仮想ターミナルの `chmod` は、シンボリックモードを 1 つずつ指定する形に対応しています。`chmod u+x,go-w file` のようにカンマでまとめる書き方は、実機の Linux で使ってください。 ::: ## この記事でわかること {#what-you-will-learn} - 数値モード(`644`・`755`)とシンボリックモード(`u+x`・`go-w`)が同じ権限を表すことが分かる - `r=4`・`w=2`・`x=1` の足し算で数値モードを自分で計算できる - `u`/`g`/`o`/`a` と `+`/`-`/`=` でシンボリックモードを組み立てられる - 「丸ごと決める」「足し引きする」で 2 つを使い分けられる - `chmod 777` のような事故を避ける安全な型が身につく ## 1. まず共通の土台:rwx と 3 つの相手 {#rwx} > **結論**: 権限は「読む r・書く w・実行 x」を「所有者 u・グループ g・その他 o」の 3 者それぞれに与える仕組み。数値もシンボリックもこれを表しているだけ。 ::: dialogue @lina: そもそも `ls -l` で出てくる `rwxr-xr-x` が、毎回呪文に見えます。 @linny: 先頭の 1 文字は権限ではなく種別だよ。`-` はファイル、`d` はディレクトリだね。 @linny: そのあとを 3 文字ずつのカタマリで読むんだ。最初の `rwx` が所有者、次の `r-x` がグループ、最後の `r-x` がその他だよ。 @linny: 文字の意味は 3 つだけ。`r` が読む、`w` が書く、`x` が実行できる、ということだね。 ::: ::: tip **3 つの相手の意味** - **所有者(`u`)**:そのファイルを作った本人です。「オーナー」とも呼びます。 - **グループ(`g`)**:同じチームとして登録された人たちです。 - **その他(`o`)**:上の 2 つに当てはまらない、すべての人です。「other」の頭文字です。 ::: ```bash $ ls -l script.sh ``` ```output -rwxr-xr-x 1 user user 128 Jun 5 10:00 script.sh ``` | 位置 | 相手 | 記号 | この例 | 意味 | | ------------ | -------- | ------------ | ------ | ---------------------------------- | | 1 文字目 | 種別 | - | `-` | `-` はファイル、`d` はディレクトリ | | 2〜4 文字目 | 所有者 | `u`(user) | `rwx` | 読み書き実行すべて可 | | 5〜7 文字目 | グループ | `g`(group) | `r-x` | 読みと実行のみ | | 8〜10 文字目 | その他 | `o`(other) | `r-x` | 読みと実行のみ | ::: tip `a`(all)は「u と g と o の全員」をまとめて指す記号です。あとで使います。 ::: ## 2. シンボリックモード:足し引きで考える {#symbolic} > **結論**: シンボリックモードは「誰に(u/g/o/a)・どうする(+/-/=)・何を(r/w/x)」の 3 点セット。今の権限を起点に足し引きするので意図が明確。 シンボリックモードは次の 3 つを組み合わせます。 | 要素 | 記号 | 意味 | | -------- | --------------------- | --------------------------------- | | 誰に | `u` / `g` / `o` / `a` | 所有者 / グループ / その他 / 全員 | | どうする | `+` / `-` / `=` | 追加 / 削除 / 設定(完全上書き) | | 何を | `r` / `w` / `x` | 読み / 書き / 実行 | ### 2-1. 実行権限を所有者に追加する ```bash $ chmod u+x script.sh ``` `u`(所有者)に `+`(追加)で `x`(実行)を与えています。たとえば `rw-r--r--`(`644`)のファイルに実行すると、`rwxr--r--` になります。実行できるのは所有者だけです。 ### 2-2. グループとその他から書き込みを奪う ```bash $ chmod go-w secret.txt ``` `g` と `o`(グループとその他)から `-`(削除)で `w`(書き込み)を外しています。このように複数の相手をまとめて指定できます。 ### 2-3. 全員を読み取り専用にする(= の威力) ```bash $ chmod a=r notes.txt ``` `a`(全員)に `=`(設定)で `r` だけを与えています。**`=` は「今ある権限を消してから設定」します。** そのため、書き込みや実行が残っていても確実に「読みだけ」になります。 ::: dialogue @lina: `+` と `=` は、どう違うんですか? @linny: `+r` は「今の権限に読みを足す」だよ。`=r` は「読みだけにする」で、他は消える。 @linny: 実行権限を確実に外したいときは `=` が便利だね。 ::: ::: warning 複数の指定はカンマで区切れます。たとえば `chmod u+x,go-w file` と書けます。「所有者に実行を足し、グループとその他から書き込みを外す」を一度に指定できます。 この書き方は実機の Linux で使えます。本サイトの仮想ターミナルでは、`chmod u+x file` と `chmod go-w file` のように 2 回に分けて実行してください。 ::: ## 3. 数値モード:r=4 w=2 x=1 の足し算 {#numeric} > **結論**: 数値モードは r=4・w=2・x=1 を足した「1 桁の数字」を、所有者・グループ・その他の順に 3 桁並べる。`644` は所有者 rw・他は r。 ::: dialogue @lina: 数字の方は、どうやって決まるんですか? `644` の `6` はどこから来たんですか? @linny: 各権限に点数がついているんだ。`r` が 4 点、`w` が 2 点、`x` が 1 点だよ。 @linny: あとはその点数を足すだけ。`rw-` なら 4+2 で 6 になる。`r--` なら 4 だね。 @linny: だから所有者 6・グループ 4・その他 4 で `644` になるんだ。 ::: 各権限の点数を覚えましょう。この 3 つだけです。 | 権限 | 点数 | | ----------- | ---- | | `r`(読み) | 4 | | `w`(書き) | 2 | | `x`(実行) | 1 | ### 3-0. 3 ステップで `644` を組み立てる 数字は暗記しなくても、次の 3 ステップで作れます。 **ステップ 1**:相手ごとに「与えたい権限」を言葉で書きます。 - 所有者:読みたい。書きたい - グループ:読みたい - その他:読みたい **ステップ 2**:相手ごとに点数を足します。 - 所有者:`r`(4)+ `w`(2)= 6 - グループ:`r`(4)= 4 - その他:`r`(4)= 4 **ステップ 3**:その数字を **所有者・グループ・その他** の順に並べます。 - 6 と 4 と 4 を並べて `644` 1 桁が 1 人分の権限を表します。だから 3 桁で 3 人分になります。 ::: tip 逆向きにもたどれます。`755` の `7` は 4+2+1 です。つまり `rwx` です。`5` は 4+1 なので `r-x` です。 `4` が出てきたら `r`、`2` が出てきたら `w`、`1` が出てきたら `x` と考えます。組み合わせは足し算で決まります。 ::: | 数字 | 内訳 | 記号 | 意味 | | ---- | ----- | ----- | ---------- | | 7 | 4+2+1 | `rwx` | 全部 | | 6 | 4+2 | `rw-` | 読み書き | | 5 | 4+1 | `r-x` | 読みと実行 | | 4 | 4 | `r--` | 読みのみ | | 0 | 0 | `---` | 権限なし | ### 3-1. よく使う数値の組み合わせ ```bash $ chmod 644 notes.txt # rw-r--r-- 一般的なファイル $ chmod 755 script.sh # rwxr-xr-x 実行可能なスクリプト・ディレクトリ $ chmod 600 id_rsa # rw------- 秘密鍵など本人だけ ``` | 数値 | 記号 | よくある用途 | | ----- | ----------- | ---------------------------------- | | `644` | `rw-r--r--` | 通常のファイル(所有者だけ編集可) | | `755` | `rwxr-xr-x` | スクリプト・ディレクトリ | | `600` | `rw-------` | 秘密鍵・パスワードファイル | | `700` | `rwx------` | 本人専用ディレクトリ | ::: tip **逆算のコツ**:`755` を読むときは 1 桁ずつ分解します。`7` は `rwx`、`5` は `r-x`、最後の `5` も `r-x` です。 `r=4 w=2 x=1` さえ覚えていれば、記号と数字を自由に行き来できます。 ::: ## 4. どっちを使う? 使い分けの型 {#choose} > **結論**: 「権限を丸ごと決める」なら数値、「今の状態から少し足し引き」ならシンボリック。意図が伝わる方を選ぶ。 ::: dialogue @lina: 結局、現場ではどっちを使えばいいんですか? @linny: 目的で決めると迷わないよ。完成形がはっきりしているなら数値が速い。 @linny: 「実行権限だけ足したい」のように、今を起点に変えるならシンボリックが安全だね。どちらも読めるようになるのが理想だよ。 ::: | やりたいこと | おすすめ | 例 | | ------------------------ | ------------ | ------------------ | | 権限を完成形で一括設定 | 数値 | `chmod 644 file` | | 今の権限に追加・削除 | シンボリック | `chmod u+x file` | | 他の権限を壊さず足し引き | シンボリック | `chmod g+w file` | | 秘密鍵を本人専用に固定 | 数値 | `chmod 600 id_rsa` | | 全員から実行権限だけ外す | シンボリック | `chmod a-x file` | ::: tip **実務での目安** - スクリプトに実行権限を付けるだけ → `chmod +x script.sh`(相手を省くと全員が対象になります。ただし `umask` の影響を受けます。既定の環境では `a+x` とほぼ同じ結果です) - 設定ファイルを標準権限に直す → `chmod 644 config.yaml` ::: ## 5. よくある事故と安全な型 {#safety} > **結論**: `chmod 777` は「全員になんでも許可」で危険。必要な権限だけを最小限に与えるのが安全な型。 ::: danger **やりがちな事故:`chmod 777`** ```bash $ chmod 777 file # rwxrwxrwx = 誰でも読み書き実行できる ``` 「Permission denied が消えるから」という理由で `777` にするのは最悪のパターンです。その他(other)にまで書き込みと実行を許してしまいます。これはセキュリティホールになります。 ::: ### 5-1. リナの失敗:`chmod 444` で自分が書けなくなる {#failure} ::: dialogue @lina: 「他の人に書きかえられたくない」と思って `chmod 444 notes.txt` にしました。そのあと編集しようとしたら、自分まで保存できません。 ::: ```bash $ chmod 444 notes.txt $ ls -l notes.txt ``` ```output -r--r--r-- 1 user user 42 Jun 5 10:20 notes.txt ``` ::: dialogue @lina: えっ、自分のファイルなのに書けないんですか。所有者だけは特別だと思っていました。 @linny: 所有者にも同じルールが当てはまるんだ。`444` は 4+0+0 だから、3 人とも `r` だけ。所有者の `w` も消えているんだよ。 @lina: なるほど。`4` を選んだ時点で、自分の書き込みまで外していたんですね。 @linny: そのとおり。所有者だけ読み書きしたいなら `600` だね。`6` は 4+2 で `rw-` だよ。 @lina: 納得しました。数字を書く前に「所有者は何点か」を口に出して確かめます。 ::: ```bash $ chmod 600 notes.txt $ ls -l notes.txt ``` ```output -rw------- 1 user user 42 Jun 5 10:21 notes.txt ``` 権限を減らしすぎても、ファイルの中身は消えません。`chmod` をもう一度実行すれば元に戻せます。 ::: dialogue @lina: 別のときは、つい `777` にしました。動いたので大丈夫かと思っていました。 @linny: 動くけれど「全開放」だからね。まずは `ls -l` で今の権限を見よう。そのうえで、足りないものだけ足すのが正解だよ。 @linny: 自分が実行できないだけなら `chmod u+x` で十分。`777` はほぼ出番がないよ。 ::: ::: warning **ディレクトリの `x` に注意** ディレクトリの `x` は「中に入れる」権限です。`cd` できるかどうか、と考えてください。`r` だけの場合、一覧は見えますが中には入れません。 そのためディレクトリには `755`(`rwxr-xr-x`)がよく使われます。 ::: 安全な型は次の 3 ステップです。 1. `ls -l` で今の権限を確認する 2. 「誰に・何が足りない/余分か」を判断する 3. 足りない分だけ `chmod u+x` のように最小限を指定する ## 6. ミニ課題:実際にやってみよう {#practice} > **結論**: 数値の計算・シンボリックでの足し引き・両者の一致確認の 3 問で、chmod の基本を手で確かめる。 ::: dialogue @lina: 知識は入りました。手を動かして試したいです。 @linny: いいね、3 問用意したよ。まず練習用のディレクトリとテスト用ファイルを作ろう。消えて困らないファイルで試せば安心だね。 ::: ```bash $ mkdir -p ~/chmod-practice && cd ~/chmod-practice $ touch demo.txt $ ls -l demo.txt ``` ```output -rw-r--r-- 1 user user 0 Jun 5 10:10 demo.txt ``` **課題 1**: `demo.txt` を「所有者だけが読み書きできる」状態にしよう。数値モードを使います。 :::details ヒント 1(方向づけ)を見る 相手ごとに点数を足します。所有者は「読み」と「書き」の 2 つです。グループとその他には何も与えません。 何も与えない場合の点数は 0 です。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `chmod` です。`r` は 4、`w` は 2 なので、所有者は 4+2 になります。残り 2 桁は 0 です。 結果の確認には `ls -l` を使います。 ::: :::details 答えを見る ```bash $ chmod 600 demo.txt $ ls -l demo.txt ``` ```output -rw------- 1 user user 0 Jun 5 10:11 demo.txt ``` `6` は 4+2 で `rw-` です。`0` は権限なしなので `---` になります。 ::: **課題 2**: `demo.txt` に、所有者の実行権限だけを足そう。今ある権限は消さずに残します。 :::details ヒント 1(方向づけ)を見る 「今の状態に少しだけ足す」場面です。完成形を丸ごと指定する必要はありません。 足す相手は所有者だけです。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `chmod` です。相手は `u`、動作は `+`、権限は `x` です。この 3 つを続けて書きます。 ::: :::details 答えを見る ```bash $ chmod u+x demo.txt $ ls -l demo.txt ``` ```output -rwx------ 1 user user 0 Jun 5 10:12 demo.txt ``` 読みと書きは残ったままです。実行だけが足されました。数値で書くなら `700` と同じ状態です。 ::: **課題 3**: 同じ `rw-------` を、シンボリックモードだけで作ろう。今の権限が何であっても、確実にその状態にします。 :::details ヒント 1(方向づけ)を見る 「足す」でも「引く」でもありません。今ある権限を消してから決め直す記号を使います。 まず全員を空にして、そのあと所有者にだけ与える、と 2 段階で考えます。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `chmod` です。消してから設定する記号は `=` です。全員は `a`、所有者は `u` です。 2 つの指定はカンマで区切って並べられます。 ::: :::details 答えを見る ```bash $ chmod a=r demo.txt $ chmod u=rw demo.txt $ ls -l demo.txt ``` ```output -rw-r--r-- 1 user user 0 Jun 5 10:13 demo.txt ``` 1 行目の `a=r` で全員を読み取りだけにします。2 行目の `u=rw` で所有者にだけ書き込みを足しています。 `=` は「今ある権限を消してから設定する」記号でした。だから 2 段階に分けても、途中の状態に引きずられません。 実機の Linux では、次のようにカンマで 1 行にまとめられます。この形なら `600` と同じ `rw-------` になります。 ```bash $ chmod a=,u=rw demo.txt ``` 本サイトの仮想ターミナルは 1 指定ずつの入力に対応しています。 ::: ::: tip 余裕があれば `chmod 755 demo.txt` と `chmod u=rwx` → `chmod go=rx` の 2 段階も試してみましょう。実機ならカンマでまとめた `chmod u=rwx,go=rx demo.txt` でも同じ結果になります。 ::: ## 7. 振り返り {#review} ::: dialogue @lina: 整理します。`r` が 4、`w` が 2、`x` が 1。相手ごとに足して 3 桁並べるんですね。 @linny: そのとおり。所有者・グループ・その他の順だよ。 @lina: `444` で自分まで書けなくなったのは、所有者の `w` を外していたからでした。 @linny: よく覚えていたね。書きかえる前に `ls -l` で今の権限を見る。この 1 手間で失敗はほとんど防げるよ。 ::: ::: danger **片づけには `rm` を使います。ここだけ扱いが違います** `rm` で消したファイルは、GUI と違ってゴミ箱に行きません。その場で完全に消えます。元には戻せません。 - `-r`:ディレクトリを中身ごと消します - `-i`:1 つずつ「消してよいか」を聞きます `-i` を付けると、対象ごとに確認が表示されます。消してよければ `y` を入力して `Enter` を押します。やめるときは `n` を入力します。 消す前に、必ず `ls` でパスと中身を目で確かめてください。 ```bash $ cd ~ $ ls ~/chmod-practice $ rm -ri ~/chmod-practice ``` 本サイトの仮想ターミナルは学習用です。ここで実行しても、あなたのパソコンのファイルは消えません。安心して試してください。 ::: ## 今日の 3 行まとめ {#three-lines} 1. `r=4` `w=2` `x=1` を足すと数値モードになる。並び順は所有者・グループ・その他 2. シンボリックモードは「誰に(`u`/`g`/`o`/`a`)・どうする(`+`/`-`/`=`)・何を(`r`/`w`/`x`)」の 3 点セット 3. 完成形が決まっているなら数値、今から足し引きするならシンボリックを選ぶ ## まとめ表 {#summary} | やりたいこと | 数値モード | シンボリックモード | | -------------- | -------------------- | ------------------------ | | 一般ファイル | `chmod 644 file` | `chmod u=rw,go=r file` | | 実行スクリプト | `chmod 755 file` | `chmod u=rwx,go=rx file` | | 実行権限を足す | (丸ごと指定が必要) | `chmod u+x file` | | 書き込みを外す | (丸ごと指定が必要) | `chmod go-w file` | | 秘密鍵 | `chmod 600 file` | `chmod a=,u=rw file` | シンボリックモード側のカンマ区切りは、実機の Linux 向けの書き方です。本サイトの仮想ターミナルでは 1 指定ずつ実行してください。 ::: tip **覚えておくべき 3 つのポイント** 1. **r=4 w=2 x=1**:足し算で数値モードが読める・書ける 2. **u/g/o/a と +/-/=**:シンボリックモードは「誰に・どうする・何を」 3. **完成形なら数値、足し引きならシンボリック**:目的で選ぶ ::: ## 次に読む {#next} - [chmod・chown の使い方(基礎)](/articles/tutorials/permissions-basics) - [umask 入門](/articles/tutorials/umask-basics) - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) # chown/chgrp 入門 - ファイル所有者を変更する Source: https://penguin-gym-linux.com/articles/tutorials/chown-chgrp-basics ## chown / chgrp でできること {#intro} `chown` はファイルの**所有ユーザー**を変更するコマンド、`chgrp` は**所有グループ**を変更するコマンドだ。`chown user:group` の形式で両方を一度に変更できる。 ::: tip **結論(実務の型)** - **所有ユーザーを変える** → `sudo chown user file` - **グループも一緒に変える** → `sudo chown user:group file` - **グループだけ変える** → `sudo chgrp group file` - **ディレクトリ配下を一括変更** → `sudo chown -R user:group dir/` - **変更前後に `ls -la` で確認する** ::: ## ls -l で現在の所有者を確認する {#check} 変更前に現在の所有者を把握する。`ls -l` 出力の3・4列目が所有ユーザーと所有グループだ。 ```bash $ ls -l /var/www/html/index.html ``` ```output -rw-r--r-- 1 root root 1024 Jun 1 10:00 /var/www/html/index.html ``` | 位置 | 値 | 意味 | | ----- | ------ | ------------ | | 3列目 | `root` | 所有ユーザー | | 4列目 | `root` | 所有グループ | ## chown の基本構文 {#chown} 所有ユーザーを変更するには `chown` を使う。自分以外のファイルを変更するには `sudo` が必要だ。 ### 所有ユーザーだけ変更する ```bash $ sudo chown www-data /var/www/html/index.html $ ls -l /var/www/html/index.html ``` ```output -rw-r--r-- 1 www-data root 1024 Jun 1 10:00 /var/www/html/index.html ``` ### 所有ユーザーとグループを同時に変更する `user:group` の形式で一度に両方変更できる。 ```bash $ sudo chown www-data:www-data /var/www/html/index.html $ ls -l /var/www/html/index.html ``` ```output -rw-r--r-- 1 www-data www-data 1024 Jun 1 10:00 /var/www/html/index.html ``` ### グループのみ変更する(chown の `:group` 記法) `:group` と書けば所有ユーザーはそのままでグループだけ変更できる。 ```bash $ sudo chown :developers /var/www/html/ ``` ::: tip コロン `:` の代わりにドット `.` を使う旧記法(`chown user.group`)も動作するが、現在はコロンが標準。 ::: ## chgrp の基本構文 {#chgrp} グループのみ変更する専用コマンドが `chgrp` だ。`chown :group` と同じ結果になる。 ```bash $ sudo chgrp www-data /var/www/html/index.html $ ls -l /var/www/html/index.html ``` ```output -rw-r--r-- 1 root www-data 1024 Jun 1 10:00 /var/www/html/index.html ``` `chown :group` と `chgrp group` は機能的に等価。グループ変更の意図を明示したい場合は `chgrp` を使うと可読性が上がる。 ## -R でディレクトリを再帰的に変更する {#recursive} ディレクトリ配下のすべてのファイル・サブディレクトリをまとめて変更するには `-R` を付ける。 ```bash $ sudo chown -R www-data:www-data /var/www/html/ $ ls -la /var/www/html/ ``` ```output drwxr-xr-x 2 www-data www-data 4096 Jun 1 10:00 . drwxr-xr-x 3 root root 4096 Jun 1 09:00 .. -rw-r--r-- 1 www-data www-data 1024 Jun 1 10:00 index.html -rw-r--r-- 1 www-data www-data 512 Jun 1 10:00 style.css ``` ::: warning `-R` は指定ディレクトリ配下の**すべて**に適用される。影響範囲が大きいため、実行前に `ls -la` でターゲットを確認すること。 ::: ## よくある使用パターン {#patterns} ### Web サーバーへのアプリデプロイ時 ```bash # Nginx / Apache の実行ユーザー(www-data)に所有権を渡す $ sudo chown -R www-data:www-data /var/www/myapp/ ``` ### アプリケーション専用ユーザーへの引き渡し ```bash $ sudo chown -R deploy:deploy /opt/myapp/ ``` ### sudo で作ったファイルを自分の所有に戻す `sudo` で作成したファイルは `root` 所有になる。自分の所有に戻すには: ```bash $ sudo chown $USER:$USER ~/myfile.txt ``` `$USER` は現在のログインユーザー名に自動展開される。 ## よくあるエラーと対処 {#errors} ### Operation not permitted ```output chown: changing ownership of 'file.txt': Operation not permitted ``` 原因: 所有者の変更は root のみ可能。`sudo` なしで実行すると発生する。 ```bash $ sudo chown user:group file.txt ``` ### invalid user / invalid group ```output chown: invalid user: 'webmaster' chgrp: invalid group: 'webteam' ``` 原因: 存在しないユーザーまたはグループを指定している。 ```bash # ユーザー一覧 $ getent passwd | cut -d: -f1 # グループ一覧 $ getent group | cut -d: -f1 ``` ## まとめ {#summary} | コマンド | 変更対象 | | ----------------------- | --------------------- | | `chown user file` | 所有ユーザーのみ | | `chown :group file` | 所有グループのみ | | `chown user:group file` | 所有ユーザー+グループ | | `chgrp group file` | 所有グループのみ | | `-R` オプション | ディレクトリを再帰 | ::: tip **確認の型** ```bash # 変更前確認 ls -la /path/to/target # 変更実行 sudo chown -R user:group /path/to/target # 変更後確認 ls -la /path/to/target ``` ::: ## 次に読む {#next} - [chmod でファイルの権限(rwx)を変更する](/articles/tutorials/permissions-basics) - [Permission denied エラーの解決方法](/articles/troubleshooting/permission-denied-fix) - [umask でデフォルト権限を制御する](/articles/tutorials/umask-basics) # chroot 入門 - ルートディレクトリを切り替えて環境を分離する Source: https://penguin-gym-linux.com/articles/tutorials/chroot-basics ## この記事で解決できること {#intro} - `chroot` で **プロセスから見えるルートディレクトリを切り替える** 仕組みが分かる - 「chroot 環境でコマンドが動かない」を **依存ライブラリの観点で切り分け** できる - live USB / レスキューモードからの **システム復旧の型** が身につく - chroot が **セキュリティ境界ではない** 理由と、代わりに何を使うべきかが分かる ::: tip **結論(実務の型)** - chroot は **ファイルシステムの見え方を切り替える** だけ。プロセス・ネットワークは隔離しない - 動かす **コマンド本体とその共有ライブラリ** を新ルート内に揃える必要がある - システム復旧では `/dev` `/proc` `/sys` を **bind mount で持ち込んでから** chroot する - 本物の隔離が必要なら chroot ではなく **コンテナ(namespace)** を使う ::: ::: warning **前提(対象環境)** - OS: Ubuntu / Debian 系(RHEL 系もコマンドは同一) - `chroot` 実行には **root 権限**(`CAP_SYS_CHROOT`)が必要 - `chroot` コマンドは coreutils 同梱(追加インストール不要) ::: ## chroot とは何か? {#what} > **結論**: chroot は、あるプロセスとその子プロセスから見える「ルートディレクトリ `/`」を指定したディレクトリに付け替えるコマンド。そのプロセスは新ルートの外にあるファイルへアクセスできなくなる。 通常、すべてのプロセスはシステム全体のルート `/` を起点にファイルを辿る。`chroot /mnt/newroot` を実行すると、その配下のプロセスにとって `/mnt/newroot` が新しい `/` になる。 ```bash $ sudo chroot /mnt/newroot ``` この瞬間、chroot されたシェルから見える `/etc/passwd` は、実体としては `/mnt/newroot/etc/passwd` を指す。新ルートの外側(ホスト本来の `/home` など)は **パスとして表現できなくなる**。これが「環境を分離する」と表現される所以である。 ::: tip chroot された環境は慣習的に **chroot jail(牢獄)** と呼ばれる。プロセスがその「牢獄」の外のファイルツリーを見られないことに由来する。 ::: ## なぜ chroot を使うのか? {#why} > **結論**: 主な用途はシステム復旧・クリーンなビルド/テスト環境・特定環境への閉じ込めの3つ。「本番とは独立したファイルシステムでコマンドを実行したい」場面で使う。 代表的なユースケースは次のとおり。 | 用途 | 具体例 | | ------------ | ------------------------------------------------------------------------------------------ | | システム復旧 | 起動しなくなった OS を live USB から `chroot` して `grub` 再インストール・パスワード再設定 | | クリーン環境 | パッケージビルドを汚れていない最小環境で実行(`debootstrap` で作成した root など) | | 隔離実行 | 公開 FTP/SFTP をホームディレクトリ配下に閉じ込める(`sshd` の `ChrootDirectory`) | | 検証 | 別ディストリ・別バージョンのユーザーランドを既存カーネル上で動かす | ::: warning chroot は **カーネルを切り替えない**。新ルート内に別バージョンのライブラリやコマンドを置けても、動いているカーネルはホストのものである。 ::: ## どう chroot を実行するのか? {#basic} > **結論**: 構文は `chroot 新ルート [コマンド]`。コマンドを省略するとそのユーザーのシェル(`$SHELL` または `/bin/sh`)を対話起動する。実行には root 権限が要る。 基本構文: ```bash $ sudo chroot NEWROOT [COMMAND [ARG...]] ``` コマンドを省略した場合: ```bash # 新ルート内のシェルを対話起動する $ sudo chroot /mnt/newroot ``` コマンドを明示する場合: ```bash # 新ルート内の /bin/ls を実行して終了する $ sudo chroot /mnt/newroot /bin/ls -l / ``` ::: warning `COMMAND` に指定するパスは **新ルートから見た絶対パス**。`chroot /mnt/newroot /bin/bash` は `/mnt/newroot/bin/bash` を実行する。ホスト側のパスではない点に注意。 ::: ## chroot 環境に必要なものは? {#deps} > **結論**: 動かしたいコマンドの実体に加え、それが依存する共有ライブラリ(`.so`)を新ルート内に揃える必要がある。揃っていないと「No such file or directory」で失敗する。 最小の落とし穴がこれである。`chroot /mnt/newroot /bin/bash` を実行しても、`/mnt/newroot/bin/bash` が存在しなければ当然動かない。さらに **bash が依存する共有ライブラリ** も新ルート内に必要になる。 依存ライブラリは `ldd` で確認する。 ```bash $ ldd /bin/bash ``` ```output linux-vdso.so.1 (0x00007fff...) libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007f...) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f...) /lib64/ld-linux-x86-64.so.2 (0x00007f...) ``` 表示された `.so` ファイルを、新ルート内の **同じパス** にコピーすれば bash が動く。手作業での最小 jail 構築例: ```bash $ NEWROOT=/mnt/jail $ sudo mkdir -p $NEWROOT/{bin,lib,lib64} $ sudo cp /bin/bash $NEWROOT/bin/ # ldd の出力を見て必要な .so を同じ階層へコピー $ sudo cp /lib/x86_64-linux-gnu/{libtinfo.so.6,libc.so.6} $NEWROOT/lib/ $ sudo cp /lib64/ld-linux-x86-64.so.2 $NEWROOT/lib64/ $ sudo chroot $NEWROOT /bin/bash ``` ::: tip 実用的な chroot 環境は手作業ではなく `debootstrap`(Debian/Ubuntu)や `dnf --installroot`(RHEL 系)で **依存込みの完全なユーザーランドを一括展開** して作るのが普通。手動コピーは仕組みの理解用と捉えるとよい。 ::: ## システム復旧での chroot 実践 {#recovery} > **結論**: 起動しない OS の修復では、対象ディスクをマウントし、`/dev` `/proc` `/sys` を bind mount で持ち込んでから chroot する。これでホスト稼働中の OS をあたかも通常起動したかのように操作できる。 live USB やレスキューモードから本来の OS に入って修復する手順。`/dev/sda1` をルートパーティションとする。 ```bash # 1. 対象のルートパーティションをマウント $ sudo mount /dev/sda1 /mnt # 2. 仮想ファイルシステムを bind mount で持ち込む $ sudo mount --bind /dev /mnt/dev $ sudo mount --bind /proc /mnt/proc $ sudo mount --bind /sys /mnt/sys # 3. chroot で本来の OS に入る $ sudo chroot /mnt # 4. ここからは通常の OS 操作(例: GRUB 再インストール) # grub-install /dev/sda && update-grub ``` ::: warning `/dev` `/proc` `/sys` を持ち込まないと、`grub-install` やパッケージ操作など **カーネルやデバイス情報を参照するコマンドが失敗** する。復旧 chroot ではこの bind mount がほぼ必須。 ::: 作業後は **chroot を抜けてからアンマウント** する。順序を間違えると `target is busy` になる。 ```bash # chroot を抜ける # exit $ sudo umount /mnt/sys /mnt/proc /mnt/dev $ sudo umount /mnt ``` ::: tip `/dev/pts`(疑似端末)や `/run` が必要なケースもある。`arch-chroot`(Arch Linux 提供だが他環境でも利用可)はこれらの bind mount を自動でやってくれる便利ラッパー。 ::: ## chroot はセキュリティ境界になるのか? {#security} > **結論**: ならない。chroot はファイルシステムの見え方を変えるだけで、root 権限を持つプロセスは標準的な手法で chroot の外へ脱出できる。隔離目的には namespace ベースのコンテナを使うべき。 chroot を「サンドボックス」と誤解すると危険である。chroot 環境内で **root 権限を持つプロセス** は、二重 chroot などの古典的テクニックで容易に脱獄(chroot escape)できる。これは設計上の制約であり、バグではない。 chroot が隔離 **しない** もの: - プロセス空間(`ps` で外のプロセスが見え、`kill` できる) - ネットワーク(同じネットワークスタックを共有) - ユーザー / 権限(chroot 内の root はホストの root と同一) - カーネル(共有) ::: danger 信頼できないコードの隔離・マルチテナント分離に chroot を使ってはいけない。本物の隔離が必要なら **namespace + cgroup(コンテナ)** や **仮想マシン** を使う。chroot 内の root を非 root に落とす(`capsh --drop` 等)のは緩和策にはなるが、それでも完全な境界にはならない。 ::: ::: tip 非特権ユーザー向けの安全な閉じ込めとしては、`sshd` の `ChrootDirectory`(SFTP 専用閉じ込め)や、`systemd` サービスの `RootDirectory=` + `PrivateDevices=` などの組み合わせが現実的な選択肢になる。 ::: ## よくあるエラーと対処 {#errors} > **結論**: 失敗の大半は「依存ライブラリ不足」「bind mount 漏れ」「root 権限不足」の3つ。エラーメッセージから原因を切り分ける。 ### chroot: failed to run command '/bin/bash': No such file or directory bash 本体は存在するのにこのエラーが出る場合、**依存する共有ライブラリ(ローダ含む)が新ルート内に無い** ことがほとんど。`ldd /bin/bash` の出力を新ルートへ揃える([依存セクション](#deps)参照)。 ```bash $ sudo chroot /mnt/jail ldd /bin/bash # 解決済みかは外から ldd で確認できないため $ ldd /bin/bash # ホスト側で必要 .so を洗い出す ``` ### chroot: cannot change root directory to '/mnt': Operation not permitted root 権限が不足している。`sudo` を付ける。コンテナ内などで `CAP_SYS_CHROOT` が剥奪されている場合も同じエラーになる。 ### chroot 後に「command not found」が多発する `PATH` が新ルート内の構成と合っていない、または必要なコマンド群が未配置。最小環境では `ls` すら無いことがある。`debootstrap` 等で完全なユーザーランドを展開する。 ### umount で target is busy chroot シェルを抜ける前にアンマウントしようとしている。先に chroot を `exit` し、その後 `/sys` `/proc` `/dev` `/mnt` の順でアンマウントする。 ::: tip **コピペ用: 復旧 chroot の安全テンプレ** ```bash # 入る sudo mount /dev/sda1 /mnt sudo mount --bind /dev /mnt/dev sudo mount --bind /proc /mnt/proc sudo mount --bind /sys /mnt/sys sudo chroot /mnt # 作業後、chroot を exit してから sudo umount /mnt/sys /mnt/proc /mnt/dev sudo umount /mnt ``` ::: ## 次に読む {#next} - [mount と fstab の基本](/articles/tutorials/mount-fstab-basics) - [パーミッションの基本](/articles/tutorials/permissions-basics) - [SELinux 入門](/articles/tutorials/selinux-introduction) # column/pr/fmt - 整形コマンド 3 兄弟 Source: https://penguin-gym-linux.com/articles/tutorials/column-pr-fmt ## この 3 コマンドを一言で {#summary} `column` は列を揃える、`pr` はページを割り付ける、`fmt` は段落を折り返す——どれも「テキストを人間が読みやすい形に整える」コマンドだ。grep や awk でデータを絞り込んだ後、最後の仕上げに使うことが多い。 ::: tip **結論:使い分けの型** - **スペース / タブ区切りデータを表形式に** → `column -t` - **長いリストを複数段組で表示** → `pr -N -t` - **長い行を指定幅で折り返す** → `fmt -w 72` ::: ## column - なぜ列が揃わないのか? {#column} スペースやタブ区切りのデータをそのまま `cat` すると列がずれる。`column -t` は各フィールドの最大幅を計算し、スペースで埋めて整列させる。 ### 基本:スペース区切りを表形式に ```bash echo -e "name age city\nAlice 30 Tokyo\nBob 25 Osaka" | column -t ``` ```output name age city Alice 30 Tokyo Bob 25 Osaka ``` ### CSV / TSV の整形 `-s` でセパレータを指定する。 ```bash head -5 /etc/passwd | column -t -s: ``` ```output root x 0 0 root /root /bin/bash daemon x 1 1 daemon /usr/sbin /usr/sbin/nologin bin x 2 2 bin /bin /usr/sbin/nologin sys x 3 3 sys /dev /usr/sbin/nologin sync x 4 65534 sync /bin /bin/sync ``` ::: warning `column -t -s` の組み合わせは Linux(util-linux 版)専用。macOS(BSD 版)では `-s` の動作が異なるか未サポートの場合がある。本記事は Linux を前提とする。 ::: ### 複数カラムに並べる `-c` でページ幅(文字数)を指定し、複数段組で表示する。 ```bash ls /etc | column -c 80 ``` ```output adduser.conf dhcp group magic protocols alternatives environment hostname modprobe.d resolv.conf apt fstab lsb-release os-release rsyslog.conf ... ``` ## pr - なぜリストが読みにくいのか? {#pr} `ls` の出力を縦一列で眺めると、件数が多い場合に画面が縦長になりすぎる。`pr` は複数段組にして横に並べることで、一画面に収まる量を増やす。もともとはプリンター用のページ割り付けコマンドだが、端末での段組表示としても有用だ。 ### 複数段組 `-N` で段数を指定する。 ```bash ls /usr/bin | pr -3 -t ``` ```output [ addpart apt-add-repository aa-enabled addr2line apt-cache aa-exec appstreamcli apt-cdrom ... ``` - `-3`:3 段組 - `-t`:ヘッダとフッタを省略(ファイル名・日付・ページ番号を出力しない) ::: tip `-t` を省略するとファイル名・日付・ページ番号のヘッダが付く。端末で使うときはほぼ必ず `-t` を付けると覚えておくと楽。 ::: ### 2 ファイルを横に並べて比較 `-m` で複数ファイルをマージして横並びに表示できる。 ```bash pr -m -t file1.txt file2.txt ``` `diff` のようにマーカーは付かないが、2 つのテキストを目視で比較するときに手軽。 ### ページ幅・行数の指定 ```bash pr -3 -t -w 120 -l 60 large_list.txt ``` - `-w 120`:ページ幅 120 文字 - `-l 60`:ページあたり 60 行 ## fmt - なぜ行が長くなりすぎるのか? {#fmt} コピー&ペーストや外部ファイルからのテキストは、行末が統一されていないことがある。`fmt` は段落を認識し、指定した文字数で折り返し直す。 ### 基本:折り返し幅を指定して整形 ```bash cat long_text.txt | fmt -w 72 ``` デフォルトは 75 文字前後(実装依存)。`-w` で明示するのが確実。 ### 段落を維持しながら折り返す 連続する空行が段落区切りとして扱われ、段落単位で折り返す。 ```bash cat README.txt | fmt -w 80 ``` ```output This is the first paragraph of the document. It explains what the program does and how to use it effectively. This is the second paragraph. It provides additional details about the configuration options available. ``` ::: warning `fmt` は**段落内のテキストを結合して再折り返す**。改行を無視して一段落を一つの塊として扱うため、手動で入れた改行は消える。「改行位置だけ変えたい」のか「段落ごと再整形したい」のかを意識すること。コードブロックや箇条書きを含むファイルには使わないほうが安全。 ::: ### `-u` オプション(不均一モード) `-u` を付けると、複数スペースを単一スペースに圧縮する。コピー時に混入した余分なスペースを取り除くのに便利。 ```bash echo "This has extra spaces" | fmt -u ``` ```output This has extra spaces ``` ## 3 コマンドの使い分け {#when-to-use} | 用途 | コマンド | | ----------------------------------- | -------------------- | | スペース/タブ区切りデータを表形式に | `column -t` | | CSV/TSV を整列して確認 | `column -t -s,` | | ファイル名一覧を複数段組で表示 | `ls \| column -c 80` | | 長いリストを 2〜3 段組に並べる | `pr -N -t` | | 2 つのテキストを横並びで比較 | `pr -m -t` | | 長い行を折り返す(文書整形) | `fmt -w 72` | | 余分なスペースを圧縮して整形 | `fmt -u` | ::: highlight **パイプでつなぐパターン** これらのコマンドはパイプの末尾で「出力を読みやすくする」役割を果たす。 ```bash # CSV の特定列を切り出して表形式で確認 cut -d, -f1,3,5 data.csv | column -t -s, # 長いファイル一覧を 3 段組で表示 find /etc -maxdepth 1 -name "*.conf" | pr -3 -t # コミットログを指定幅で折り返して確認 git log --oneline -20 | fmt -w 72 ``` ::: ## 次に読む {#next} - [cut/paste/tr 入門 - 列の抽出と文字変換](/articles/tutorials/cut-paste-tr-basics) - [sort と uniq の使い方 - データを並べ替えて重複を削る](/articles/tutorials/sort-uniq-basics) - [xargs 実践活用 - 標準入力をコマンド引数に変換する](/articles/tutorials/xargs-practical) # comm / join 入門 - 2つのファイルを比較・結合する Source: https://penguin-gym-linux.com/articles/tutorials/comm-join-basics ## この記事で解決できること {#intro} - `comm` で 2 ファイルの **共通行・差分** を 3 列で取り出せる - `join` で共通キーをもとに 2 ファイルを **横方向に結合** できる - 「行が出てこない」「`not sorted` 警告」などの **ソート起因の事故** を防げる ::: tip **結論(使い分け)** - 行単位で **共通 / 差分** を知りたい → `comm` - キー列で **2 表を横に結合** したい(SQL の JOIN 相当)→ `join` - どちらも **入力がソート済みであること** が絶対条件 ::: ::: warning **前提(対象環境)** - GNU coreutils(Ubuntu / 一般的な Linux ディストリ) - `comm` / `join` は coreutils 同梱。追加インストール不要 ::: ## comm とは? 何ができるのか {#comm} > **結論**: `comm` はソート済み 2 ファイルを 1 行ずつ突き合わせ、「左だけ / 右だけ / 共通」を 3 列で出力するコマンド。 `comm` は 2 つの **ソート済み** ファイルを比較し、結果を 3 列に振り分ける。 - 1 列目: file1 だけにある行 - 2 列目: file2 だけにある行 - 3 列目: 両方にある行(共通行) 例として 2 つのファイルを用意する。 ```bash $ cat a.txt apple banana cherry $ cat b.txt banana cherry date ``` ```bash $ comm a.txt b.txt ``` ```output apple banana cherry date ``` 列の位置はタブのインデントで表される。`apple` は左だけ(1 列目)、`banana` と `cherry` は両方(3 列目・タブ 2 個)、`date` は右だけ(2 列目・タブ 1 個)。 ## なぜ comm はソートが必要なのか {#comm-sort} > **結論**: `comm` は前から 1 行ずつ照合する単純なマージ方式のため、入力が昇順に並んでいないと共通行を取りこぼす。 `comm` は両ファイルを先頭から同時に読み進める。並びが崩れていると「同じ行」を正しく検出できず、結果が壊れる。未ソートだと次の警告が出る。 ```output comm: file 1 is not in sorted order ``` 事前に `sort` を通すのが鉄則。プロセス置換を使えば一時ファイル不要で書ける。 ```bash $ comm <(sort a.txt) <(sort b.txt) ``` ::: warning `sort` の照合順は **ロケール依存**。`comm` と `sort` で並び順がずれると誤動作するため、迷ったら `LC_ALL=C sort` で固定すると安定する。 ::: ## comm の列を絞り込むには? {#comm-columns} > **結論**: `-1` `-2` `-3` で対応する列を抑制する。番号は「消す列」を指す点に注意。 オプションは表示したい列ではなく **抑制する列** を指定する。 | やりたいこと | コマンド | 残る列 | | -------------------- | -------------- | --------- | | 共通行だけ | `comm -12 a b` | 3 列目 | | 差分だけ(共通以外) | `comm -3 a b` | 1・2 列目 | | file1 だけにある行 | `comm -23 a b` | 1 列目 | | file2 だけにある行 | `comm -13 a b` | 2 列目 | ```bash $ comm -12 a.txt b.txt banana cherry ``` ```bash $ comm -23 a.txt b.txt apple ``` ::: tip **覚え方**: 番号は「いらない列を捨てる」。共通行だけ欲しいなら 1 列目と 2 列目を捨てて `-12`。 ::: ## join とは? comm と何が違うのか {#join} > **結論**: `join` は共通キー列をもとに 2 ファイルの行を横方向に連結する。SQL の INNER JOIN に相当する。 `comm` が行全体の一致を見るのに対し、`join` は **指定したキー列** が一致する行同士を 1 行に結合する。デフォルトのキーは各行の **1 番目のフィールド**。 ```bash $ cat users.txt 1 alice 2 bob 3 carol $ cat depts.txt 1 sales 2 engineering 4 marketing ``` ```bash $ join users.txt depts.txt ``` ```output 1 alice sales 2 bob engineering ``` キー `1` と `2` は両方に存在するため結合される。`3 carol` と `4 marketing` は相手がいないため出力されない(内部結合)。 ::: warning `join` も **キー列でソート済み** であることが前提。未ソートだと `join: ... is not sorted` 警告とともに結果が欠落する。`comm` と同じく `sort` を先に通すこと。 ::: ## join のフィールドと区切り文字を指定するには? {#join-options} > **結論**: `-t` で区切り文字、`-1` / `-2` で各ファイルのキー列番号、`-o` で出力列を指定する。 CSV のように区切り文字が異なる場合や、キーが 1 列目でない場合に調整する。 ```bash $ cat users.csv 1,alice,tokyo 2,bob,osaka $ cat depts.csv sales,1 engineering,2 ``` `users.csv` はキーが 1 列目、`depts.csv` はキーが 2 列目。区切りはカンマ。 ```bash $ join -t, -1 1 -2 2 users.csv depts.csv ``` ```output 1,alice,tokyo,sales 2,bob,osaka,engineering ``` - `-t,`: 区切り文字をカンマに変更 - `-1 1`: file1 のキーは 1 列目 - `-2 2`: file2 のキーは 2 列目 ::: tip `-o` で出力列を明示できる。`-o 1.1,1.2,2.1` は「file1 の 1・2 列目と file2 の 1 列目」の意味。`.` の形式で並べる。 ::: ## マッチしない行も残したい(外部結合) {#join-outer} > **結論**: `-a` で片側の非マッチ行も出力する。`-a 1` は左外部結合、`-a 1 -a 2` は完全外部結合に相当する。 内部結合では相手のいない行は捨てられる。残したい場合は `-a` を使う。 ```bash $ join -a 1 users.txt depts.txt ``` ```output 1 alice sales 2 bob engineering 3 carol ``` `3 carol` は `depts.txt` に相手がいないが、`-a 1` により欠損したまま出力される。空欄を埋めたい場合は `-e` と `-o` を組み合わせる。 ```bash $ join -a 1 -e '-' -o '1.1,1.2,2.2' users.txt depts.txt ``` ```output 1 alice sales 2 bob engineering 3 carol - ``` `-e '-'` は欠損フィールドを `-` で埋め、`-o` で出力列を固定している。 ## comm と join の使い分けまとめ {#summary} > **結論**: 行集合の差分は `comm`、キー結合は `join`。いずれもソート済み入力が前提という点を必ず押さえる。 | やりたいこと | コマンド | | ------------------------------ | -------- | | 2 ファイルの共通行・差分を知る | comm | | 共通キーで 2 表を横に結合する | join | | 重複排除・並べ替え(前処理) | sort | ::: warning **やってはいけないこと** - 未ソートのまま `comm` / `join` に渡す(結果が静かに欠落する) - `comm` と `sort` でロケールを混在させる(並び順のずれで誤動作) - `join` のキー列指定(`-1` / `-2`)を忘れて 1 列目固定のまま実行する ::: - [sort / uniq でソート・重複排除を学ぶ](/articles/tutorials/sort-uniq-basics) - [cut / paste / tr で列を抽出・結合する](/articles/tutorials/cut-paste-tr-basics) - [diff / patch で差分を取得・適用する](/articles/tutorials/diff-patch-basics) # 「command not found」の対処法 - コマンドが見つからないときの解決策 Source: https://penguin-gym-linux.com/articles/tutorials/command-not-found ## この記事で解決できること {#intro} - `command not found` の **原因を 30 秒で切り分け** られる - `PATH` / `hash` / `sudo` / `cron` の **典型パターン** を把握できる - 「インストールしたはずなのに見つからない」を **再発しない形で潰せる** ::: tip **結論(実務の型)** 1. `command -v <名前>` で存在確認 2. `echo $PATH` で探索パス確認 3. **パッケージ未導入 / PATH 不足 / hash キャッシュ / sudo secure_path / 入力ミス** のどれか ::: ::: warning **前提(対象環境)** - OS:Ubuntu / Debian 系(apt)。RHEL 系(dnf / yum)も併記 - シェル:bash / zsh ::: ## 1. 原因はほぼこの 5 つ {#causes} > **結論**: 原因は未インストール・PATH 不足・hash キャッシュ・sudo secure_path・入力ミスの 5 つにほぼ収束する。 `command not found` の事故原因は次のいずれかにほぼ収束する。 | # | 原因 | 切り分けキー | | --- | -------------------------------------- | ------------------------------------------- | | 1 | コマンドが**インストールされていない** | `apt-cache search` / `dnf provides` | | 2 | `PATH` に実行ファイルがない | `echo $PATH` / `which -a` | | 3 | シェルの `hash` キャッシュが古い | `hash -r` | | 4 | `sudo` の `secure_path` 制限 | `sudo -V` / `/etc/sudoers` の `secure_path` | | 5 | 入力ミス(大文字小文字 / typo) | `type` / `compgen -c` | ::: tip 8 割は **②PATH** か **③hash** が真因。先にこの 2 つを潰す。 ::: ## 2. 切り分け手順(30 秒テンプレ) {#diagnose} > **結論**: `command -v` で存在確認、`echo $PATH` で探索パス確認、未導入なら提供パッケージを探す順で詰める。 ### 2-1. まず存在確認 ```bash $ command -v ansible ``` - 出力あり → コマンドは見えている。実行できない場合は権限の問題(`Permission denied` 系を参照) - 出力なし → 2-2 へ ::: tip `command -v` は POSIX 標準。`which` はディストリ実装依存(Debian の `which` は単なる exit ステータス返却)で挙動が分かれるため、スクリプト内では `command -v` を使う。`type` は alias / function / builtin / file を区別表示できるので調査向き。 ::: ### 2-2. PATH を表示 ```bash $ echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ``` 期待するインストール先が **PATH に含まれているか** を確認する。代表例: - `~/.local/bin`(`pip install --user` / `pipx`) - `~/bin`(Ubuntu の `~/.profile` で自動追加。ただし bash **ログインシェル**のみ) - `/usr/local/bin`(手動ビルド系) - `/snap/bin`(snap 経由) ### 2-3. パッケージから探す そもそも未インストールなら、提供パッケージを探す。 ```bash # Ubuntu / Debian $ apt-cache search ^ansible$ $ /usr/lib/command-not-found ansible # Ubuntu の親切ヒント ``` ```bash # RHEL / Fedora $ dnf provides '*/ansible' ``` ## 3. ケース別対処 {#solutions} > **結論**: インストール直後・pip/npm 導入・typo・シェル切替など、ケースごとに原因と対処が決まっている。 ### 3-1. インストール直後なのに見つからない シェルが古い実行パスを `hash` でキャッシュしている。 ```bash $ hash -r # bash / zsh いずれも有効 $ rehash # zsh の慣用形 ``` これで直らない場合、PATH に新パッケージのバイナリディレクトリが入っていないか、別ユーザー(root / 他)でインストールしたかのどちらか。 ### 3-2. `pip install --user` で入れたコマンドが見つからない インストール先は `~/.local/bin`。PATH に追加する。 ```bash $ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc $ source ~/.bashrc ``` ::: tip Python の CLI を入れるなら **pipx** を推奨。`pipx ensurepath` で PATH 設定までやってくれるため、`pip install --user` よりも事故が少ない。 ::: ### 3-3. `npm install -g` のコマンドが見つからない `npm config get prefix` でインストール先を確認する。`/usr/local` ならパーミッションで失敗している可能性、`~/.npm-global` 等の独自プレフィックスなら `bin` を PATH に追加する。 ```bash $ npm config get prefix $ ls "$(npm config get prefix)/bin" ``` ### 3-4. 大文字小文字 / typo Linux はファイル名の **大文字小文字を区別する**。 ```bash $ Vim file.txt # → command not found $ vim file.txt # OK ``` 候補を一覧: ```bash $ compgen -c vim # vim で始まるコマンドを列挙 ``` ### 3-5. シェルを変えたら見つからない `bash` → `zsh` のような切替時、PATH を書く場所が変わる。 | シェル | ログインシェル | 対話シェル | | ------ | -------------------------------- | ----------- | | bash | `~/.bash_profile` → `~/.profile` | `~/.bashrc` | | zsh | `~/.zprofile` | `~/.zshrc` | ::: warning `~/.profile` は POSIX シェルが読む共通設定。`~/.bashrc` は **bash 専用** で zsh からは読まれない。zsh に乗り換えたら `~/.zshrc` 側にも PATH 設定を移すこと。 ::: ## 4. PATH を永続化する正しい方法 {#path-persist} > **結論**: PATH は `$PATH` を保持して追記し、上書きは厳禁。システム全体は `/etc/profile.d/*.sh` を使う。 ### 4-1. 推奨パターン ```bash # ~/.bashrc または ~/.zshrc の末尾 export PATH="$HOME/.local/bin:$PATH" ``` - **既存 PATH の前後どちらに置くか** で優先順位が変わる - 自作スクリプトを優先したいなら **前** - システムコマンドを優先したいなら **後** ::: danger **やってはいけない:PATH の上書き** ```bash # NG: 既存 PATH を消し飛ばす export PATH="$HOME/.local/bin" ``` `ls` も `cat` も `sudo` も使えなくなる典型事故。必ず `$PATH` を保持して **追加** すること。 ::: ### 4-2. システムワイドに通したい場合 `/etc/profile.d/*.sh` を使う。`/etc/environment` は変数代入のみ(シェル構文不可)。 ```bash # /etc/profile.d/myapp.sh export PATH="/opt/myapp/bin:$PATH" ``` ## 5. sudo / cron で見つからないとき {#privilege} > **結論**: sudo は secure_path、cron は最小 PATH で動くため、フルパス指定か PATH 明示で解決する。 ### 5-1. sudo で command not found `sudo` 実行時の PATH は `/etc/sudoers` の `secure_path` で **上書き** される。 ```bash $ sudo -V | grep -i path Value to override user's $PATH with: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ``` 対策: ```bash # (1) フルパスで叩く(最も確実) $ sudo /home/user/.local/bin/myscript # (2) sudo -E で環境変数を引き継ぐ(secure_path 設定により無視される場合あり) $ sudo -E env "PATH=$PATH" myscript # (3) 恒久対応: visudo で secure_path を編集 $ sudo visudo # Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/user/.local/bin" ``` ::: warning `secure_path` は権限昇格時の PATH 注入攻撃を防ぐためのセキュリティ機構。安易に広げず、必要なディレクトリだけを追加する。 ::: ### 5-2. cron で command not found cron は **最小限の PATH** で起動する。 ```bash $ env -i sh -c 'echo $PATH' /usr/bin:/bin ``` 対策(推奨順): ```cron # (1) crontab 内で PATH を明示 PATH=/usr/local/bin:/usr/bin:/bin:/home/user/.local/bin * * * * * myscript # (2) スクリプト内でフルパスを使う * * * * * /home/user/.local/bin/myscript ``` ::: tip cron トラブルの 7 割は PATH。次点は標準出力 / 標準エラーの未捕捉。`>> /tmp/cron.log 2>&1` を必ず付けて原因を可視化する。 ::: ## 6. コピペ用:診断テンプレート {#template} > **結論**: 存在確認・PATH 表示・hash クリア・パッケージ検索・sudo 確認を並べた診断テンプレをコピペで使える。 ```bash # 1. 存在確認 command -v ansible # 2. PATH 表示 echo $PATH # 3. hash クリア(インストール直後の鉄板) hash -r command -v ansible # 4. パッケージ検索 apt-cache search ^ansible$ # Debian / Ubuntu dnf provides '*/ansible' # RHEL / Fedora # 5. sudo / cron 経由の確認 sudo command -v ansible sudo -V | grep -i path ``` ::: warning **やってはいけないこと** - `PATH=` で既存 PATH を消す - `~/.bash_profile` と `~/.profile` の両方に同じ PATH を書く(一方が無視される) - `sudo` 失敗時に脊髄反射で `chmod 777` ::: ## 次に読む {#next} - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) - [シェルスクリプトの書き方入門](/articles/tutorials/shell-scripting-basics) - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) # コマンド置換 (command substitution) 入門 — $(...) でコマンド結果を変数に取り込む Source: https://penguin-gym-linux.com/articles/tutorials/command-substitution ## この記事で学べること {#intro} - **コマンド置換** という考え方がわかります - `$(コマンド)` で **実行結果を変数に取り込む** 方法がわかります - 古い書き方である **バッククォート** `` `...` `` との違いがわかります - なぜ `"$(...)"` と **クォートで囲む** のかがわかります ::: tip **言葉の整理** - **変数**:値を入れておく名前付きの箱です。名前を書けば、中身を取り出せます。 - **コマンド置換**:コマンドの実行結果を、その場の文字として埋め込む仕組みです。「コマンド展開」「Capturing Output」と呼ばれることもあります。 - **クォート**:引用符のことです。`"..."` をダブルクォート、`'...'` をシングルクォートと呼びます。 なお、この記事で使う `date` / `pwd` / `whoami` / `ls` は、すべて表示だけを行うコマンドです。ファイルを消したり書きかえたりはしません。何度打ち間違えても、あなたのパソコンは壊れません。 ::: ::: tip **結論(先に覚えるべき型)** - コマンドの結果を **変数に入れる** → `VAR=$(コマンド)` - 結果を **文章に埋め込む** → `echo "今日は $(date)"` - 迷ったら **`"$(...)"` とダブルクォートで囲む** ::: ## 1. コマンド置換とは何か? {#what} > **結論**: コマンド置換はコマンドの実行結果を、その場の文字列として埋め込む仕組み。`$(...)` で書く。 ::: dialogue @lina: 先輩、`date` を実行すると今日の日付が出ますよね。あの結果を別のコマンドの中で使いたいです。どうすればいいでしょうか。 @linny: それがまさに **コマンド置換(command substitution)** の出番だよ。`$(コマンド)` と書くと、その部分が **コマンドの実行結果に置き換わる** んだ。 @lina: 置き換わる、というのはどういうことでしょうか。 @linny: 手紙の穴埋めを想像してみて。「今日は ___ です」の空欄を、先に調べた日付で埋めてから読み上げる感じだよ。 @linny: `$(date)` と書くと、シェルはまず `date` を実行する。その出力で `$(date)` を丸ごと差し替える。そのうえでコマンド全体を実行するんだ。 ::: ```bash $ echo "今日は $(date) です" ``` ```output 今日は 2026年 6月 5日 金曜日 12:00:00 JST です ``` ::: highlight **置き換えのイメージ** ``` echo "今日は $(date) です" ↓ date を先に実行 echo "今日は 2026年 6月 5日 ... です" ``` `$(...)` の中身が **実行結果という名の文字列** に化けてから、外側のコマンドが動く。 ::: ## 2. 結果を変数に取り込む {#variable} > **結論**: `VAR=$(コマンド)` でコマンドの出力を変数に保存できる。`=` の前後にスペースを入れないのが鉄則。 ### 2-1. 基本形 ```bash $ today=$(date +%Y-%m-%d) $ echo "$today" ``` ```output 2026-06-05 ``` ::: dialogue @lina: `today` という変数に日付が入りましたね! これは便利。 @linny: そう。一度変数に入れておけば、後で何度でも使い回せる。ログのファイル名にしたり、メッセージに埋め込んだりね。 ::: ```bash $ logfile="backup-$today.log" $ echo "$logfile" ``` ```output backup-2026-06-05.log ``` ### 2-2. `=` の前後にスペースを入れない ::: warning 変数の代入では、**`=` の前後にスペースを入れてはいけません。** ```bash today = $(date) # NG: today というコマンドを探しに行ってエラー today=$(date) # OK ``` スペースを入れると、シェルは `today` を **コマンド名** だと解釈します。そのため `command not found` になります。 ::: ## 3. バッククォートとの違い {#backtick} > **結論**: 古い書き方の `` `...` `` でも同じ動作。しかし入れ子や可読性で不利なため、今は `$(...)` を使う。 ::: dialogue @lina: ネットで調べると、`` `date` `` のようにバッククォートで囲んでいる記事もありました。あれは何でしょうか。 @linny: それは **古いコマンド置換の書き方** だよ。動作は `$(date)` とほぼ同じ。ただ今は `$(...)` を使うのが推奨なんだ。 @lina: どうして新しいほうがよいのでしょうか。 ::: ```bash # 古い書き方(バッククォート) $ echo "今日は `date` です" # 新しい書き方(推奨) $ echo "今日は $(date) です" ``` ::: highlight **`$(...)` が推奨される理由** | 観点 | バッククォート `` `...` `` | `$(...)` | | ------------ | -------------------------- | -------------- | | 入れ子 | 書きにくい(要エスケープ) | そのまま書ける | | 見やすさ | 縦棒と紛らわしい | 括弧で明確 | | 引用符の扱い | クセがある | 素直 | ::: ::: tip バッククォートは、古い記事を読むときに理解できれば十分です。**自分で書くときは `$(...)` に統一しましょう。** ::: ## 4. 入れ子(ネスト)で組み合わせる {#nest} > **結論**: `$(...)` は中にさらに `$(...)` を書ける。内側から順に実行され、結果が外側へ渡る。 ```bash $ echo "$(basename $(pwd))" ``` ```output myproject ``` ::: dialogue @lina: `$(...)` の中に、また `$(...)` がありますね。これはどう動くのでしょうか。 @linny: **内側から外側へ** 順番に処理されるよ。まず `$(pwd)` が現在のパスになる。たとえば `/home/user/myproject` だね。 @linny: それを `basename` が受け取って、末尾の `myproject` だけを取り出すんだ。`basename` はパスから最後の名前だけを取り出すコマンドだよ。 ::: ::: highlight **内側から外側への流れ** ``` basename $(pwd) ↓ pwd を実行 basename /home/user/myproject ↓ basename を実行 myproject ``` バッククォートの場合、内側を `` \` `` と書き直す必要があります。この書き直しをエスケープと呼びます。手間がかかります。 `$(...)` なら **そのまま入れ子にできます**。これが大きな強みです。 ::: ## 5. クォートで囲む理由 {#quote} > **結論**: `"$(...)"` とダブルクォートで囲むと、結果に空白や改行があっても 1 つの値として安全に扱える。 ### 5-1. リナの失敗:クォートを忘れて改行が消える ```bash $ files=$(ls) $ echo $files # クォートなし ``` ```output Documents Downloads report.txt ``` ::: dialogue @lina: ファイル一覧が横一列に並びました。改行が消えています。変数の中身まで壊れてしまったんでしょうか。 @linny: 壊れてないよ。変数の中には、ちゃんと改行入りで入っている。 @lina: えっ、中身は無事なんですか。それは意外です。 @linny: そう。壊れているのは表示のほうなんだ。クォートを付けないと、`$(...)` の結果はスペースや改行の位置でバラバラの単語に分けられる。これを単語分割と呼ぶよ。 @linny: `echo` はその単語をスペース区切りで並べ直す。だから横一列に見えるんだ。 @lina: 中身ではなく渡し方の問題だったんですね。納得しました。 ::: ```bash $ echo "$files" # クォートあり ``` ```output Documents Downloads report.txt ``` ### 5-2. 迷ったら囲む ::: warning コマンド置換の結果は、**基本的に `"$(...)"` とダブルクォートで囲みます。** - ファイル名にスペースが含まれていても壊れません - 改行がそのまま保たれます - 結果が空のときに、引数ごと消えてエラーになるのを防げます 「迷ったら囲む」を習慣にするだけで、事故の大半を防げます。 ::: ## 6. よくある初心者のつまずき {#pitfalls} > **結論**: 末尾の改行は自動で消える、`$()` の中でも変数は使える、の 2 点を押さえると混乱しにくい。 ### 6-1. 末尾の改行は取り除かれる ```bash $ count=$(ls | wc -l) $ echo "ファイル数は $count 個" ``` ```output ファイル数は 3 個 ``` コマンド置換は、**出力の末尾にある改行を自動で取り除きます。** だから上の例のように、数値をそのまま文章へ埋め込めます。 ### 6-2. `$()` の中でも変数や引数が使える ```bash $ dir=/etc $ echo "$dir のファイル数: $(ls "$dir" | wc -l)" ``` ```output /etc のファイル数: 220 ``` `$(...)` の中は **ふつうのコマンドラインと同じ** です。変数も、パイプも、別のコマンド置換も自由に書けます。 ### 6-3. 結果が空でも壊れないようにする ```bash $ result="$(grep "存在しない語" file.txt)" $ echo "[$result]" ``` ```output [] ``` クォートで囲んでいれば、検索結果が空でも `[]` と表示されるだけで済みます。 クォートを外すと、引数そのものが消えます。そのため思わぬ動作の原因になります。 ## 7. ミニ課題:実際にやってみよう {#exercise} > **結論**: 変数代入・文章への埋め込み・入れ子の 3 問で、コマンド置換の基本を手で確かめる。 ::: dialogue @lina: 知識は入りました。手を動かして試したいです。 @linny: いいね、3 問用意したよ。ターミナルでやってみて。 ::: **課題 1**: 現在のユーザー名を `me` という変数に入れて、文章の中で表示しよう。 :::details ヒント 1(方向づけ)を見る まずユーザー名を調べるコマンドを実行します。その結果を箱に入れ、あとから文章の一部として取り出します。 ::: :::details ヒント 2(コマンド名)を見る ユーザー名は `whoami` でわかります。箱に入れる形は `変数名=$(コマンド)` です。表示は `echo` です。 ::: :::details 答えを見る ```bash $ me=$(whoami) $ echo "私は $me です" ``` ```output 私は lina です ``` 表示される名前は、お使いの環境によって変わります。 ::: **課題 2**: 「現在のディレクトリは ◯◯ です」という文章を、`pwd` の結果を埋め込んで表示しよう。 :::details ヒント 1(方向づけ)を見る 今回は変数を使いません。文章の中に、コマンドの結果を直接はめ込みます。 ::: :::details ヒント 2(コマンド名)を見る 現在地は `pwd` でわかります。文章全体をダブルクォートで囲み、`$(pwd)` を差し込みます。 ::: :::details 答えを見る ```bash $ echo "現在のディレクトリは $(pwd) です" ``` ```output 現在のディレクトリは /home/lina/myproject です ``` 表示されるパスは、お使いの環境によって変わります。 ::: **課題 3**: 今いるディレクトリの **名前だけ** を表示しよう。パス全体ではなく、末尾の 1 つだけです。 :::details ヒント 1(方向づけ)を見る 2 段構えです。まず現在地のパス全体を取り出します。次にそのパスから、末尾の名前だけを取り出します。 ::: :::details ヒント 2(コマンド名)を見る パス全体は `pwd` です。末尾だけを取り出すのは `basename` です。`$(...)` を入れ子にします。 ::: :::details 答えを見る ```bash $ echo "$(basename "$(pwd)")" ``` ```output myproject ``` `pwd` が取得したパスの末尾だけを `basename` が取り出します。内側の `$(pwd)` から先に実行される点を確かめてください。 ::: ## 8. 振り返り {#review} ::: dialogue @lina: 整理します。`$(コマンド)` は、その場所をコマンドの実行結果に置きかえる書き方ですね。 @linny: そのとおり。変数に入れたいときは `VAR=$(コマンド)` の形にする。 @lina: そして結果を使うときは `"$(...)"` と囲む。囲まないと単語分割で崩れてしまうんですね。 @linny: 完璧だね。バッククォートは読めれば十分。書くときは `$(...)` に統一しよう。 ::: ## 9. コピペ用テンプレート {#templates} > **結論**: 代入・埋め込み・ファイル名生成・カウントのよく使う型をまとめて手元に置いておく。 ::: tip **よく使う型をまとめておく** ```bash # 結果を変数に入れる VAR=$(コマンド) # 文章に埋め込む(必ずダブルクォート) echo "結果は $(コマンド) です" # 日付入りのファイル名を作る logfile="app-$(date +%Y%m%d).log" # 行数を数えて変数に count=$(ls | wc -l) # 入れ子で組み合わせる name="$(basename "$(pwd)")" ``` ::: ## 今日の 3 行まとめ {#three-lines} 1. `$(コマンド)` は、その場所をコマンドの実行結果に置きかえる 2. 変数に入れるときは `VAR=$(コマンド)`。`=` の前後にスペースを入れない 3. 結果を使うときは `"$(...)"` と囲む。囲まないと空白や改行で分割される ## 次に読む {#next} - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [date コマンド入門](/articles/tutorials/date-formatting) - [pwd・cd・lsの使い方](/articles/tutorials/basic-commands) - [仮想ターミナルでコマンドを試す](/terminal) # cronが動かない原因と対処法 - crontabの使い方とログ確認 Source: https://penguin-gym-linux.com/articles/tutorials/cron-basics ## この記事で解決できること {#intro} - cron が「動かない」「実行されない」原因を体系的に切り分けられます。 - **ログの場所**と**確認方法**が分かります。 - crontab の時刻指定を読み書きできます。 - 実務で頻発する「ユーザー・PATH・権限」の罠を避けられます。 ::: tip **結論(障害対応の型)** cron が動かないときは、**感覚で直さない**。必ず次の順で見る。 1. **本当に登録されているか** 2. **どのユーザーの cron か** 3. **ログに実行痕跡があるか** 4. **手動実行すると動くか** 5. **PATH / 権限 / 実行ファイル問題** ::: ::: warning **前提(対象環境)** - OS:Ubuntu - 新人〜実務初期 - root / 一般ユーザー両方を想定 ::: ::: danger **先に 1 つだけ警告:`crontab -r` は使わない** `crontab -r` は、そのユーザーの登録内容を**確認なしで全部消す**コマンドです。 `crontab -e`(編集)とキーが隣接しているため、押し間違いによる全消しが定番の事故になっています。 削除した内容を戻す機能はありません。 - 編集の前に必ず控えを取る:`crontab -l > ~/crontab-$(date +%Y%m%d).bak` - 一部だけ止めたいなら `crontab -e` で該当行の先頭に `#` を付ける - どうしても削除する場合は `crontab -i -r` を使う(`-i` は削除前に確認を出す) ::: ## 1. cron の種類(ここを誤解すると詰む) {#types} > **結論**: ユーザーcron・root cron・`/etc/cron.*`があり、実行ユーザーが異なる。まずどこに書いたか明確にする。 Ubuntu には cron が複数あります。 **先に用語を整理します。** | 用語 | 一行の意味 | 混同されやすい言い方 | | -------- | ------------------------------------------------------ | ------------------------------------------ | | デーモン | 画面を持たず、裏で動き続けるプログラム | 「常駐プロセス」「サービス」とも呼ばれます | | cron | 決めた時刻にコマンドを自動実行するデーモン | 「cron デーモン」「crond」も同じものです | | crontab | 実行予定を書いた設定ファイル、およびそれを編集するコマンド | 「クーロンタブ」と読みます | | ジョブ | crontab に書いた 1 行分の実行予定 | 「エントリ」「タスク」も同義です | | MTA | メールを送る役割のソフトウェア | Postfix / sendmail などが該当します | ### 1-1. ユーザー cron ```bash $ crontab -e ``` - 実行ユーザー:そのユーザー - 一番よく使う ### 1-2. root cron ```bash $ sudo crontab -e ``` - 実行ユーザー:root - 権限が必要な処理用 ### 1-3. /etc/crontab と /etc/cron.d システム全体用の定義です。**書式が 6 フィールド**で、5 つの時刻指定の次に**実行ユーザー欄**が入ります。 ```cron # 分 時 日 月 曜日 ユーザー コマンド 0 3 * * * root /usr/local/bin/backup.sh ``` ::: warning ユーザー cron と同じ 5 フィールドで書くと、コマンド名がユーザー名として読まれます。 ログに `unknown user` などが出て動きません。書く場所によって書式が変わる点に注意してください。 ::: ### 1-4. /etc/cron.daily などのディレクトリ `/etc/cron.hourly` / `/etc/cron.daily` などに置いたスクリプトは `run-parts` が実行します。制約が 2 つあります。 - **実行権限が必要**(`chmod +x`) - **ファイル名にドットを含むと無視される**(`backup.sh` は実行されない。拡張子を外して `backup` にする) ::: tip **どこに書いたかをまず明確にする** ::: ## 2. crontab の書き方(時刻指定の 5 フィールド) {#syntax} > **結論**: 時刻指定は分・時・日・月・曜日の 5 フィールドで、左から順に細かい単位が並ぶ。 ユーザー crontab(`crontab -e` で開くもの)の 1 行は、「いつ実行するか」5 つと「何を実行するか」で構成されます。 `/etc/crontab` / `/etc/cron.d` は §1-3 のとおり 6 フィールドなので混同しないでください。 ``` * * * * * コマンド | | | | | | | | | +-- 曜日(0-7、0 と 7 は日曜) | | | +---- 月(1-12) | | +------ 日(1-31) | +-------- 時(0-23) +---------- 分(0-59) ``` `*` は「毎回」という意味です。 **例:** - `0 3 * * *` — 毎日 3:00 - `*/5 * * * *` — 5 分おき - `0 0 * * 0` — 毎週日曜の 0:00 ::: warning 「日」と「曜日」の両方を `*` 以外にすると、**どちらか一方でも一致すれば実行**されます。 `0 3 1 * 1` は「毎月 1 日」と「毎週月曜」の両方で動きます。片方だけにしたい場合、もう一方は `*` にしてください。 ::: ::: warning crontab のコマンド部では、エスケープしていない `%` が改行に置き換えられます。 `date +%Y%m%d` のような書き方は、シェルでは動いても crontab に貼った瞬間に壊れます。 ```cron # NG(% 以降が切れる) 0 3 * * * /home/user/backup.sh >> /home/user/log/backup-$(date +%Y%m%d).log 2>&1 # OK(\% とエスケープする) 0 3 * * * /home/user/backup.sh >> /home/user/log/backup-$(date +\%Y\%m\%d).log 2>&1 ``` 日付処理をスクリプト側に寄せてしまうのも確実な回避策です。 ::: ### 2-1. 書いた内容が意図どおりか先に確かめる いきなり本番の処理を登録せず、まず 1 分おきに `date` を書き出すだけの行で動作を確認すると安全です。 ```bash * * * * * /usr/bin/date >> /tmp/cron-test.log 2>&1 ``` 数分待って `/tmp/cron-test.log` に行が増えていれば、cron 自体は動いています。 確認が済んだらこの行は消してください。ログファイルが増え続けます。 ## 3. 登録されているか確認 {#check-registered} > **結論**: `crontab -l`と`sudo crontab -l`で登録内容を確認する。「登録したつもり」が最多の事故原因。 ```bash $ crontab -l $ sudo crontab -l ``` `crontab -l` は登録内容を表示するだけで、何も変更しません。安全に何度でも実行できます。 ::: warning 「登録したつもり」が一番多い事故。 ::: ## 4. cron のログを見る(最重要) {#logs} > **結論**: `grep CRON /var/log/syslog`か`journalctl -u cron`で実行痕跡を確認。ログに出ない=実行されていない。 ### 4-1. syslog から確認 ```bash $ grep CRON /var/log/syslog ``` ```output Dec 15 03:00:01 web01 CRON[2451]: (user) CMD (/home/user/backup.sh) Dec 15 03:05:01 web01 CRON[2478]: (root) CMD (/usr/local/bin/cleanup.sh) ``` `(user)` の部分が実行ユーザー、`CMD (...)` が実行されたコマンドです。 時間を絞る: ```bash $ grep CRON /var/log/syslog | tail -n 50 ``` ### 4-2. journalctl で確認 ```bash $ journalctl -u cron ``` 直近だけ: ```bash $ journalctl -u cron -n 100 ``` ::: tip **ログに出ていない=実行されていない** ::: `/var/log/syslog` が存在しない構成では `journalctl -u cron` を使います。 どちらのコマンドもログを表示するだけで、cron の設定は変わりません。 ## 5. 「実行されているが失敗している」ケース {#executed-but-failed} > **結論**: ログに`CMD`が出ていればcron自体は動いている。実行ユーザーで手動実行して原因を切り分ける。 ログに次が出ることがある。 ```bash CMD (/path/to/script.sh) ``` これは **cron自体は動いている**。 次にやること: ```bash $ sudo -u /path/to/script.sh ``` ::: warning 手動実行は、そのスクリプトが行う処理を**実際に実行します**。 ファイル削除やデータ更新を含むスクリプトの場合、テスト用のディレクトリで試すか、処理内容を先に読んでから実行してください。 ::: ## 6. 一番多い罠①:PATHが違う {#trap-path} > **結論**: cronのPATHは極端に短い。コマンドはフルパスで書くか、冒頭でPATHを定義して回避する。 cron の PATH は極端に短い。 PATH とは、コマンド名だけで実行できる場所の一覧です。ログイン時のシェルと cron では中身が違います。 ### NG例 ```bash mysqldump ... ``` ### OK例 ```bash /usr/bin/mysqldump ... ``` フルパスは `which` で調べられます。 ```bash $ which mysqldump /usr/bin/mysqldump ``` **対策:** - フルパスで書く - 冒頭で PATH を定義 ```bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ``` ## 7. 一番多い罠②:実行権限がない {#trap-permission} > **結論**: `Permission denied`が出たら実行権限不足。`ls -l`で確認し`chmod +x`で付与する。 ```bash Permission denied ``` 確認: ```bash $ ls -l script.sh ``` 対処: ```bash $ chmod +x script.sh ``` `chmod +x` は誰に付けるかを省略した書き方で、通常は所有者・グループ・その他へまとめて実行権限が付きます。 自分だけに付けたい場合は `chmod u+x script.sh` と対象を明示してください。 ## 8. 一番多い罠③:相対パス {#trap-relative} > **結論**: cronは実行ディレクトリが不定。`./script.sh`は避け、必ず絶対パスで指定する。 cron は **どこで実行されるか分からない**。 ### NG ```bash ./script.sh ``` ### OK ```bash /home/user/script.sh ``` スクリプトの中で相対パスを使っている場合も同じ理由で失敗します。スクリプト内のファイル指定も絶対パスにしてください。 ## 9. メール通知(失敗を可視化) {#mail} > **結論**: `MAILTO`で失敗を通知できる。メールが来ない場合はMTA未設定かcron自体が動いていない。 cron はジョブが何か出力を出したとき、その内容をメールで送る。 エラーメッセージも出力の一種なので、失敗の検知に使える。 ```bash MAILTO=you@example.com ``` メールが来ない場合: - MTA未設定 - そもそも cron が動いていない メールに頼れない環境では、実行結果をファイルへ書き出す方が確実です。 ```bash 0 3 * * * /home/user/backup.sh >> /home/user/log/backup.log 2>&1 ``` `>>` は追記、`2>&1` はエラー出力も同じファイルへまとめる指定です。 ::: warning 出力先は**実行ユーザーが書き込めるパス**にしてください。 一般ユーザーの cron から `/var/log/` へ書こうとすると `Permission denied` になり、リダイレクトごと失敗します。 `/var/log/` 配下へ書くのは、root cron の場合か、あらかじめ書き込み権限を付与した専用ファイルがある場合に限ります。 ::: ## 10. cron が「重い処理」の犯人になるケース {#heavy} > **結論**: バックアップやrsync等の重い処理がI/O・CPU負荷の犯人になりやすい。負荷調査記事と併せて確認する。 - バックアップ - ログ圧縮 - rsync - Docker cleanup 負荷が跳ねた時刻と、これらのジョブが動く時刻が一致していないかを確認する。 突き合わせには `journalctl -u cron --since "<時刻>"` で実行痕跡を見るのが早い。 ::: warning **やってはいけないこと** - ログを見ずに書き換える - root cron に何でも入れる - フルパスを書かない - 手動実行テストを省略する - 控えを取らずに `crontab -r` を実行する ::: ## 11. 作業完了チェックリスト {#checklist} > **結論**: 登録・実行痕跡・出力先・控えの 4 点が揃っていれば、cron の設定作業は完了と判断できる。 - [ ] `crontab -l` に意図した行が入っている(root なら `sudo crontab -l` も確認) - [ ] ログ(`grep CRON /var/log/syslog` または `journalctl -u cron`)に実行痕跡がある - [ ] コマンドとスクリプト内のパスがすべて絶対パスになっている - [ ] 実行結果をファイルまたはメールで受け取れる - [ ] 編集前の crontab を控えてある ::: tip **コピペ用:cron 障害切り分けテンプレ** ```bash # 控えを取る(編集前に必ず) crontab -l > ~/crontab-$(date +%Y%m%d).bak # 登録確認 crontab -l sudo crontab -l # ログ確認 grep CRON /var/log/syslog journalctl -u cron -n 100 # 手動実行(実行ユーザーで) sudo -u user /path/to/script.sh ``` ::: ## 次に読む {#next} - [cronのログを詳しく調べる](/articles/tutorials/journalctl-basics) - [cronがI/O負荷を起こしている場合](/articles/troubleshooting/disk-io-troubleshooting) - [cronがCPU負荷を起こしている場合](/articles/troubleshooting/cpu-high-load) # curl/wget入門 - コマンドラインでのHTTP通信 Source: https://penguin-gym-linux.com/articles/tutorials/curl-wget-basics ## この記事で学べること {#intro} - `curl` と `wget` の **役割の違い** がわかります - ダウンロードしたファイルが **どこに作られるか** を先に確認できます - **上書きの危険** を避けながらファイルを取ってこられます - ヘッダー確認や API アクセスを **コピペで動かせる** ようになります ::: tip **言葉の整理(先に取りちがえを防ぎます)** - **ダウンロード**:ネットワークの向こうにあるデータを、自分のパソコンに写し取ることです。 - **リダイレクト**:「このページは別の場所へ引っこしました」というサーバからの案内です。シェルの `>`(リダイレクト)とは **別のもの** です。同じ言葉が 2 つの意味で使われます。 - **ヘッダー**:本文とは別に送られてくる情報の札です。中身の種類や大きさが書かれています。 - **ステータスコード**:結果を表す 3 桁の数字です。`200` は成功、`404` は見つからない、を表します。 - **API**:プログラム同士がやり取りするための窓口です。返事は HTML ではなく JSON という形式が多いです。 ::: ::: tip **結論(先に覚える型)** - 単発のファイルダウンロード → `wget URL` - API を叩く・ヘッダー確認・POST → `curl URL` - リダイレクトを追従したい → `curl -L URL` - ダウンロードが途中で切れた → `wget -c URL` ::: ::: warning **前提(対象環境)** - OS:Ubuntu / 一般的な Linux - `curl` は標準搭載が多いです。`wget` は `sudo apt install wget` で入ります - 対象は HTTP / HTTPS です(curl は ftp / sftp / smtp 等にも対応します) ::: ## 1. curl と wget の違い:まず役割を分ける {#difference} > **結論**: wget はダウンローダーで保存がデフォルト、curl は多機能 HTTP クライアントで標準出力がデフォルト。 ::: dialogue @lina: 先輩、curl と wget ってどっちも「URL からデータを取ってくる」コマンドですよね。どちらを使えばいいのか毎回迷います。 @linny: いい質問。実は **設計の目的が違う** んだ。一言で言うと、wget は **ダウンローダー**、curl は **多機能 HTTP クライアント** だよ。 @lina: ダウンローダーと、クライアント、ですか。 @linny: ダウンローダーは「ファイルを落とす専門の道具」。クライアントは「サーバに話しかける側」のことだよ。 @lina: なるほど。curl の方が守備範囲が広いのですね。 @linny: そう。wget は「URL を渡したらファイルを保存する」のがデフォルト動作。curl は「URL を叩いてレスポンスを画面に出す」のがデフォルト。この差が、ほかの全部の違いにつながっているよ。 ::: ::: highlight **ざっくり比較** | 項目 | curl | wget | | ---------------- | ------------------------ | ---------------------- | | デフォルト動作 | 標準出力に表示 | ファイルとして保存 | | HTTP メソッド | GET/POST/PUT/DELETE 等 | 基本 GET のみ | | リダイレクト追従 | `-L` を付ける必要あり | 自動で追従 | | 再帰ダウンロード | 苦手 | 得意(`-r`) | | レジューム | `-C -` で対応 | `-c` で対応 | | 用途 | API 叩く・デバッグ・POST | サイト丸ごとミラー・DL | ::: ::: tip **迷ったら**:API・ヘッダー・POST が絡むなら curl です。ファイルを落とすだけなら wget です。 ::: ## 2. curl の基本:まず画面に出す {#curl-basic} > **結論**: curl は既定で内容を画面に出す。保存するには `-o 名前`(名前指定)か `-O`(URL のファイル名)を付ける。 ### 2-1. URL を叩いて結果を画面に出す ```bash $ curl https://example.com ``` ```output Example Domain ... ``` ::: dialogue @lina: えっ、HTML が全部画面に流れてきました。 @linny: それが curl のデフォルト動作。**標準出力(画面)に丸ごと吐く** んだ。ファイルにしたいときは `-o`(名前指定)か `-O`(URL のファイル名を使う)を付けるよ。 ::: ### 2-2. 名前を指定して保存 `-o` ```bash $ curl -o page.html https://example.com ``` `-o` は **o**utput(出力)の頭文字です。**小文字の o** は、保存する名前を引数で指定します。 ### 2-3. URL のファイル名で保存 `-O` ```bash $ curl -O https://example.com/sample.tar.gz ``` **大文字の O** は、URL の末尾のファイル名をそのまま使います。この例なら `sample.tar.gz` という名前で保存されます。 ::: warning **`-o` と `-O` を間違えると悲しい結末** - `-O` を付ける URL は **末尾がファイル名であること** が前提です - `curl -O https://example.com/`(末尾スラッシュ)は、ファイル名が決まらず失敗します - 迷ったら `-o 名前` で明示する方が安全です ::: ### 2-4. どこに保存される? 上書きは? {#where} ダウンロードの前に、2 つだけ確認しておきます。「どこに作られるか」と「同じ名前があったらどうなるか」です。 ::: warning **保存先と上書きの先回り** - 保存先は **いま自分がいるディレクトリ**(カレントディレクトリ)です。`pwd` で確認できます - `curl -O` と `curl -o` は、**同じ名前のファイルがあっても確認なしで上書きします** - GUI のファイル操作と違い、上書き前の確認ダイアログは出ません。元の中身は戻せません - 上書きを止めたいときは `curl --no-clobber -O URL` を使います。同じ名前があれば curl は何もしません - `wget` は既定では上書きしません。代わりに `sample.tar.gz.1` のように連番を足して保存します - `curl` と `wget` は本サイトの仮想ターミナルでは動きません。実行は自分のパソコンのターミナルで行います - 練習は「10. ミニ課題」で作る、からっぽのディレクトリの中だけで行ってください。そこなら、もとからあるファイルを壊しません ::: ::: dialogue @lina: 先輩、`curl -O` で落とした設定ファイルを少し書きかえたんです。そのあと、もう一度同じコマンドを実行しました。そうしたら、書きかえた内容が消えていました。 @linny: それは上書きだね。curl は「同じ名前があるよ、いい?」とは聞かない。だまってファイルを置きかえるんだ。 @lina: えっ、確認なしなんですか。ゴミ箱にも残らない…。 @linny: 残らないよ。だから **落とす前に `ls` で同じ名前がないか見る** のが安全。名前を変えたいときは `-o` で自分で決める。この 2 つで事故はほぼ防げるよ。 @lina: なるほど。「落とす前に `ls`」を先にやればよかったんですね。 ::: ```bash # 落とす前に、いる場所と既存ファイルを確認する $ pwd $ ls sample.tar.gz # 上書きが怖いときは自分で名前を決める $ curl -o sample-20260605.tar.gz https://example.com/sample.tar.gz ``` ### 2-5. ダウンロード進捗を見たい ```bash $ curl -O --progress-bar https://example.com/big.iso ``` `--progress-bar` を付けると、シンプルな進捗バーが出ます。長いダウンロードでも状況がわかります。 ## 3. 初心者の最大の罠:リダイレクト `-L` {#redirect} > **結論**: curl は既定でリダイレクトを追わない。GitHub 等を叩くときは `-L` を付けて最終ページを取得する。 ::: dialogue @lina: 先輩、`https://github.com/torvalds/linux` を curl で取ってみたんです。でも中身が変なんです。 @linny: ああ、それは典型的なハマりどころ。GitHub やショート URL は、途中で **リダイレクト** が挟まることが多いんだ。 @lina: リダイレクト、でしたね。「引っこしました」の案内ですか。 @linny: そう。curl はデフォルトで **その案内を追いかけない**。だから引っこし先ではなく、案内のページだけが返ってくる。`-L` を付けると追いかけるようになるよ。 ::: ### 3-1. リダイレクトを追従する `-L` ```bash # NG: リダイレクトされた案内が返るだけ $ curl https://github.com/torvalds/linux # OK: 最終ページの内容を取得 $ curl -L https://github.com/torvalds/linux ``` `-L` は **L**ocation の頭文字です。Location は、引っこし先を伝える HTTP ヘッダーの名前です。 ::: highlight **型として覚える** - 外部サイトを curl で叩くときは **常に `-L` を付ける** と事故が減ります - ファイルダウンロードでも `-L` が必要な場面は多いです(GitHub Releases 等) ::: ```bash # 安全な定番形 $ curl -LO https://github.com/some/repo/releases/download/v1.0/binary.tar.gz ``` `-LO` は `-L` と `-O` をつなげた書き方です。1 文字のオプションは、このようにまとめて書けます。`-L -O` と離して書いても同じ意味です。 ## 4. ヘッダーだけ確認したい `-I` {#header} > **結論**: `-I` は本文を落とさずヘッダーだけ取得する。先頭の HTTP ステータスコードで生死やリダイレクトが分かる。 「リンク先は生きているか」「リダイレクト先はどこか」を、**本文をダウンロードせずに** 確認できます。 ```bash $ curl -I https://example.com ``` ```output HTTP/2 200 content-type: text/html; charset=UTF-8 content-length: 1256 date: Sun, 26 May 2026 09:00:00 GMT server: ECS ``` ::: dialogue @lina: なるほど、`HTTP/2 200` は「成功」ですね。 @linny: その通り。最初の数字が **HTTP ステータスコード**。これだけ覚えれば実務の 9 割は読めるよ。 ::: ::: highlight **HTTP ステータス 早見表** | コード | 意味 | 状況 | | ------ | -------------- | ---------------------------------- | | `2xx` | 成功 | `200 OK`、`204 No Content` | | `3xx` | リダイレクト | `301`(恒久)、`302`(一時) | | `4xx` | クライアント側 | `404`(無い)、`401`/`403`(権限) | | `5xx` | サーバー側 | `500`(バグ)、`503`(過負荷) | ::: ### 4-1. リダイレクト先を辿る ```bash $ curl -ILs https://bit.ly/3xxxxx | grep -i location ``` `-ILs` は `-I` `-L` `-s` を 1 つにまとめた書き方です。`-L` でリダイレクト追従、`-s` で進捗とエラーの非表示、`-I` でヘッダーのみになります。`location:` の行に遷移先の URL が出ます。リダイレクトが複数回起きると、その回数だけ `location:` が並びます。一番下が最終的な行き先です。 ## 5. POST と JSON:API を叩く {#api} > **結論**: API 送信は `-X`(メソッド)・`-H`(ヘッダー)・`-d`(ボディ)の 3 点セット。JSON はシングルクォートで囲む。 ::: dialogue @lina: REST API を curl で叩きたいです。JSON はどう送ればいいですか。 @linny: API テストは curl の得意分野。`-X` で HTTP メソッド、`-H` でヘッダー、`-d` でボディを指定する。この **3 点セット** を覚えるだけだよ。 ::: ### 5-1. GET(クエリパラメータ付き) ```bash $ curl "https://api.example.com/users?id=42" ``` URL に `?` 以降を含めるときは、**必ずクォート** します。囲まないと、シェルが `&` を別の意味に解釈します。 ### 5-2. POST で JSON を送る ```bash $ curl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d '{"name":"lina","role":"beginner"}' ``` ::: highlight **3 点セットの意味** - `-X POST`:HTTP メソッドの指定です。`-d` を付けると curl は自動で POST になります。そのため普段は書かなくても動きます - `-H "Content-Type: application/json"`:「JSON を送ります」という宣言です - `-d '...'`:送る本体(JSON)です。**シングルクォート推奨** です。中の `"` をエスケープせずに済みます ::: ### 5-3. Bearer トークン認証 ```bash $ curl https://api.example.com/me \ -H "Authorization: Bearer YOUR_TOKEN_HERE" ``` ::: warning **トークンを履歴に残さない** ```bash # NG: シェル履歴・ps に丸見え $ curl -H "Authorization: Bearer abc123..." ... # OK: 環境変数経由 $ export API_TOKEN=abc123... $ curl -H "Authorization: Bearer $API_TOKEN" ... ``` `history` や `ps` でトークンが見えると事故になります。環境変数か `~/.netrc` を使ってください。 ::: ### 5-4. 基本認証(Basic 認証) ```bash $ curl -u username:password https://example.com/private ``` `-u` に `user:pass` を渡します。HTTPS で使ってください。 ## 6. wget の本領:堅実なダウンロード {#wget-basic} > **結論**: wget は保存がデフォルトでリダイレクトも自動追従。`-O` で名前指定、`-c` で中断ダウンロードを再開できる。 ### 6-1. 基本:ファイルとして保存 ```bash $ wget https://example.com/sample.tar.gz ``` ::: dialogue @lina: wget はオプションを付けなくてもファイルになるんですね。楽です。 @linny: そう、wget は **保存がデフォルト**。リダイレクトも自動で追う。シンプルなダウンロードなら wget の方が事故が少ないよ。 ::: ::: tip **wget は勝手に上書きしません** 同じ名前のファイルがあると、wget は `sample.tar.gz.1` のように **連番を足した別名** で保存します。ただし `-O 名前` を付けたときは指定した名前を上書きします。ここだけ挙動が変わるので注意してください。 ::: ### 6-2. 名前を変えて保存 `-O` ```bash $ wget -O custom.tar.gz https://example.com/sample.tar.gz ``` ::: warning **curl と wget で `-O` の意味が逆** - curl の `-O`:URL のファイル名で保存します - wget の `-O`:**ファイル名を指定** して保存します(curl の `-o` に相当) 両方使う人は、ここで必ず一度ハマります。 ::: ### 6-3. 中断したダウンロードを再開 `-c` ```bash $ wget -c https://example.com/big.iso ``` `-c` は **c**ontinue です。回線が切れて途中までしか落ちていないファイルを、**続きから** ダウンロードします。 ### 6-4. リトライ・タイムアウト ```bash $ wget --tries=5 --timeout=30 https://example.com/file.zip ``` 不安定な回線でも、あきらめずに再試行する設定です。 ## 7. 再帰ダウンロード `wget -r`(注意して使う) {#recursive} > **結論**: `wget -r` はリンクを再帰追跡する。`-l` で深さ、`-np` で範囲を必ず制限し、相手サーバへの配慮を忘れない。 ```bash $ wget -r -l 2 -np https://example.com/docs/ ``` - `-r`:再帰的にリンクをたどります - `-l 2`:階層を 2 階層までに制限します - `-np`:親ディレクトリに登りません ::: danger **やってはいけないこと** - 上限を付けずに `-r` だけで実行する → **サイト全体を吸い出してしまいます** - 短い間隔で大量アクセスする → **相手サーバへの攻撃** と見なされます - `robots.txt` を無視する → 規約違反になります `--wait=2` で間隔を空けてください。対象範囲を URL で限定し、相手の利用規約も確認してください。 ::: ## 8. 文字化け・改行コードの罠 {#charset} > **結論**: curl/wget は生のバイト列を渡す。非 UTF-8 は `iconv`、Windows 由来の CRLF は `tr -d '\r'` で整える。 ::: dialogue @lina: 取ってきた HTML が `譁�ュ怜喧` みたいな読めない文字だらけです。 @linny: 文字化けだね。ブラウザは文字の種類を自動で見分けて直してくれる。でも curl と wget は **そのまま** 渡すんだ。文字コードが UTF-8 でないページは `iconv` で変換すると読めるようになるよ。 @lina: 改行コードも気になります。 @linny: Windows のサーバから取ったファイルは、改行が `\r\n`(CRLF)のことがある。Linux で扱うなら `dos2unix` か `tr -d '\r'` で除くのが定番だよ。 ::: ```bash # Shift_JIS のページを UTF-8 に変換しながら保存 $ curl -s https://example.com/sjis.html | iconv -f SHIFT_JIS -t UTF-8 > page.html # CRLF を LF に変換 $ curl -s https://windows-server.example/data.csv | tr -d '\r' > data.csv ``` ::: warning `>` は **シェルのリダイレクト** です。ここでも同じで、指定した名前のファイルがあれば中身を消して上書きします。大事なファイル名を書かないよう気をつけてください。 ::: ## 9. よくある初心者のつまずき {#pitfalls} > **結論**: 保存されない(`-O`/`-o` 忘れ)、リダイレクト未追従(`-L` 忘れ)、`&` の未クォートが典型的なつまずき。 ### 9-1. `curl URL` で何も保存されない **原因**:curl のデフォルトは「画面に出す」です。保存したいなら `-O` か `-o` を付けます。 ```bash $ curl -O https://example.com/file.zip $ curl -o my.zip https://example.com/file.zip ``` ### 9-2. リダイレクト先の中身が取れない **原因**:`-L` を忘れています。 ```bash $ curl -L https://github.com/... ``` ### 9-3. クエリの一部が無視される・変なエラーが出る **原因**:`&` が、シェルのバックグラウンド実行の記号として解釈されました。`&` の前までが curl に渡され、後ろは別の処理になります。 - `&page=2` のように `=` があるとき:**エラーは出ません**。`&page=2` が静かに捨てられ、結果だけが期待と違います。これが一番気づきにくいパターンです - `&page` のように `=` がないとき:`page: command not found` が出ます ```bash # NG $ curl https://api.example.com/search?q=linux&page=2 # OK $ curl "https://api.example.com/search?q=linux&page=2" ``` ### 9-4. SSL 証明書エラーで止まる **原因**:自己署名証明書、期限切れ、社内 CA などです。 ```bash # 検証あり(推奨) $ curl https://internal.example.com # 検証スキップ(緊急回避のみ。本番禁止) $ curl -k https://internal.example.com ``` ::: warning `-k` は **検証スキップ** です。通信に割り込まれる危険が上がります。本番運用では証明書を正しく整えてください。 ::: ### 9-5. プロキシ環境で繋がらない ```bash $ export http_proxy=http://proxy.example.com:8080 $ export https_proxy=http://proxy.example.com:8080 $ curl https://example.com ``` 社内環境では `http_proxy` と `https_proxy` を設定します。 ## 10. ミニ課題:手を動かして確かめよう {#exercise} > **結論**: httpbin.org を使い、ステータス確認・JSON POST・リダイレクト追従の 3 課題を実際に叩いて定着させる。 ::: dialogue @lina: 知識は入りました。ターミナルで試したいです。 @linny: 3 問用意したよ。`https://httpbin.org` は HTTP の練習用サイト。安心して叩いていい。 ::: 練習用のからっぽのディレクトリを作り、その中で試します。こうすれば、もとからあるファイルを上書きしません。 ```bash # 練習用ディレクトリを準備して移動する $ mkdir -p ~/curl-practice && cd ~/curl-practice ``` - `~` は「自分のホームディレクトリ(自分専用の置き場)」を表す記号です - `&&` は「左が成功したら、続けて右を実行する」という意味です。2 行に分けて打っても同じです **課題 1**:`https://httpbin.org/get` の HTTP ステータスコードと content-type を確認しよう。 :::details ヒント 1(方向づけ)を見る 本文はいりません。「札」の部分だけを取るオプションがあります。 ::: :::details ヒント 2(コマンド名)を見る 使うコマンドは `curl` です。オプションは「4. ヘッダーだけ確認したい」で出てきた 1 文字です。 ::: :::details 答えを見る ```bash $ curl -I https://httpbin.org/get ``` ```output HTTP/2 200 date: ... content-type: application/json ... ``` 先頭の `200` が成功を表します。`content-type: application/json` は、返事が JSON であることを示します。 ::: **課題 2**:`https://httpbin.org/post` に JSON `{"hello":"world"}` を POST しよう。 :::details ヒント 1(方向づけ)を見る 指定するものは 3 つです。「方法」「これは JSON という宣言」「送る中身」です。 ::: :::details ヒント 2(コマンド名)を見る 使うコマンドは `curl` です。オプションは「5-2. POST で JSON を送る」で使った 3 つです。 ::: :::details 答えを見る ```bash $ curl -X POST https://httpbin.org/post \ -H "Content-Type: application/json" \ -d '{"hello":"world"}' ``` レスポンスの中に `"json": {"hello": "world"}` が見えれば成功です。 ::: **課題 3**:`https://httpbin.org/redirect/3` のリダイレクト先を辿って、最終ページを取得しよう。 :::details ヒント 1(方向づけ)を見る curl は引っこしの案内を、そのままでは追いかけません。追いかけさせる指示が必要です。 ::: :::details ヒント 2(コマンド名)を見る 使うコマンドは `curl` です。オプションは「3. 初心者の最大の罠」で出てきた 1 文字、Location の頭文字です。 ::: :::details 答えを見る ```bash $ curl -L https://httpbin.org/redirect/3 ``` 3 回リダイレクトされたあと、`/get` のレスポンスが返ります。 ::: ## 11. コピペ用テンプレート {#templates} > **結論**: curl は表示/保存/リダイレクト/ヘッダー/POST/認証、wget は基本DL/名前変更/再開/再帰の定番形をコピペで使える。 ::: tip **curl テンプレ** ```bash # 画面に表示(デバッグ) curl URL # 保存(URL のファイル名で。同名は上書き) curl -O URL # 保存(名前指定。同名は上書き) curl -o name URL # リダイレクト追従しつつ保存(GitHub Releases 等) curl -LO URL # ヘッダーだけ確認 curl -I URL # ステータスコードだけ取得 curl -s -o /dev/null -w "%{http_code}\n" URL # JSON を POST curl -X POST URL \ -H "Content-Type: application/json" \ -d '{"key":"value"}' # Bearer トークン curl URL -H "Authorization: Bearer $API_TOKEN" # 基本認証 curl -u user:pass URL # 進捗バー付き curl -O --progress-bar URL ``` ::: ::: tip **wget テンプレ** ```bash # 基本ダウンロード(同名があれば連番を足す) wget URL # 名前を変えて保存(同名は上書き) wget -O custom.name URL # 中断したファイルの続き wget -c URL # リトライ・タイムアウト指定 wget --tries=5 --timeout=30 URL # 静かにダウンロード(ログ最小) wget -q URL # 再帰ダウンロード(マナー注意) wget -r -l 2 -np --wait=2 https://example.com/docs/ ``` ::: ## 振り返り {#review} > **結論**: 目的で curl と wget を選び、落とす前に保存先を見て、外部サイトには `-L` を付ける。 ::: dialogue @lina: 整理します。ファイルを落とすだけなら wget、API やヘッダーを見るなら curl ですね。 @linny: そのとおり。迷ったら「保存が目的か、中身を見るのが目的か」で分けるといいよ。 @lina: あと、落とす前に `pwd` と `ls`。`curl -O` は確認なしで上書きするからですね。 @linny: 完璧だね。外部サイトを curl で叩くときは `-L`。この 3 つを持ち帰ってくれれば十分だよ。 ::: ## 今日の 3 行まとめ {#summary} > **結論**: 用途で curl と wget を選び、保存先と上書きを先に確かめ、外部サイトには `-L` を付ける。 1. API やヘッダーを見るなら `curl`、ファイルを落とすだけなら `wget` を選びます 2. 落とす前に `pwd` と `ls` を見ます。`curl -O` と `curl -o` は同名ファイルを確認なしで上書きします 3. 外部サイトを curl で叩くときは `-L` を付けます。リダイレクトを追いかけてくれます ## まとめ:次に読む {#next} - [jq 入門 - シェルで JSON を扱う](/articles/tutorials/jq-basics) - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [ファイル転送の基本 scp / rsync](/articles/tutorials/scp-rsync-basics) # cut/paste/tr 入門 - 列の抽出と文字変換 Source: https://penguin-gym-linux.com/articles/tutorials/cut-paste-tr-basics ## この記事で解決できること {#intro} - `cut` でフィールド・文字位置を指定して列を抽出できる - `paste` で複数ファイルを行単位で横結合できる - `tr` で文字の変換・削除・圧縮ができる - 3 つのコマンドをパイプで組み合わせて実用的なテキスト処理ができる ::: tip **結論(用途早見表)** | コマンド | 主な用途 | | -------- | --------------------------------- | | `cut` | CSV/TSV の特定列を取り出す | | `paste` | 複数ファイルを横に並べて結合する | | `tr` | 文字を 1 文字単位で変換・削除する | ::: ::: warning **前提(対象環境)** - OS:Ubuntu(GNU coreutils 版) - macOS の `cut` / `tr` は動作が一部異なる場合がある ::: ## cut とは何か? {#what-is-cut} テキストの各行から、指定した列または文字位置を切り出すコマンド。CSV/TSV の加工や、ログから特定フィールドだけを抽出する場面で使う。 ### フィールド指定(-f / -d) `-d` で区切り文字を指定し、`-f` で取り出すフィールド番号を指定する。 ```bash # カンマ区切りの 2 列目を取り出す $ echo "Alice,30,Tokyo" | cut -d, -f2 30 # TSV(デフォルト区切りはタブ)の 1〜2 列目 $ cut -f1,2 data.tsv # 3 列目以降をすべて取り出す $ cut -f3- data.tsv ``` ### 文字位置指定(-c) バイト・文字単位でスライスする。ログの固定長フィールドに使いやすい。 ```bash # 先頭 10 文字を取り出す $ cut -c1-10 access.log # 5 文字目以降をすべて取り出す $ cut -c5- access.log ``` ::: warning `-c` はバイト数ではなく文字数を扱う(`-b` がバイト指定)。日本語を含むファイルでは `-c` が直感的。 ::: ## cut でよくはまるポイントは何か? {#cut-pitfalls} フィールド番号は 1 始まり(0 ではない)。区切り文字が連続していてもそれぞれのフィールドとして扱われるため、空フィールドがある CSV に注意する。 ```bash # フィールド番号は 1 始まり $ echo "a:b:c" | cut -d: -f1 # → a $ echo "a:b:c" | cut -d: -f2 # → b $ echo "a:b:c" | cut -d: -f3 # → c # 空フィールドもカウントされる $ echo "a::c" | cut -d: -f2 # → (空行) ``` `cut` は複数の区切り文字(連続スペースなど)をまとめて扱えない。そのような場合は `awk` が適切。 ## paste とは何か? {#what-is-paste} 複数のファイルを行ごとに横に並べて結合するコマンド。`cat` が縦(行追加)なら `paste` は横(列追加)という関係になる。 ### 基本形 ```bash # 2 つのファイルを横結合(デフォルト区切りはタブ) $ paste names.txt scores.txt Alice 95 Bob 87 Carol 72 # 区切り文字をカンマに変える $ paste -d, names.txt scores.txt Alice,95 Bob,87 Carol,72 ``` ### 1 ファイルを複数列に並べる(-s) `-s` で行を直列に並べる。1 カラムのリストを 1 行に結合したいときに使う。 ```bash $ cat items.txt apple banana cherry $ paste -s -d, items.txt apple,banana,cherry ``` ## tr とは何か? {#what-is-tr} 文字を 1 文字単位でマッピングして変換するコマンド。ファイルではなく標準入力から受け取るため、パイプと組み合わせて使う。 ### 文字変換 ```bash # 小文字 → 大文字 $ echo "hello world" | tr 'a-z' 'A-Z' HELLO WORLD # スペース → アンダースコア $ echo "foo bar baz" | tr ' ' '_' foo_bar_baz ``` ### 文字削除(-d) 指定した文字を削除する。 ```bash # 数字を削除 $ echo "abc123def456" | tr -d '0-9' abcdef # 改行を削除(複数行を 1 行にする) $ tr -d '\n' < multiline.txt ``` ### 連続文字の圧縮(-s) 同じ文字が連続している部分を 1 文字にまとめる。 ```bash # 複数スペースを 1 つにまとめる $ echo "foo bar baz" | tr -s ' ' foo bar baz # 複数改行を 1 つにまとめる $ tr -s '\n' < file.txt ``` ::: tip `tr` は正規表現を使わない。パターンマッチが必要なら `sed` や `awk` を使う。 ::: ## 3 つのコマンドをパイプで組み合わせる {#pipeline} それぞれ単体では用途が限られるが、パイプで組み合わせると強力になる。 ```bash # CSV の 2 列目を取り出して大文字に変換 $ cut -d, -f2 users.csv | tr 'a-z' 'A-Z' # TSV の 1 列目を取り出し、改行を圧縮してカンマ区切りに結合 $ cut -f1 data.tsv | tr -s '\n' | paste -s -d, # ログの IP アドレス部分だけを抽出して重複を除く $ cut -d' ' -f1 access.log | sort -u ``` ## コマンドの使い分けまとめ {#summary} | 目的 | 使うコマンド | | ------------------------------ | -------------- | | CSV/TSV の特定列を取り出す | `cut -f N -d,` | | 固定長フィールドを切り出す | `cut -c N-M` | | 複数ファイルを横結合する | `paste` | | 1 ファイルの行を横並びにする | `paste -s` | | 文字を変換する | `tr SET1 SET2` | | 特定の文字を削除する | `tr -d SET` | | 連続文字を圧縮する | `tr -s SET` | | 複数区切り文字や複雑なパターン | `awk` | ::: warning **やってはいけないこと** - `cut` でフィールド番号を 0 始まりと勘違いする - `tr` に正規表現を渡そうとする(`tr` は文字集合しか扱えない) - `paste` で行数が異なるファイルを結合する(空行が挿入される) ::: ::: tip **コピペ用:定番パターン** ```bash # CSV 2 列目を抽出 cut -d, -f2 file.csv # 先頭 5 文字を抽出 cut -c1-5 file.txt # 2 ファイルを横結合(CSV 形式) paste -d, file1.txt file2.txt # 小文字 → 大文字 tr 'a-z' 'A-Z' # スペース複数 → 1 つに圧縮 tr -s ' ' ``` ::: ## 次に読む {#next} - [sort と uniq の使い方 - データを並べ替えて重複を削る](/articles/tutorials/sort-uniq-basics) - [sed 入門 - ストリームエディタでテキスト置換](/articles/tutorials/sed-basics) - [find・grep・awk の使い方入門 - 正規表現の基礎から](/articles/tutorials/find-grep-awk-basics) # date コマンド入門 - 日付の書式・計算・タイムゾーン操作 Source: https://penguin-gym-linux.com/articles/tutorials/date-formatting ## date コマンドってなに? {#intro} ::: dialogue @lina: ライニー先輩、バックアップのファイル名に「今日の日付」を入れたいです。毎回手で打つしかないのでしょうか。 @linny: そこで `date` コマンドだよ。打つだけで、今の日時が出るんだ。 @linny: `date +%Y%m%d` と書けば `20260605` の形に整えてくれる。ファイル名にもそのまま使えるんだ。 @lina: コマンド 1 つで日付が作れるんですね。 @linny: そう。しかも「3 日前」「来週の金曜」のような計算もできる。タイムゾーンの切りかえも同じコマンドでできるよ。 ::: **`date`** は、現在の日時を表示するコマンドです。好きな書式に整えることもできます。 ログの時刻の記録、バックアップのファイル名、スクリプトの中の日付の計算に使います。仕事の場面で毎日のように出てくるコマンドです。 ::: tip **言葉の整理(先に取りちがえを防ぎます)** - **書式**:日付をどの形で出すかの指定です。「フォーマット」も同じものを指します。 - **書式指定子**:`%Y` のように `%` ではじまる記号です。年や月など、日付の部品を 1 つずつ表します。 - **ロケール**:言語と地域の設定です。曜日名などの表示がこれで変わります。 - **タイムゾーン**:地域ごとの時刻の差です。日本は JST、世界標準は UTC と呼びます。 - **エポック秒**:1970 年 1 月 1 日からの経過秒数です。「Unix 時間」も同じものを指します。 ::: ::: warning **前提(対象環境)** - OS:Ubuntu / 一般的な Linux - GNU coreutils の `date`(macOS の BSD 版は書き方がちがいます。第 7 章で説明します) - この記事の表示は日本時間(JST)です。タイムゾーンがちがう環境では時刻もずれます ::: ::: tip **一言でいうと** `date +書式` で「好きな形の日付文字列」が作れる。 例: `date +%Y-%m-%d` → `2026-06-05` ::: ## この記事でわかること {#what-you-will-learn} - `date` で今の日時を表示できます。`date +%F` などで好きな形に整えられます - `%Y` `%m` `%d` などの **書式指定子** を組み合わせて、自由な形を作れます - `date -d "3 days ago"` のように、**昨日・N 日後・来週** などの日付を計算できます - `date +%s` でエポック秒を取り出し、`date -d @秒数` で日付に戻せます - `TZ='UTC' date` や `date -u` で **タイムゾーンを切りかえ** られます - Linux(GNU date)と macOS(BSD date)で書き方がちがう点に気をつけられます ## 1. まずは現在時刻を表示する {#basic} > **結論**: 引数なしの `date` で現在の日時が出る。表示形式を変えたいときは `date +書式` を使う。 オプションを何も付けずに実行すると、今の日時が出ます。 ```bash $ date ``` ```output 2026年 6月 5日 金曜日 15:52:59 JST ``` ::: tip 表示される文字列は、システムのロケールとタイムゾーンの設定で変わります。曜日や `JST` の部分がその例です。 英語環境なら `Fri Jun 5 15:52:59 JST 2026` のように出ます。 ::: 「年月日だけ欲しい」「時刻だけ欲しい」というときは、次の章の書式指定を使います。 ## 2. 書式を自由に変える(%Y %m %d…) {#format} > **結論**: `date +` の後ろに `%Y`(年)`%m`(月)`%d`(日)などの記号を並べると、好きな形に整形できる。 `+` の後ろに **書式指定子** を書きます。すると、その形で日付が出ます。 ```bash $ date +%Y-%m-%d ``` ```output 2026-06-05 ``` ```bash $ date "+%Y/%m/%d %H:%M:%S" ``` ```output 2026/06/05 15:52:59 ``` ::: warning 書式の中にスペースを入れたいときは、全体を `"..."`(ダブルクォート)で囲みます。 囲まないと、シェルはスペースの位置で 2 つに区切ります。すると `date` は 2 つ目を余分な引数と見なし、エラーになります。 ```bash $ date +%Y-%m-%d %H:%M:%S ``` ```output date: extra operand '%H:%M:%S' Try 'date --help' for more information. ``` ::: ### 2-1. よく使う書式指定子 | 指定子 | 意味 | 例 | | ------ | ------------ | ------------ | | `%Y` | 西暦4桁 | `2026` | | `%m` | 月(01〜12) | `06` | | `%d` | 日(01〜31) | `05` | | `%H` | 時(00〜23) | `15` | | `%M` | 分(00〜59) | `52` | | `%S` | 秒(00〜60) | `59` | | `%A` | 曜日(フル) | `Friday` | | `%a` | 曜日(短縮) | `Fri` | | `%j` | 年間通算日 | `156` | | `%s` | エポック秒 | `1780642379` | ::: dialogue @lina: 指定子がたくさんあって、覚えられる気がしません。 @linny: 全部覚えなくて大丈夫だよ。よく使う組み合わせには **ショートカット** が用意されているんだ。 ::: ### 2-2. 便利なショートカット ```bash $ date +%F ``` ```output 2026-06-05 ``` ```bash $ date +%T ``` ```output 15:52:59 ``` - `%F` は `%Y-%m-%d` と同じ(ISO 8601 形式の日付) - `%T` は `%H:%M:%S` と同じ(時刻) ::: tip 迷ったら `%F`(日付)と `%T`(時刻)を覚えてください。たいていの場面はこの 2 つで足ります。 ::: ### 2-3. リナの失敗:`%m` と `%M` を取りちがえる ::: dialogue @lina: 先輩、年月日を出したのに、月のところが `52` になっています。そんな月はないですよね。 ::: ```bash $ date +%Y-%M-%d ``` ```output 2026-52-05 ``` ::: dialogue @lina: 日付が壊れてしまったのでしょうか。 @linny: 壊れていないよ。よく見て。大文字の `%M` を書いているね。 @linny: 小文字の `%m` が月、大文字の `%M` が分なんだ。今が 15 時 52 分だから、月の位置に `52` が入ったんだよ。 @lina: えっ、大文字と小文字で意味がちがうんですか。びっくりしました。 @linny: そう。日付は小文字、時こくは大文字と覚えるといい。`%Y` だけは年なのに大文字で、そこだけ例外だね。 @lina: 数字が出ていたので気づきませんでした。エラーにならないぶん、こわいですね。 @linny: そのとおり。だから書式を変えたら、まず画面に出して見てみる。これがいちばん確実な防ぎ方だよ。 ::: ::: warning `%m`(月)と `%M`(分)、`%d`(日)と `%D`(月/日/年)は取りちがえやすい組み合わせです。まちがえてもエラーにはならず、それらしい数字が出ます。だから目で確かめる習慣が大切です。 ::: ## 3. ファイル名・ログに使うテンプレート {#templates} > **結論**: `$(date +%Y%m%d)` をファイル名に埋め込むと、日付つきのファイルが自動で作れる。 `$(...)` と書くと、コマンドの結果を文字列に埋めこめます。これをコマンド置換と呼びます。バックアップで定番のテクニックです。 ```bash $ cp data.db "backup-$(date +%Y%m%d).db" ``` これで `backup-20260605.db` という名前のファイルができます。 ```bash $ echo "[$(date '+%Y-%m-%d %H:%M:%S')] 処理を開始" >> app.log ``` ログに自分で時刻を付けたいときの形です。出力の例を見てみましょう。 ```output [2026-06-05 15:52:59] 処理を開始 ``` ::: warning ファイル名に `%T` をそのまま使うのは避けてください。`%T` にはコロン(`:`)が入ります。環境やファイルシステムによっては、コロンを含む名前があつかいにくくなります。 ファイル名に時刻を入れるなら `%H%M%S` のように区切りなしで書くのが無難です。 ::: ## 4. 日付を計算する(昨日・3日後・N日前) {#calc} > **結論**: `date -d "文字列"` を使うと、「昨日」「3日後」「来週の金曜」などを英語で指定して計算できる。 `-d`(または `--date`)オプションに、計算したい日付を **英語の表現** で渡します。 ```bash $ date -d "yesterday" +%F ``` ```output 2026-06-04 ``` ```bash $ date -d "3 days ago" +%F ``` ```output 2026-06-02 ``` ```bash $ date -d "next friday" +%F ``` ```output 2026-06-12 ``` 基準になる日付を自分で決めて計算することもできます。 ```bash $ date -d "2025-01-01 +30 days" +%F ``` ```output 2025-01-31 ``` ::: dialogue @lina: 「30 日後」を自分で計算すると、月をまたぐところで必ずまちがえます。 @linny: そこを `date` が正確にやってくれる。月末の日数も、うるう年も計算に入っているんだ。 @linny: 手で数えるより、ずっと安全だよ。 ::: ::: tip **よく使う指定の例** - `tomorrow` / `yesterday`(明日 / 昨日) - `3 days ago` / `2 weeks` / `1 month`(N日前 / N週後 / Nか月後) - `next monday` / `last sunday`(次の月曜 / 前の日曜) ::: ::: warning これらの **相対日付の計算は GNU date(Linux)の機能** です。macOS では構文がちがうため動きません。くわしくは [7 章](#bsd) で説明します。 ::: ## 5. エポック秒(Unix 時間)との変換 {#epoch} > **結論**: `date +%s` で「1970年からの経過秒数」を取得でき、`date -d @秒数` で日付に戻せる。 **エポック秒(Unix 時間)** とは、1970 年 1 月 1 日 00:00:00 UTC からの経過秒数です。 プログラムやログでは、この形で時刻を残すことがよくあります。地域や書き方のちがいに左右されないからです。 ```bash $ date +%s ``` ```output 1780642379 ``` エポック秒を人が読める日付に戻すときは、数字の前に `@` を付けます。 ```bash $ date -d @1700000000 "+%Y-%m-%d %H:%M:%S" ``` ```output 2023-11-15 07:13:20 ``` ::: warning 表示される日時は、パソコンのタイムゾーン設定で変わります。上の出力は日本時間(JST)のものです。 UTC の環境では 9 時間前になり、`2023-11-14 22:13:20` と出ます。Docker コンテナやクラウドのサーバーは UTC のことが多い点に注意してください。 どの環境でも同じ結果にしたいときは `TZ` を指定します。 ```bash $ TZ='Asia/Tokyo' date -d @1700000000 "+%Y-%m-%d %H:%M:%S" ``` ::: ::: tip ログに `1700000000` のような数字だけが記録されていることがあります。そのままでは読めません。`date -d @その数字` を使えば、すぐ日付に変換できます。 ::: ## 6. タイムゾーンを切り替える(TZ / -u) {#timezone} > **結論**: `date -u` で UTC、`TZ='地域名' date` で任意のタイムゾーンの時刻を表示できる。 `-u` を付けると **UTC(協定世界時)** で表示されます。 ```bash $ date -u "+%Y-%m-%d %H:%M %Z" ``` ```output 2026-06-05 06:52 UTC ``` 特定の地域の時刻を見たいときは、`TZ` という環境変数をその場で指定します。 ```bash $ TZ='America/New_York' date "+%Y-%m-%d %H:%M %Z" ``` ```output 2026-06-05 02:52 EDT ``` ```bash $ TZ='Asia/Tokyo' date "+%Y-%m-%d %H:%M %Z" ``` ```output 2026-06-05 15:52 JST ``` ::: dialogue @lina: `TZ=...` をコマンドの前に書くと、その 1 回だけタイムゾーンが変わるのですか。 @linny: そう。`TZ='地域名' date` の形にすると、その `date` を実行する間だけ有効になるんだ。 @linny: システム全体の設定は変わらない。だから安心して試せるよ。 @lina: 海外のサーバーの時刻を確かめたいときに便利ですね。 ::: ::: tip `TZ` に書ける地域名は `timedatectl list-timezones` で一覧を見られます。`Asia/Tokyo` `America/New_York` `Europe/London` などが例です。 `TZ` は環境変数です。[環境変数の使い方](/articles/tutorials/environment-variables) も読むと理解が深まります。 ::: ## 7. macOS(BSD date)との違いに注意 {#bsd} > **結論**: Linux の date は GNU 版。macOS の date は BSD 版で、日付計算やエポック変換の構文が違う。 この記事のコマンドは **Ubuntu などの Linux(GNU coreutils の date)** を前提にしています。 macOS のターミナルに入っている `date` は **BSD 版** です。そのため一部のオプションが異なります。 | やりたいこと | Linux(GNU) | macOS(BSD) | | -------------------- | ---------------------- | ------------------------------------ | | 3日前を計算 | `date -d "3 days ago"` | `date -v-3d` | | エポック秒→日付 | `date -d @1700000000` | `date -r 1700000000` | | 文字列から日付を解釈 | `date -d "2025-01-01"` | `date -j -f "%Y-%m-%d" "2025-01-01"` | ::: warning ネットで見つけた `date -d ...` が macOS で `illegal option` と言われることがあります。その場合は BSD 版を使っている可能性が高いです。 `date --version` を実行してください。`GNU coreutils` と出れば GNU 版です。 ::: ## 8. ミニ課題:実際にやってみよう {#exercise} > **結論**: 書式の組み立て・日付の計算・エポック秒の変換の 3 問で、`date` の基本を手で確かめる。 ::: dialogue @lina: 知識は入りました。手を動かして確かめたいです。 @linny: いいね、3 問用意したよ。ターミナルで試してみて。 ::: **課題 1**:今日の日付を `2026/06/05` の形(スラッシュ区切り)で表示しよう。 :::details ヒント 1(方向づけ)を見る `+` の後ろに、年・月・日の記号をスラッシュでつないで並べます。スペースは入りません。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `date` です。年・月・日の記号は第 2 章の表にあります。月が小文字である点に注意してください。 ::: :::details 答えを見る ```bash $ date +%Y/%m/%d ``` ```output 2026/06/05 ``` ::: **課題 2**:今日の 3 日後の日付を `YYYY/MM/DD` の形で表示しよう。 :::details ヒント 1(方向づけ)を見る 2 つのことを同時に行います。日付を計算するオプションと、形を整える書式の両方を書きます。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `date` です。日付を計算するオプションは第 4 章、書式の書き方は第 2 章にあります。 ::: :::details 答えを見る ```bash $ date -d "3 days" "+%Y/%m/%d" ``` ```output 2026/06/08 ``` `"3 days"` は「今から 3 日後」という意味です。`"+3 days"` と書いても同じ結果になります。 ::: **課題 3**:エポック秒 `1700000000` を、日本時間で `YYYY-MM-DD` の形の日付に戻そう。 :::details ヒント 1(方向づけ)を見る 数字をそのまま渡すと日付として読まれません。エポック秒だと伝える印を、数字の前に付けます。あわせて、タイムゾーンを日本時間に固定します。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `date` です。エポック秒を渡す印は第 5 章、タイムゾーンの指定は第 6 章にあります。 ::: :::details 答えを見る ```bash $ TZ='Asia/Tokyo' date -d @1700000000 +%F ``` ```output 2023-11-15 ``` `%F` は `%Y-%m-%d` と同じ意味です。`TZ` を付けたのは、どの環境でも同じ日付になるようにするためです。`TZ` なしで実行すると、UTC の環境では `2023-11-14` になります。 ::: ## 9. 振り返り {#review} ::: dialogue @lina: 整理します。`date +書式` で形を決めて、`date -d` で日付を計算するんですね。 @linny: そのとおり。この 2 つは同時に使えるから、計算した結果をそのまま好きな形で出せるよ。 @lina: 書式では `%m` が月、`%M` が分。ここは必ず目で確かめます。 @linny: 完璧だね。エポック秒に戻すときは `@` を付けるのも忘れずに。 ::: ## 今日の 3 行まとめ {#three-lines} 1. `date +%F` で日付、`date +%T` で時刻。迷ったらこの 2 つ 2. `%m` は月、`%M` は分。まちがえてもエラーが出ないので目で確かめる 3. `date -d "3 days ago"` で日付を計算し、`date -d @秒数` でエポック秒を日付に戻す ## まとめ {#summary} | やること | コマンド | | ---------------------- | -------------------------------- | | 現在の日時を表示 | `date` | | 年月日だけ表示 | `date +%F`(= `date +%Y-%m-%d`) | | 時刻だけ表示 | `date +%T` | | ファイル名用の日付 | `date +%Y%m%d` | | 日付を計算する | `date -d "3 days ago" +%F` | | エポック秒を取得 | `date +%s` | | エポック秒を日付に戻す | `date -d @1700000000` | | UTC で表示 | `date -u` | | タイムゾーンを指定 | `TZ='Asia/Tokyo' date` | ::: tip **今すぐ試せる 3 ステップ** 1. `date +%F` で今日の日付を表示する 2. `date -d "tomorrow" +%F` で明日の日付を計算する 3. `cp なにか.txt "backup-$(date +%Y%m%d).txt"` で日付つきファイルを作る ::: ## 次に読む {#next} - [環境変数の使い方](/articles/tutorials/environment-variables) - [cron が動かない原因と対処法](/articles/tutorials/cron-basics) # dd コマンド入門 - ディスク複製・ISO 書き込みと安全な使い方 Source: https://penguin-gym-linux.com/articles/tutorials/dd-command-basics ## この記事で解決できること {#intro} - `dd` でディスクやパーティションを **イメージファイルに複製** できる - ダウンロードした ISO を **USB メモリに書き込んで** 起動メディアを作れる - `of=` の指定ミスで **データを全消失させない安全手順** が身につく ::: danger **最重要の注意** `dd` は `of=`(出力先)に書き込むとき **確認なしで上書き** する。Undo も「ゴミ箱」も無い。デバイス名(`/dev/sda` 等)を 1 文字間違えると、稼働中のシステムディスクを破壊しうる。実行前に必ず `lsblk` で対象を確認すること。 ::: ::: tip **結論(実務の型)** 1. `lsblk` で **対象デバイスを特定**(容量・マウント状況で照合) 2. `sudo dd if=<入力> of=<出力> bs=4M status=progress conv=fsync` 3. 完了後 `sync` で **書き込みバッファを確実に flush** ::: ::: warning **前提(対象環境)** - OS:Ubuntu / 一般的な Linux ディストリビューション - `dd` は GNU coreutils 同梱。追加インストール不要 - デバイス操作には `sudo`(root 権限)が必要 ::: ## dd コマンドの基本構文は? {#syntax} > **結論**: `dd if=入力 of=出力 bs=ブロックサイズ` が基本形。引数は `key=value` 形式で、ハイフン付きオプションではない点が他コマンドと異なる。 `dd` は入力(`if`)から読み、出力(`of`)へ書き込むだけのシンプルなツール。引数はすべて `key=value` 形式で記述する。 ```bash $ dd if=input.img of=/dev/sdb bs=4M status=progress ``` 主要オペランドの意味: - `if=`(input file):入力元。省略時は標準入力 - `of=`(output file):出力先。省略時は標準出力 - `bs=`(block size):一度に読み書きするバイト数(例 `4M` = 4 MiB) - `count=`:コピーするブロック数(先頭だけ複製したいとき) - `status=progress`:転送量・速度をリアルタイム表示 - `conv=fsync`:完了時に物理メディアへの書き込みを保証 ::: tip `bs` は性能に直結する。デフォルトの 512 バイトは遅い。USB やディスク相手なら `bs=4M` 程度が無難。 ::: ## ディスクやドライブをどう確認するのか?(最重要) {#identify} > **結論**: `lsblk` で容量とマウントポイントを照合し、書き込み先デバイス名を確定する。`/dev/sda`(ディスク全体)と `/dev/sda1`(パーティション)の違いを必ず意識する。 `dd` 事故の大半は **デバイス名の取り違え**。書き込み前に必ず確認する。 ```bash $ lsblk ``` ```output NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 238.5G 0 disk ├─sda1 8:1 0 512M 0 part /boot/efi └─sda2 8:2 0 238G 0 part / sdb 8:16 1 28.7G 0 disk └─sdb1 8:17 1 28.7G 0 part /media/user/USB ``` 読み取り方: - `sda`:238.5G で `/` にマウント → **システムディスク。絶対に書き込まない** - `sdb`:28.7G・`RM`(リムーバブル)が `1` → 差し込んだ USB メモリ - 末尾に数字が無い `sdb` は **ディスク全体**、`sdb1` は **その中のパーティション** ::: warning USB を挿す前後で `lsblk` を 2 回実行し、**新しく増えたデバイス**を対象にすると取り違えにくい。`SIZE` 列の容量照合も併用する。 ::: ::: danger ISO 書き込みでは **パーティション(`sdb1`)ではなくディスク全体(`sdb`)** を `of=` に指定する。パーティションに書くと起動メディアにならない。 ::: ## ISO イメージを USB に書き込むには? {#iso} > **結論**: 書き込み先 USB を `umount` してから `sudo dd if=xxx.iso of=/dev/sdX bs=4M status=progress` を実行する。`of=` はパーティション番号を付けないディスク全体を指定する。 Ubuntu などの起動 USB を作る定番手順。 ### 1. マウントを解除する 書き込み中にマウントされていると失敗・破損の原因になる。 ```bash $ sudo umount /dev/sdb1 ``` ### 2. dd で書き込む ```bash $ sudo dd if=ubuntu-24.04.iso of=/dev/sdb bs=4M status=progress conv=fsync ``` ```output 1466 MiB / 1466 MiB の転送が完了 1538000000 bytes (1.5 GB, 1.4 GiB) copied, 142 s, 10.8 MB/s ``` ### 3. 同期して安全に抜く `dd` 完了後もカーネルのバッファに残りがある場合がある。`sync` で確実に flush する。 ```bash $ sync ``` ::: tip `conv=fsync` と末尾の `sync` は役割が近いが、両方付けておくと「コマンドは終わったのに実際は書き込み途中」という事故を防げる。 ::: ## ディスク全体をイメージファイルに複製するには? {#image} > **結論**: `if=` にデバイス、`of=` にイメージファイルを指定すると丸ごとバックアップできる。空き領域も含むため、未使用領域は `gzip` で圧縮すると小さくなる。 ディスクやパーティションを 1 ファイルにバックアップする。 ```bash $ sudo dd if=/dev/sdb of=usb-backup.img bs=4M status=progress ``` 圧縮しながら保存(空き領域が多いとき有効): ```bash $ sudo dd if=/dev/sdb bs=4M status=progress | gzip > usb-backup.img.gz ``` ::: warning イメージファイルは **元デバイスと同じ容量** を消費する(28 GB の USB → 28 GB のファイル)。保存先の空き容量を `df -h` で先に確認すること。 ::: ## イメージからディスクへ書き戻すには? {#restore} > **結論**: `if=` と `of=` を入れ替えるだけ。圧縮イメージは `gunzip` でパイプ展開しながら書き戻す。書き戻し先の取り違えに改めて注意する。 ```bash # 非圧縮イメージ $ sudo dd if=usb-backup.img of=/dev/sdb bs=4M status=progress conv=fsync # 圧縮イメージ $ gunzip -c usb-backup.img.gz | sudo dd of=/dev/sdb bs=4M status=progress conv=fsync ``` ::: danger 書き戻しは **複製と逆方向**。`of=` がバックアップ取得時の `if=` と同じデバイスか、`lsblk` で再確認してから実行する。 ::: ## 転送速度を上げる・進捗を見るには? {#performance} > **結論**: `bs` を大きく(`4M`〜`64M`)すると速くなる。進捗は `status=progress`、コマンドが対応しない古い環境では別端末から `kill -USR1` でも確認できる。 ### ブロックサイズの調整 ```bash $ sudo dd if=/dev/sdb of=backup.img bs=64M status=progress ``` `bs` が小さいとシステムコール回数が増えて遅くなる。大きすぎてもメモリを無駄に使うため、`4M`〜`64M` が実用域。 ### 進捗を後から確認する(status=progress が無い場合) 別のターミナルから実行中の `dd` にシグナルを送ると、現在の転送量を表示する。 ```bash $ sudo kill -USR1 $(pgrep -x dd) ``` ::: tip GNU coreutils 8.24 以降は `status=progress` が使える。まずこちらを使い、`kill -USR1` は古い環境向けの代替手段と覚えておく。 ::: ## dd の事故パターンと対策は? {#pitfalls} > **結論**: 事故の原因は「`of=` の取り違え」「`if`/`of` の逆指定」「マウント中の書き込み」の 3 つにほぼ集約される。実行前チェックで防げる。 | 事故 | 原因 | 対策 | | ------------------------ | -------------------------------- | -------------------------------------- | | システムディスクを破壊 | `of=` のデバイス名取り違え | 実行前に `lsblk` で容量・マウント照合 | | バックアップが空になる | `if=` と `of=` を逆指定 | 「読む側 = if、書く側 = of」を声に出す | | 書き込んだのに起動しない | パーティション(`sdb1`)に書いた | ISO はディスク全体(`sdb`)へ | | 完了後に内容が壊れている | バッファ未 flush で USB を抜いた | `conv=fsync` + `sync` を徹底 | ::: danger **やってはいけないこと** - `lsblk` で確認せずに `of=/dev/sdX` を実行する - マウントしたまま `dd` で書き込む - `dd` 完了直後に `sync` 無しで USB を引き抜く ::: ::: tip **コピペ用:安全テンプレ** ```bash # 1. デバイス確認(必ず最初に) lsblk # 2. ISO を USB へ書き込む(umount 済み前提) sudo dd if=image.iso of=/dev/sdX bs=4M status=progress conv=fsync # 3. 完了後に同期 sync ``` `/dev/sdX` は `lsblk` で確認した実際のデバイス名に置き換える。 ::: ## 次に読む {#next} - [ディスク管理入門 - fdisk/lsblk でストレージを確認する](/articles/tutorials/disk-management-basics) - [du と df の違い - ディスク容量を正しく測る](/articles/tutorials/du-df-difference) - [tar コマンドの使い方 - 圧縮・解凍・よくある事故の防ぎ方](/articles/tutorials/tar-basics) - [ディスクがいっぱいになったときの対処](/articles/troubleshooting/no-space-left-on-device) # diff/patch 入門 - 差分の取得と適用 Source: https://penguin-gym-linux.com/articles/tutorials/diff-patch-basics ## この記事で解決できること {#intro} - `diff` コマンドで **2つのファイルの差分を取り出す方法**が分かる - `diff -u`(unified 形式)の **出力の読み方**が分かる - `patch` コマンドで **差分を別のファイルに適用する方法**が身につく - 逆適用(`patch -R`)でパッチを **元に戻す方法**が分かる ::: tip **結論(実務の型)** - **差分確認**: `diff -u old_file new_file` - **パッチ保存**: `diff -u old_file new_file > changes.patch` - **パッチ適用**: `patch -p0 < changes.patch` - **逆適用**: `patch -R -p0 < changes.patch` ::: ::: warning **前提(対象環境)** - OS: Ubuntu / Debian 系 Linux - `diff` / `patch` コマンドが使用可能(標準搭載) ::: ## diff とは何か? {#what-is-diff} `diff` は 2 つのファイルを比較して、**どの行がどう変わったか**を出力するコマンドだ。ソースコードの変更点確認や設定ファイルの比較に日常的に使われる。引数に渡した2つのファイルを行単位で比較し、差分だけを標準出力に出力する。 ## diff の基本的な使い方 {#diff-basic} ### 2つのファイルを比較する ```bash $ diff old.txt new.txt ``` 変更がない場合は何も出力されない。変更がある場合は以下のような出力になる。 ``` 2c2 < 古い内容 --- > 新しい内容 ``` この「normal 形式」は読みにくい。実務では **unified 形式**を使う。 ### unified 形式(-u オプション) {#unified} `-u` オプションを付けると unified 形式で出力される。パッチファイルの標準形式で最も広く使われている。 ```bash $ diff -u old.txt new.txt ``` 出力例: ```diff --- old.txt 2026-06-01 10:00:00.000000000 +0900 +++ new.txt 2026-06-01 10:05:00.000000000 +0900 @@ -1,5 +1,5 @@ 行1(変更なし) -古い行2 +新しい行2 行3(変更なし) ``` **読み方:** | 記号 | 意味 | | --------------- | -------------------------- | | `---` | 変更前のファイル | | `+++` | 変更後のファイル | | `@@` | 変更箇所の行番号情報 | | `-` | 削除された行 | | `+` | 追加された行 | | ` `(スペース) | 変更なし(コンテキスト行) | ::: tip `@@ -1,5 +1,5 @@` は「変更前ファイルの1行目から5行 / 変更後ファイルの1行目から5行」を示す。この前後3行がコンテキスト行として表示される。 ::: ### ディレクトリを再帰的に比較する(-r オプション) {#recursive} ```bash $ diff -ur dir_old/ dir_new/ ``` `-u`(unified 形式)と `-r`(再帰)を組み合わせてディレクトリ全体の差分を一度に確認できる。 ## diff の出力をパッチファイルに保存する {#save-patch} リダイレクトで保存するだけだ。 ```bash # ファイル単体 $ diff -u old.txt new.txt > changes.patch # ディレクトリ全体 $ diff -ur dir_old/ dir_new/ > project.patch ``` このファイルが `patch` コマンドに渡す「パッチファイル」になる。 ## patch とは何か? {#what-is-patch} `patch` は `diff` が生成した差分を、別のファイルに**適用する**コマンドだ。ソフトウェアのバグ修正やセキュリティパッチを配布する際に広く使われる。`diff` で差分を生成し、`patch` で適用する、という組み合わせが基本形だ。 ## patch の基本的な使い方 {#patch-basic} ### -p オプションとは何か? {#p-option} `-p` はパッチ内のパス(`--- a/path/to/file.txt` の部分)から先頭のディレクトリをいくつ除去するかを指定する。 | オプション | 動作 | | ---------- | -------------------------------------------- | | `-p0` | パスをそのまま使う | | `-p1` | 先頭の1要素を除去(`a/` や `b/` を取り除く) | ```diff --- a/path/to/file.txt ← -p1 を使うと "path/to/file.txt" として解釈 +++ b/path/to/file.txt ``` Git が生成するパッチは `a/` `b/` プレフィックスを付けるため、`-p1` が一般的だ。 ### シンプルなパッチ適用(-p0) {#apply-p0} `diff -u` で直接作ったパッチを適用する場合: ```bash $ patch -p0 < changes.patch ``` 適用前に `--dry-run` で確認する。 ```bash $ patch --dry-run -p0 < changes.patch ``` ::: tip `--dry-run` は実際にはファイルを変更しない。適用できるかどうかだけ確認できるため、本番実行前の確認に必須だ。 ::: ### Git 形式のパッチ(-p1) {#apply-p1} Git リポジトリや多くのオープンソースプロジェクトのパッチには `-p1` を使う。 ```bash $ patch -p1 < project.patch ``` ## パッチの逆適用(元に戻す) {#revert} `-R` オプションでパッチを逆適用(ロールバック)できる。 ```bash $ patch -R -p0 < changes.patch ``` ::: warning 逆適用はパッチ内のコンテキスト行がファイルと一致する場合のみ成功する。すでに対象ファイルが別の変更を受けていると失敗することがある。 ::: ## 実践的なワークフロー {#workflow} ### 設定ファイルの変更を共有する ```bash # 1. 元のファイルをバックアップ $ cp config.conf config.conf.orig # 2. ファイルを編集 $ vim config.conf # 3. 差分をパッチファイルに保存 $ diff -u config.conf.orig config.conf > config-fix.patch # 4. 受け取った側がパッチを適用 $ patch --dry-run -p0 < config-fix.patch $ patch -p0 < config-fix.patch ``` ### ソフトウェアソースへのパッチ適用 ```bash # プロジェクトルートで実行 $ patch -p1 < bugfix.patch # 問題があれば逆適用 $ patch -R -p1 < bugfix.patch ``` ## まとめ:diff/patch の要点 {#summary} | コマンド | 用途 | | --------------------------------- | ------------------------ | | `diff -u old new` | unified 形式で差分確認 | | `diff -ur dir1/ dir2/` | ディレクトリ全体を比較 | | `diff -u old new > fix.patch` | パッチファイルを生成 | | `patch --dry-run -p0 < fix.patch` | 適用前の確認 | | `patch -p0 < fix.patch` | パッチを適用(通常形式) | | `patch -p1 < fix.patch` | パッチを適用(Git 形式) | | `patch -R -p0 < fix.patch` | パッチを逆適用 | ::: tip **コピペ用テンプレ** ```bash # パッチ生成 diff -u original.txt modified.txt > changes.patch # 適用前確認 patch --dry-run -p0 < changes.patch # 適用 patch -p0 < changes.patch # 逆適用(元に戻す) patch -R -p0 < changes.patch ``` ::: ## 次に読む {#next} - [tarコマンドの使い方](/articles/tutorials/tar-basics) - [Git + Linux の基本操作](/articles/tutorials/git-linux-basics) - [sed 入門 - ストリームエディタでテキスト置換](/articles/tutorials/sed-basics) # ディスク管理入門 - fdisk/lsblkでストレージを確認する Source: https://penguin-gym-linux.com/articles/tutorials/disk-management-basics ## この記事で解決できること {#intro} - `fdisk -l` と `lsblk` でストレージ構成を正しく読み取れる - ブロックデバイス・パーティション・マウントポイントの関係が分かる - `df` と `du` をどこで使い分けるかが分かる ::: tip **結論** - **ディスク構成の確認** → `lsblk`(ツリーで一目瞭然・root 不要) - **パーティションの詳細** → `sudo fdisk -l`(セクタ・タイプまで確認) - **空き容量** → `df -h`(マウント済みFS単位) - **使用量の内訳** → `du -sh *`(ディレクトリ単位) ::: ## lsblk とは? ブロックデバイスをツリーで把握する {#lsblk} `lsblk`(list block devices)はディスク・パーティション・マウントポイントをツリー形式で表示するコマンドです。root 権限不要で実行できるため、ストレージ構成を確認するときの最初の一手として使えます。 ```bash $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 20G 0 disk ├─sda1 8:1 0 1G 0 part /boot ├─sda2 8:2 0 2G 0 part [SWAP] └─sda3 8:3 0 17G 0 part / sdb 8:16 0 50G 0 disk └─sdb1 8:17 0 50G 0 part /data ``` **列の読み方:** | 列 | 意味 | | ----------- | ---------------------------------------------------------- | | NAME | デバイス名(sda = 1枚目の SATA/SCSI/NVMe ディスク) | | SIZE | デバイスのサイズ | | TYPE | `disk`(物理ディスク)/ `part`(パーティション)/ `lvm` 等 | | MOUNTPOINTS | マウント先(空欄 = 未マウント) | ### -f オプションでファイルシステム情報も表示する {#lsblk-f} ```bash $ lsblk -f NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS sda ├─sda1 ext4 1.0 a1b2c3d4-... 800M 20% /boot ├─sda2 swap 1 [SWAP] └─sda3 ext4 1.0 e5f6a7b8-... 14.2G 16% / ``` `-f` オプションでファイルシステム種別(FSTYPE)と UUID が確認できます。UUID は `/etc/fstab` のマウント設定を確認・編集するときに必要になります。 ## fdisk -l とは? パーティション詳細を確認する {#fdisk} `fdisk -l`(list)はディスクのパーティションテーブルを詳細表示します。セクタ数・パーティションタイプ(Linux / Linux swap / EFI System 等)が確認できます。実行には root 権限が必要です。 ```bash $ sudo fdisk -l /dev/sda Disk /dev/sda: 20 GiB, 21474836480 bytes, 41943040 sectors Disk model: Virtual disk Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: gpt Disk identifier: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Device Start End Sectors Size Type /dev/sda1 2048 2099199 2097152 1G Linux filesystem /dev/sda2 2099200 6293503 4194304 2G Linux swap /dev/sda3 6293504 41943006 35649503 17G Linux filesystem ``` **注目すべき情報:** - **Disklabel type**: `gpt`(GUID Partition Table)または `dos`(MBR)。新しい環境は GPT が主流 - **Type**: パーティションの用途(`Linux filesystem` / `Linux swap` / `EFI System` 等) - **Start / End / Sectors**: パーティション境界。ディスク障害の診断時に参照する ::: tip 特定ディスクのみ確認する場合は `sudo fdisk -l /dev/sda` のようにデバイス名を指定する。全ディスクを一括確認するには引数なしで `sudo fdisk -l` を実行する。 ::: ::: warning `fdisk` はパーティション**編集**もできるコマンドです。`-l` オプション(一覧表示)以外の操作は誤ると復旧困難になります。本記事では読み取り操作のみ扱います。 ::: ## df と du との使い分けは? {#df-du} `lsblk` と `fdisk` はディスクのハードウェア構成を見るコマンドです。`df` と `du` はファイルシステムの空き容量・使用量を確認するコマンドで、役割が異なります。 | コマンド | 見るもの | 典型的な用途 | | ---------- | -------------------------------- | -------------------------------- | | `lsblk` | ブロックデバイスのツリー構造 | ディスク構成の把握 | | `fdisk -l` | パーティションテーブルの詳細 | パーティション境界・タイプの確認 | | `df -h` | マウント済みFSの使用量・空き容量 | どのFSが残り少ないかの確認 | | `du -sh` | ディレクトリ・ファイルの使用量 | どのディレクトリが大きいかの調査 | ```bash # ファイルシステム単位の空き容量確認 $ df -h Filesystem Size Used Avail Use% Mounted on /dev/sda3 17G 2.7G 13G 17% / /dev/sda1 974M 189M 718M 21% /boot /dev/sdb1 50G 1.2G 47G 3% /data # 直下のディレクトリ別使用量 $ du -sh /var/* 48M /var/cache 1.2G /var/log 8.0K /var/mail ``` ::: tip 「`df` では空き容量があるのに書き込めない」場合は inode 枯渇の可能性があります。`df -i` で inode 使用状況を確認してください。 ::: ## デバイス名の命名規則 {#naming} ディスクデバイスの命名はハードウェアによって異なります。混乱しやすい点なので整理しておきます。 | プレフィックス | 対象 | 例 | | -------------- | -------------------------------------- | ------------------ | | `/dev/sd*` | SATA / SCSI / USB / 多くの仮想ディスク | sda, sdb, sdc | | `/dev/nvme*` | NVMe SSD | nvme0n1, nvme0n1p1 | | `/dev/vd*` | KVM/QEMU 仮想ディスク | vda, vdb | | `/dev/xvd*` | Xen 仮想ディスク | xvda | NVMe の場合、パーティションは `p` + 番号で表現されます(`nvme0n1p1` = 1枚目の NVMe の 1番パーティション)。 ```bash $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS nvme0n1 259:0 0 256G 0 disk ├─nvme0n1p1 259:1 0 512M 0 part /boot/efi ├─nvme0n1p2 259:2 0 2G 0 part /boot └─nvme0n1p3 259:3 0 253G 0 part / ``` ## まとめ {#summary} - **`lsblk`**: ディスク構成をまず確認するときの最初の一手。root 不要 - **`sudo fdisk -l`**: パーティションタイプ・境界を詳細に確認。root 必要 - **`df -h`**: 各ファイルシステムの空き容量を確認 - **`du -sh`**: どのディレクトリが容量を消費しているか調査 ディスク調査の基本フローは「`lsblk` でデバイス構成を把握 → `df -h` で満杯の疑いがあるFSを特定 → `du -sh` でどのディレクトリが原因かを深掘り」です。 ## 次に読む {#next} - [du と df の違いを詳しく理解する](/articles/tutorials/du-df-difference) - [ディスクが満杯になったときの解決手順](/articles/troubleshooting/no-space-left-on-device) # dmesg 入門 - カーネルログでハードウェア・起動の問題を読む Source: https://penguin-gym-linux.com/articles/tutorials/dmesg-kernel-messages ## この記事で解決できること {#intro} - `dmesg` で **カーネルリングバッファ** を読み、起動・ハードウェアの異常を特定できる - タイムスタンプ・ログレベル・追従表示など **実務で効くオプション** を使い分けられる - 「ディスク I/O エラー」「OOM Killer」「デバイス未認識」などの **定番障害を切り分け** できる - `dmesg` と `journalctl -k` の **使い分け** が分かる ::: tip **結論(実務の型)** - まず `sudo dmesg -H` で全体を眺める(色付き + ページャ) - 障害の時刻を掴むなら `dmesg -T`、新着を追うなら `dmesg -w` - レベルで絞るなら `dmesg -l err,warn`、キーワードは `grep -i` - 再起動後も残したいログは `journalctl -k`(リングバッファは揮発する) ::: ::: warning **前提(対象環境)** - OS:Ubuntu / Debian / RHEL 系などの systemd Linux - `util-linux` の `dmesg`(ほぼ全ディストリ標準) - 多くの環境で読み取りに `sudo` が必要(後述) ::: ## dmesg とは何か? {#what} > **結論**: `dmesg` はカーネルが出力したメッセージを保持する「リングバッファ」を表示するコマンド。起動ログやハードウェア検出の記録を読むための入口。 `dmesg`(diagnostic message)は、Linux カーネルが生成したメッセージ群を表示する。これらは **カーネルリングバッファ** と呼ばれる固定サイズのメモリ領域に蓄積される。 カーネルはブート時のハードウェア検出、ドライバの読み込み、デバイスの抜き差し、ファイルシステムのマウント、I/O エラーなどをここに記録する。アプリケーションのログではなく、**OS の最下層で起きたこと** を観測できるのが `dmesg` の役割。 ```bash $ sudo dmesg ``` ```output [ 0.000000] Linux version 6.8.0-106-generic ... [ 0.004000] Memory: 16310632K/16777216K available ... [ 2.314551] usb 1-2: new high-speed USB device number 3 ... [ 2.461230] ata1.00: configured for UDMA/133 ``` ::: warning リングバッファは **固定サイズ** のため、古いメッセージは新しいメッセージで上書きされる。さらに再起動すると消える。永続的に残したいログは後述の `journalctl -k` を使う。 ::: ## なぜ root 権限が必要なのか? {#permission} > **結論**: 多くのディストリは `kernel.dmesg_restrict=1` で非特権ユーザーの読み取りを禁じている。`sudo dmesg` で読むのが基本。 近年の Ubuntu などでは、セキュリティ設定 `kernel.dmesg_restrict` が `1` になっており、一般ユーザーは `dmesg` を読めない。カーネルログにはアドレスなど攻撃の手がかりになる情報が含まれ得るためだ。 ```bash # 現在の設定を確認 $ sysctl kernel.dmesg_restrict ``` ```output kernel.dmesg_restrict = 1 ``` `1` なら `sudo` が必要。`0` なら一般ユーザーでも読める。 ```bash $ sudo dmesg ``` ::: tip 恒久的に一般ユーザーへ開放することも可能だが、セキュリティ上の理由で制限されている設定なので、**基本は `sudo` を付ける** 方針が無難。 ::: ## ログはどう読むのか? {#read} > **結論**: 素の `dmesg` は読みにくい。`-H`(色付き + ページャ)、`-T`(人間可読時刻)、`-x`(レベル表示)の3つを押さえれば一気に読みやすくなる。 ### -H:色付き + ページャで全体を眺める ```bash $ sudo dmesg -H ``` `-H`(`--human`)は色付け・相対時刻の整形・ページャ(`less`)起動をまとめて有効化する。まず全体を俯瞰したいときの定番。 ### -T:いつ起きたのかを実時刻で見る デフォルトの `[ 2.314551]` は **起動からの経過秒数**。実際の日時を知りたいときは `-T` を使う。 ```bash $ sudo dmesg -T ``` ```output [Thu Jun 5 09:12:47 2026] usb 1-2: new high-speed USB device number 3 ... ``` ::: warning `-T` のタイムスタンプは稼働時間から逆算した近似値。サスペンド/レジュームを挟むとずれることがある。正確な時刻が重要なら `journalctl -k` の方が信頼できる。 ::: ### -x:どのレベル・どの分類かを表示する ```bash $ sudo dmesg -x ``` ```output kern :err : [ 3.821] EXT4-fs error (device sda1): ... kern :info : [ 2.314] usb 1-2: new high-speed USB device ... ``` 各行の facility(分類)と level(重大度)が頭に付くため、深刻な行を目視で拾いやすくなる。 ## どうやってリアルタイムに監視するのか? {#follow} > **結論**: `dmesg -w` で新着メッセージを追従表示できる。USB を挿す・デバイスを操作するなど、操作とログを対応付けたいときに有効。 ```bash $ sudo dmesg -w ``` `-w`(`--follow`)は `tail -f` のようにバッファを開いたまま待機し、新しいカーネルメッセージが出るたびに表示する。例えば USB メモリを挿した瞬間に何が認識されるかをその場で観察できる。`Ctrl + C` で終了。 ::: tip 既存ログを出さず新着だけ見たいなら `dmesg -W`(`--follow-new`)。挿抜テストの切り分けで便利。 ::: ## ログレベルでどう絞り込むのか? {#filter} > **結論**: `dmesg -l err,warn` で重大度を絞れる。レベルは emerg / alert / crit / err / warn / notice / info / debug の8段階。 大量の info 行に埋もれた異常を探すなら、レベルでの絞り込みが効く。 ```bash # エラーと警告だけ表示 $ sudo dmesg -l err,warn ``` ```output [ 3.821] EXT4-fs error (device sda1): ext4_find_entry: ... [ 12.044] ata1.00: failed command: READ FPDMA QUEUED ``` カーネルメッセージだけに限定するなら `-k`(`--kernel`)、特定の分類で絞るなら `-f kern` のように `--facility` を使う。 ::: tip キーワード検索は `grep` と組み合わせるのが手早い。`-i` で大文字小文字を無視する。 ```bash $ sudo dmesg | grep -i 'error\|fail\|warn' ``` ::: ## ハードウェア・起動の問題をどう探すのか? {#troubleshoot} > **結論**: 症状ごとに探すキーワードがある。ディスクなら `ata`/`I/O error`、メモリ枯渇なら `Out of memory`、デバイス未認識なら `usb`/`firmware` を起点にする。 ### ディスク・ストレージの異常 ```bash $ sudo dmesg | grep -iE 'ata[0-9]|i/o error|ext4-fs|sd[a-z]' ``` `I/O error`、`ata1.00: failed command`、`EXT4-fs error` などが出ていれば、ディスクまたは接続の劣化を疑う。`lsblk` / `blkid` でデバイス構成と突き合わせる。 ### メモリ枯渇(OOM Killer) プロセスが突然死した原因が掴めないとき、OOM Killer が候補になる。 ```bash $ sudo dmesg | grep -i 'out of memory\|oom-killer\|killed process' ``` ```output [ 1843.221] Out of memory: Killed process 2217 (java) total-vm:4513980kB ... ``` カーネルがメモリ不足で特定プロセスを強制終了した記録。どのプロセスが落とされたかが分かる。 ### デバイス・ドライバの未認識 ```bash $ sudo dmesg | grep -iE 'usb|firmware|failed to load|no such device' ``` `firmware: failed to load` や `failed to load` は、ドライバ・ファームウェアの不足を示すことが多い。 ::: warning リングバッファは揮発する。問題発生から時間が経っていると該当ログが既に上書きされていることがある。その場合は `journalctl -k --boot=-1` で前回起動分を確認する。 ::: ## dmesg と journalctl はどう違うのか? {#vs-journalctl} > **結論**: `dmesg` は揮発するリングバッファの即時表示、`journalctl -k` は永続化されたカーネルログ。過去の起動まで遡るなら journalctl が必須。 | 観点 | dmesg | journalctl -k | | ------------------ | ---------------------- | -------------------------- | | データ源 | カーネルリングバッファ | systemd-journald(永続可) | | 再起動後に残るか | 残らない(揮発) | 永続化設定なら残る | | 過去の起動を見る | 不可 | `--boot=-1` で可能 | | タイムスタンプ精度 | `-T` は近似 | 実時刻で正確 | | 即時性・手軽さ | 高い | やや高機能 | ```bash # 今回の起動のカーネルログ $ journalctl -k # 前回起動のカーネルログ $ journalctl -k --boot=-1 ``` ::: tip 即座に今の状態を見るなら `dmesg`、後から障害を追跡・遡及するなら `journalctl -k`。両者は競合せず役割分担する。詳しくは [journalctl の使い方](/articles/tutorials/journalctl-basics) を参照。 ::: ## よく使うコマンドまとめ {#summary} > **結論**: `sudo dmesg -H` で眺め、`-T`/`-w`/`-l` で絞り、残すなら `journalctl -k`。この型で大半の調査は回る。 ::: tip **コピペ用:dmesg 調査テンプレ** ```bash # まず全体を色付き + ページャで sudo dmesg -H # 実時刻で確認 sudo dmesg -T # エラー・警告だけ sudo dmesg -l err,warn # 新着を追従(USB挿抜テスト等) sudo dmesg -w # 症状別キーワード検索 sudo dmesg | grep -iE 'i/o error|oom|firmware' # 永続ログ / 前回起動 journalctl -k --boot=-1 ``` ::: ## 次に読む {#next} - [journalctl の使い方:ログ調査の基本](/articles/tutorials/journalctl-basics) - [ディスクがいっぱいになったときの対処](/articles/troubleshooting/no-space-left-on-device) - [lsblk / blkid でブロックデバイスを確認する](/articles/tutorials/lsblk-blkid-basics) # dnfパッケージ管理 - Fedora/RHEL系の最新ツール Source: https://penguin-gym-linux.com/articles/tutorials/dnf-package-management ## この記事で解決できること {#intro} - Fedora/RHEL 系で `dnf` を使ったパッケージ管理の基本操作が身につく - `yum` との違いと `dnf` に移行すべき理由が分かる - モジュールストリームなど dnf 固有の機能が活用できる ::: tip **結論(実務の型)** - **インストール**: `sudo dnf install [パッケージ名]` - **更新**: `sudo dnf update`(全体)/ `sudo dnf update [パッケージ名]`(個別) - **削除**: `sudo dnf remove [パッケージ名]` - **トラブル時**: `dnf history undo [id]` でロールバック可能 ::: ## dnf とは何か? {#what-is-dnf} dnf(Dandified YUM)は `yum` の後継パッケージマネージャーで、Fedora 22 以降と RHEL 8 / CentOS 8 以降で標準採用されている。依存解決が高速で、メモリ効率も `yum` より優れる。 対応ディストリビューション: Fedora / RHEL 8+ / CentOS 8+ / AlmaLinux / Rocky Linux / Oracle Linux ::: warning **Ubuntu/Debian 系は対象外** Ubuntu/Debian 系は `apt` を使う。`dnf` は RPM ベースのディストリビューション専用。 [apt/yum の基本はこちら](/articles/tutorials/apt-yum-basics) ::: ## パッケージのインストール・削除 {#install-remove} インストールと削除は dnf の最も基本的な操作。 ### インストールする ```bash sudo dnf install vim ``` 複数パッケージを一度にインストール: ```bash sudo dnf install vim git curl ``` ローカルの RPM ファイルからインストール: ```bash sudo dnf install ./package.rpm ``` ### 削除する ```bash sudo dnf remove vim ``` ::: tip `remove` は対象パッケージに依存する他のパッケージも削除する。単体削除のみ行いたい場合は `--no-autoremove` を付ける。 ::: 依存関係として残った不要パッケージを掃除: ```bash sudo dnf autoremove ``` ## パッケージの更新 {#update} 定期的な更新はセキュリティの基本。 ### 全パッケージを更新する ```bash sudo dnf update ``` `dnf upgrade` は `dnf update` の別名で動作は同じ。 ### 特定パッケージだけ更新する ```bash sudo dnf update vim ``` ### セキュリティアップデートのみ適用する ```bash sudo dnf update --security ``` ### 更新可能なパッケージを確認する(実行しない) ```bash dnf check-update ``` ## パッケージの検索と情報確認 {#search-info} インストール前にパッケージを調べる方法。 ### キーワードで検索する ```bash dnf search nginx ``` パッケージ名と概要の両方をキーワード検索: ```bash dnf search all nginx ``` ### パッケージの詳細情報を確認する ```bash dnf info nginx ``` バージョン・アーキテクチャ・サイズ・リポジトリ・説明などを表示する。 ### インストール済みパッケージを一覧表示する ```bash dnf list installed ``` 利用可能な(未インストール含む)パッケージ一覧: ```bash dnf list available ``` 特定パッケージの状態確認: ```bash dnf list vim ``` ### コマンドがどのパッケージから提供されるか調べる ```bash dnf provides /usr/bin/vim ``` ## パッケージグループの管理 {#group} 関連パッケージをまとめてインストールできる。 ### 利用可能なグループを表示する ```bash dnf group list ``` ### グループをインストールする ```bash sudo dnf group install "Development Tools" ``` ### グループの内容を確認する ```bash dnf group info "Development Tools" ``` ## リポジトリの管理 {#repo} パッケージの提供元(リポジトリ)を管理する方法。 ### 有効なリポジトリを一覧表示する ```bash dnf repolist ``` 無効なものも含めて表示: ```bash dnf repolist --all ``` ### EPEL リポジトリを追加する(RHEL/CentOS 系) ```bash sudo dnf install epel-release ``` ### リポジトリを一時的に無効にしてインストールする ```bash sudo dnf install [package] --disablerepo=epel ``` ## キャッシュの管理 {#cache} ダウンロードキャッシュを管理してディスクを節約する。 ```bash sudo dnf clean all ``` パッケージリストのみ更新(キャッシュは保持): ```bash sudo dnf makecache ``` ## 履歴と更新のロールバック {#history} dnf の強力な機能の一つが操作履歴とロールバック。 ### 操作履歴を確認する ```bash dnf history ``` ### 特定の操作の詳細を確認する ```bash dnf history info 5 ``` ### 操作をロールバック(取り消す) ```bash sudo dnf history undo 5 ``` ::: tip アップデート後に問題が発生した場合、`dnf history undo` で元の状態に戻せる。本番環境での作業で特に有用。 ::: ## モジュールストリーム {#modules} モジュールストリームは dnf 固有の機能で、同一パッケージの複数バージョンを切り替えて使える仕組み。 ### 利用可能なモジュールを一覧表示する ```bash dnf module list ``` ### モジュールの詳細を確認する ```bash dnf module info nodejs ``` ### 特定バージョンのモジュールを有効化してインストールする ```bash sudo dnf module enable nodejs:18 sudo dnf install nodejs ``` ### モジュールストリームを切り替える ```bash sudo dnf module reset nodejs sudo dnf module enable nodejs:20 sudo dnf distro-sync ``` ::: warning モジュールストリームを切り替える際は `dnf distro-sync` を実行して依存関係を整合させること。 ::: ## yum との主な違い {#yum-comparison} | 操作 | yum | dnf | | ------------------ | ---------------- | ------------------ | | インストール | `yum install` | `dnf install` | | 削除 | `yum remove` | `dnf remove` | | 更新 | `yum update` | `dnf update` | | 検索 | `yum search` | `dnf search` | | 不要パッケージ削除 | `yum autoremove` | `dnf autoremove` | | モジュール管理 | 非対応 | `dnf module` | | ロールバック | 限定的 | `dnf history undo` | ::: tip RHEL 7 以前の `yum` コマンドは、RHEL 8 以降では互換シムとして動作するが、実体は `dnf` を呼び出している。 ::: ## 次に読む {#next} - [パッケージ管理入門 - apt/yumの基本操作と使い分け](/articles/tutorials/apt-yum-basics) # du と df の違い - ディスク容量を正しく測る Source: https://penguin-gym-linux.com/articles/tutorials/du-df-difference ## この記事で解決できること {#intro-list} - `du` と `df` の **役割の違い** を説明できます - 「`df` は満タンなのに `du` で計算しても合わない」現象の **原因** が分かります - 「削除したのに容量が戻らない」事件を **自力で解決** できます - ディスクが足りなくなったときの **調査の型** が身につきます **対象読者**:Linux 入門者。`du` と `df` を感覚で使い分けている方。 ::: tip **言葉の整理** - **ファイルシステム**:ディスクを区切って使うための仕組みです。この記事では「`/` や `/home` のように独立して容量を数える単位」と考えてください。 - **マウント**:ディスクをディレクトリに結びつけて使える状態にすることです。結びつけた場所を「マウントポイント」と呼びます。 - **inode**:ファイル 1 つ 1 つの管理情報を入れる札です。札の数には上限があります。 - **プロセス**:実行中のプログラムのことです。 - **`sudo`**:管理者の権限でコマンドを実行するための命令です。他人のファイルを調べるときに必要です。実行するとパスワードを聞かれることがあります。 - **パイプ(`|`)**:左のコマンドの結果を右のコマンドに渡す記号です。`A | B` で「A の結果を B で処理する」という意味になります。 - **`grep`**:文字を探し出すコマンドです。`grep deleted` なら「deleted という文字を含む行だけ残す」という意味です。 この記事の中心は `df` と `du` です。どちらも表示するだけのコマンドで、ファイルを消したり書きかえたりしません。何度実行してもディスクの中身は変わりません。安心して試してください。 なお、記事の後半には削除や再起動をともなうコマンドも出てきます。そこには個別に注意書きを付けています。 ::: ## 導入:リナのディスクパンク事件 {#intro} ::: dialogue @lina: ライニー先輩、大変です。サーバーのディスクが急に満タンになりました。 @lina: `du` で大きいファイルを探したんですけど、合計しても全然容量が足りません。どうしてですか? @linny: 典型的な「`du` と `df` のズレ」だね。これを正しく理解している人は意外と少ないんだ。 @lina: えっ、`du` と `df` って似たコマンドじゃないんですか? @linny: そう見えるよね。でも **見ているものが違う** んだ。今日はその違いと、ズレが起きる理由を見ていこう。 ::: ::: tip **結論を先に** - `df` = **ファイルシステム単位** の空き容量(マウントポイントごと) - `du` = **ディレクトリ・ファイル単位** の使用量(指定パス以下を集計) - 一致しないのは **削除済み開きファイル** / **マウント境界** / **root 予約ブロック** が主因 **2 つの視点のたとえ** 冷蔵庫を思いうかべてください。`df` は「冷蔵庫にどれだけ空きがあるか」を外から見る係です。`du` は「棚ごとに何がどれだけ入っているか」を中から数える係です。 同じ冷蔵庫を見ていますが、数え方が違います。だから答えがずれることがあります。 ::: ## df - ファイルシステム単位で空きを見る {#df} > **結論**: `df` はマウントポイントごとに全体・使用・空きを一瞬で表示する。`-h` と `-i` の両方を確認する。 ::: dialogue @linny: まずは `df` だよ。「disk free」の略だね。ファイルシステム単位で「全体・使用済み・空き」を表示するよ。 @lina: ファイルシステム単位、ですか。 @linny: 簡単に言うと「マウントされている場所」ごとだね。`/`(ルート)、`/home`、USB メモリの `/mnt/usb` などが、別々に数えられるんだ。 ::: ### 実際に試してみよう {#df-try} ```bash $ df -h ``` ```output Filesystem Size Used Avail Use% Mounted on /dev/sda1 50G 42G 5.5G 89% / tmpfs 1.9G 0 1.9G 0% /dev/shm /dev/sda2 100G 60G 40G 61% /home ``` ::: tip **読み方** - `Filesystem`:デバイス名(`/dev/sda1` など) - `Size`:全体容量 - `Used`:使用済み - `Avail`:空き - `Use%`:使用率(**90% を超えたら警戒**) - `Mounted on`:マウントポイント ::: ::: dialogue @lina: `-h` は何ですか? @linny: `--human-readable` の略だよ。`50G` や `5.5G` のように **人間が読みやすい単位** で表示してくれる。 @linny: `-h` を付けない `df` は KB 表示になる。桁を数えるのが大変だね。 @lina: たしかに `52428800` と出てきても、すぐには分かりません。 ::: ### よく使うオプション {#df-options} ```bash $ df -h # 人間が読みやすい単位(G, M, K) $ df -T # ファイルシステムの種類も表示(ext4, xfs など) $ df -i # 使用量ではなく inode 数を表示 $ df -h /var # 特定パスが属するファイルシステムだけ ``` ::: warning **`df -i` を忘れない** ディスク容量に余裕があるのに「No space left」エラーが出ることがあります。その場合は **inode の枯渇** が原因かもしれません。小さいファイルが大量にあると起こります。 `df -h` と `df -i` は **両方確認する習慣** をつけましょう。 ::: ## du - ディレクトリ単位で使用量を測る {#du} > **結論**: `du` はファイルを実際に走査して使用量を計算する。`-sh` で合計、`sort -h` で大きい順に並ぶ。 ::: dialogue @linny: 次は `du` だよ。「disk usage」の略だね。指定したディレクトリ以下のファイルを **実際に走査して** 使用量を計算するよ。 @lina: 走査するというのは、ファイルを 1 個ずつ見るということですか? @linny: そのとおり。だから大きいディレクトリで `du` を実行すると **時間がかかる** ことがある。 @linny: `df` が一瞬で返ってくるのと対照的だね。 ::: ### 実際に試してみよう {#du-try} ```bash $ du -sh /var/log ``` ```output 1.2G /var/log ``` ::: tip **よく使うオプションの組み合わせ** - `-s`(summary):合計だけ表示 - `-h`(human-readable):人間が読める単位 - `--max-depth=N`:N 階層まで表示 `-sh` はワンセットで覚えると便利。 ::: ### 階層別に大きいディレクトリを探す {#du-depth} ```bash $ du -h --max-depth=1 /var ``` ```output 4.0K /var/games 1.2G /var/log 512M /var/cache 24M /var/lib 1.7G /var ``` ::: dialogue @lina: これはすごく便利ですね。 @linny: そうだね。さらに **大きい順に並べかえる** と、特に大きいディレクトリが一目で見えるよ。 ::: ### サイズ順に並べる {#du-sort} ```bash $ du -sh /var/* 2>/dev/null | sort -h ``` ```output 4.0K /var/games 4.0K /var/opt 24M /var/lib 512M /var/cache 1.2G /var/log ``` ::: tip **ポイント** - `2>/dev/null`:権限が足りず読めないディレクトリのエラーメッセージを捨てます - `sort -h`:**単位付きの数字を正しく並べます**(`1.2G` を `512M` より大きいと判定します) `sort -n`(数値ソート)には注意が必要です。`1.2G` の `1.2` だけを見るため、小さい扱いになります。 ::: ## du と df の決定的な違い {#difference} > **結論**: `df` はファイルシステム単位、`du` はディレクトリ単位で見る。見ている階層が違うのでズレる。 ::: dialogue @lina: だいぶ分かってきました。結局、何が違うんですか? @linny: 一言で言うと **見ている階層が違う** んだ。表にすると分かりやすいよ。 ::: ::: highlight **比較表** | 項目 | df | du | | -------------------- | ------------------------ | --------------------------- | | 単位 | ファイルシステム | ディレクトリ・ファイル | | 取得方法 | スーパーブロックから取得 | 実際にファイルを走査 | | 速度 | 一瞬 | 大きいパスでは遅い | | 削除済み開きファイル | **含まれる** | 含まれない | | 別マウント | 別物として集計 | 跨いで集計(`-x` で抑制可) | | root 予約ブロック | 影響あり | 影響なし | ::: ::: dialogue @lina: なるほど。だから「`df` では 90% なのに、`du` では 60% しか使っていない」ということが起こるんですね。 @linny: そのとおり。よくある原因は 3 つある。次から詳しく見ていこう。 ::: ## ズレの主犯:削除済み開きファイル事件 {#deleted-files} > **結論**: 削除しても開いているファイルは `df` だけがカウントする。`lsof | grep deleted` で見つけて解放する。 ::: dialogue @linny: `df` と `du` がズレる **一番多い原因** がこれだよ。「**削除したのに、まだ誰かが開いているファイル**」だね。 @lina: 削除したのに開いている、ですか。どういうことでしょう。 ::: ### リナの失敗:ログを消したのに容量が戻らない {#failure} ::: dialogue @lina: 実は今日、`/var/log/nginx/access.log` を `rm` で消しました。500 MB もあったのに、`df -h` の空き容量が 1 バイトも増えません。消し方を間違えたんでしょうか。 ::: ```bash $ df -h / ``` ```output Filesystem Size Used Avail Use% Mounted on /dev/sda1 50G 42G 5.5G 89% / ``` ::: dialogue @lina: えっ、ファイルは一覧から消えているのに、使用量は 42G のままです。消えていないんでしょうか。 @linny: 消し方は間違っていないよ。Linux では、`rm` してもファイルの実体がすぐには消えないことがあるんだ。 @linny: そのファイルを開いているプロセスがいる間、実体は残り続ける。今回は起動中の nginx が、そのログを開いたままなんだよ。 @lina: 名前だけ消えて、中身は残っているんですね。それは想像していませんでした。 @linny: そう。`du` は名前をたどって数えるから、この分を見つけられない。`df` はファイルシステムから見た使用量だから、この分を数えるんだ。 @lina: 納得しました。`df` と `du` がズレたら、まず開きっぱなしのファイルを疑います。 ::: ::: dialogue @linny: つまり `rm` は「名前を消すコマンド」と考えるといい。実体が解放されるのは、それを開いている全員が閉じたあとだよ。 ::: ### 削除済み開きファイルを見つける {#find-deleted} ```bash $ sudo lsof | grep deleted ``` ```output nginx 1234 root 5w REG 8,1 524288000 ... /var/log/nginx/access.log (deleted) mysqld 5678 mysql 7w REG 8,1 104857600 ... /tmp/ibdata.tmp (deleted) ``` ::: tip **読み方** - 列 1:プロセス名(`nginx`, `mysqld`) - 列 2:PID(プロセス ID。プロセスに付く番号です) - 列 7:**サイズ**(バイト単位) - 末尾の `(deleted)`:削除済みなのに開かれている印です `lsof` は「list open files」の略です。開かれているファイルを一覧するコマンドで、表示するだけです。実行してもファイルは変わりません。 ::: ### 解放するには {#release-deleted} ```bash # 該当プロセスを再起動 or リロード $ sudo systemctl restart nginx $ sudo systemctl reload mysql # プロセスが特定できれば、再起動なしで切り離す方法もある(上級者向け) # /proc//fd/ を /dev/null にリダイレクトする手法など ``` ::: warning **再起動の前に必ず影響範囲を確認** `systemctl restart` はサービスをいったん止めます。失敗すると、その間サービスが応答しなくなります。 本番サーバーで実行する前に、次の 2 点を確認してください。 - サービスが止まってよい時間帯か - そのサービスに依存している別のサービスはないか 安全に試したい場合は、まず学習用の環境か、止めても影響のないサービスで練習してください。詳しくは [ディスクがいっぱい](/articles/troubleshooting/no-space-left-on-device) を参照してください。 ::: ## ズレの第二犯:マウント境界 {#mount-boundary} > **結論**: `du` は既定でマウント境界を跨いで集計する。`df` と揃えるには `-x` を付けて同一 FS に限定する。 ::: dialogue @lina: あと 2 つは何ですか? @linny: 1 つはマウント境界だね。たとえば `/home` が別パーティションになっているとしよう。そこで `du -sh /` を実行すると、どうなると思う? @lina: `/home` も含めて全部足される、でしょうか。 @linny: そのとおり。`du` は既定で **マウントポイントを跨いで集計する** んだ。 @linny: 一方 `df` は別ファイルシステムを別物として数える。だから `du -sh /` の数字が、`df` の `/` の使用量より **大きく出る** ことがあるんだよ。 ::: ```bash # マウント境界を跨がない(その FS だけ集計) $ sudo du -sh -x / ``` ::: tip **`-x`(`--one-file-system`)** これを付けると `du` は **指定パスと同じファイルシステムだけ** を集計します。`df` との比較がしやすくなります。 ::: ## ズレの第三犯:root 予約ブロック {#reserved-blocks} > **結論**: ext4 は root 用に約 5% を予約する。`df` の `Avail` は予約分を差し引くため数字が合わない。 ::: dialogue @linny: 最後は地味だけど大事な話だよ。ext4 などのファイルシステムは **root 用に 5% の領域を予約** しているんだ。 @lina: どうして予約するんですか? @linny: 通常ユーザーが容量を使い切っても、root なら緊急の対応ができるようにするためだよ。 @linny: `df` の `Avail` はこの予約分を **差し引いた値** なんだ。だから `Size - Used` と `Avail` が一致しないように見えるんだよ。 ::: ```bash # 予約ブロックの割合を確認 $ sudo tune2fs -l /dev/sda1 | grep -i reserved ``` ```output Reserved block count: 655360 Reserved blocks uid: 0 (user root) Reserved blocks gid: 0 (group root) ``` ::: warning **予約ブロックを減らすのは慎重に** `tune2fs -m 1 /dev/sda1` を実行すると 1% に減らせます。ただし、この操作はファイルシステムの設定を書きかえます。 システム領域では予約を **残しておくのが基本** です。減らすとしても、データ専用パーティションだけにしてください。 なお `tune2fs -l`(一覧表示)は読むだけの操作です。設定は変わりません。 ::: ## 実務で使う「調査の型」 {#patterns} > **結論**: `df -h` で俯瞰、`df -i` で inode 確認、`du` で大きいディレクトリ特定、`lsof` でズレ調査の順に動く。 ::: dialogue @lina: 違いは分かりました。実際に「ディスクが足りない」と言われたとき、どう動けばいいですか? @linny: いい質問だね。手順を型として決めておくと、あわてずに済むよ。 ::: ::: highlight **ディスク調査の型(上から順に)** 1. **全体を俯瞰**:`df -h`(どのファイルシステムが満タンか) 2. **inode も確認**:`df -i`(小さいファイル大量問題) 3. **大きいディレクトリを特定**:`sudo du -h --max-depth=1 / 2>/dev/null | sort -h` 4. **`df` と `du` がズレてたら**:`sudo lsof | grep deleted` 5. **古いログを削除**:`/var/log` 配下の `.gz` などを確認 6. **解放されない時**:該当サービスを `systemctl restart` ::: ### 各ステップのコマンド集 {#patterns-commands} ```bash # 1. 全体を俯瞰 df -h # 2. inode 確認 df -i # 3. 大きいディレクトリ特定(root から 1 階層ずつ深掘り) sudo du -h --max-depth=1 / 2>/dev/null | sort -h sudo du -h --max-depth=1 /var 2>/dev/null | sort -h sudo du -h --max-depth=1 /var/log 2>/dev/null | sort -h # 4. 削除済み開きファイル sudo lsof | grep deleted | sort -k7 -n -r | head # 5. 大きいファイル単位で探す sudo find / -type f -size +100M 2>/dev/null ``` ## ミニ課題:自分の環境で試してみよう {#exercise} > **結論**: 使用率確認・大きいディレクトリ Top3・`df` と `du` の差分説明の 3 問で理解を定着させる。 ::: dialogue @lina: 知識は入りました。手を動かして試したいです。 @linny: いいね、3 問用意したよ。どれも表示するだけの操作だから、何度実行してもディスクは変わらないよ。 @linny: ただし、この章のコマンドは実機の Linux か WSL で試してほしい。`lsof` や `--max-depth` は本サイトの仮想ターミナルには入っていないんだ。 @linny: `sudo` が使えない環境なら、自分のホームディレクトリの中だけで試そう。 ::: **課題 1**: 自分の `/` パーティションの使用率を確認しよう。 :::details ヒント 1(方向づけ)を見る ファイルシステム単位で空きを見る側のコマンドを使います。ディレクトリを 1 つずつ数える必要はありません。 読みやすい単位で表示するオプションも付けます。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `df` です。読みやすい単位は `-h` です。調べたい場所として `/` を指定します。 ::: :::details 答えを見る ```bash $ df -h / ``` ```output Filesystem Size Used Avail Use% Mounted on /dev/sda1 50G 42G 5.5G 89% / ``` `Use%` の列が `/` の使用率です。`90%` を超えたら片づけを検討してください。 ::: **課題 2**: ホームディレクトリの直下で、最も大きいディレクトリ Top 3 を見つけよう。 :::details ヒント 1(方向づけ)を見る 今度はディレクトリごとに数える側のコマンドを使います。深くもぐらず、1 階層だけ見れば十分です。 そのうえで大きい順に並べ、末尾の数行を取り出します。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `du` です。階層を 1 つに絞るのは `--max-depth=1` です。並べかえは `sort -h` です。 末尾の行を取り出すのは `tail` です。一番下は全体の合計になるので、1 行多めに取ります。ホームディレクトリは `~` と書けます。 ::: :::details 答えを見る ```bash $ du -h --max-depth=1 ~ 2>/dev/null | sort -h | tail -4 ``` ```output 64M /home/user/Documents 512M /home/user/Downloads 1.2G /home/user/Videos 2.5G /home/user ``` `sort -h` は小さい順に並べます。だから末尾を取れば大きい方が取れます。 一番下の行はホームディレクトリ全体の合計です。その上の 3 行が Top 3 です。だから `tail -4` にしています。 ::: **課題 3**: `df -h /` と `sudo du -sh -x /` の数字を比べ、差が出る理由を 1 行で説明しよう。 :::details ヒント 1(方向づけ)を見る まず 2 つの数字を並べて見ます。そのうえで、この記事で挙げた 3 つの原因を思い出してください。 どちらか一方だけが数えるものがあります。 ::: :::details ヒント 2(コマンド名)を見る `df -h /` と `sudo du -sh -x /` を続けて実行します。差が大きいときは `sudo lsof | grep deleted` も実行します。 ::: :::details 答えを見る ```bash $ df -h / $ sudo du -sh -x / $ sudo lsof | grep deleted | head ``` 差が出る主な理由は次の 3 つです。 - 削除済みだが開いているファイル(`df` だけが数えます) - 別のマウントポイントの存在(`du -x` で除外していない場合) - ext4 の root 予約ブロック(`df` の `Avail` に影響します) 1 行でまとめるなら「`df` はファイルシステムから見た使用量、`du` は名前をたどった使用量なので、名前のないデータの分だけずれる」となります。 ::: ## よくある落とし穴 {#pitfalls} > **結論**: `df` の数字だけで消す・全削除する操作は危険。消す前に `ls -lh` で確認し `truncate` も検討する。 ::: warning **やってはいけない 3 つのパターン** 1. **`du -sh /` を `nohup` なしで本番で実行する** → SSH が切れると途中で止まります 2. **`df` の数字だけを見てファイルを消す** → 削除済み開きファイルが原因のときは効果がありません 3. **`rm -rf /tmp/*` で全部を消す** → 起動中のアプリの作業ファイルを壊します ::: ::: tip **安全な型** - `du` はどの書き方でも、指定した場所より下をすべて数えます。**走査量そのものは減りません** - 表示だけを絞るなら `sudo du -h --max-depth=1 / 2>/dev/null | sort -h` を使います。1 階層ごとの合計が見えるので、次にどこを掘るか決められます - 時間がかかる調査は `tmux` や `screen` の中で実行します。接続が切れても止まりません - 消す前に `ls -lh` で **サイズと更新日時を確認します** - 大きいログは消さず、`truncate -s 0 ファイル名` で **中身だけを空にします** 3 つ目について補足します。`truncate -s 0` はファイルの中身を空にする操作です。中身は戻せません。ただし、ファイル自体は残るため、プロセスが開いたままでも安全に容量を解放できます。 先に `ls -lh` でサイズを見て、消してよい内容かを確かめてから実行してください。 ::: ## 振り返り {#review} ::: dialogue @lina: 整理します。`df` はファイルシステム単位、`du` はディレクトリ単位で数えるんですね。 @linny: そのとおり。同じディスクを、外から見るか中から数えるかの違いだね。 @lina: ログを消しても容量が戻らなかったのは、nginx がそのファイルを開いたままだったからでした。 @linny: よく覚えていたね。ズレを見つけたら `sudo lsof | grep deleted` を先に実行する。この順番だけ覚えておけば大丈夫だよ。 ::: ## 今日の 3 行まとめ {#three-lines} 1. `df` はファイルシステム単位の空き、`du` はディレクトリ単位の使用量を見る 2. ズレの主な原因は「削除済み開きファイル」「マウント境界」「root 予約ブロック」の 3 つ 3. 調べる順番は `df -h` → `df -i` → `du --max-depth=1` → `lsof | grep deleted` ## 次に読む {#next} - [ディスクがいっぱい(No space left on device)](/articles/troubleshooting/no-space-left-on-device) - [find・grep・awkの使い方入門 - 正規表現の基礎から](/articles/tutorials/find-grep-awk-basics) - [パイプとリダイレクト入門 - データの流れを理解する](/articles/tutorials/pipe-redirect-basics) # env コマンド入門 - 環境変数を一時設定してコマンドを実行する Source: https://penguin-gym-linux.com/articles/tutorials/env-command-usage ## env コマンドとは? {#intro} > **結論**: `env` は環境変数を**一時的に変更してコマンドを実行する** coreutils のツール。`env VAR=value command` の形で、現在のシェルを汚さずに特定のコマンドだけ別の環境で走らせられる。 `env` には大きく 2 つの用途がある。 - **環境変数を一時設定して実行する**(`env LANG=C date` のように、その 1 回だけ変数を変える) - **引数なしで現在の環境変数を一覧する**(`printenv` と同等) 加えて、スクリプトの shebang 行(`#!/usr/bin/env python3`)でインタプリタを `PATH` から探させる用途でも広く使われる。 ::: tip **この記事で扱う環境** - GNU coreutils の `env`(Ubuntu / Debian / RHEL 系など一般的な Linux ディストリビューション) - 一部のオプション(`-C` / `-S`)は coreutils のバージョンに依存する。後述 ::: ## なぜ `env VAR=value` を使うのか? {#why} > **結論**: 設定を**恒久化せず**、**現在のシェルも汚さず**、その 1 コマンドにだけ環境変数を効かせたいから。`export` は以降ずっと残るが、`env` は一過性で済む。 環境変数を変えてコマンドを動かす方法は複数ある。違いを押さえておく。 ```bash # (1) シェル組み込みの一時代入(env なし) $ LANG=C date # (2) env 経由の一時代入 $ env LANG=C date # (3) export(以降のコマンドすべてに影響、恒久的) $ export LANG=C $ date ``` (1) と (2) はどちらも「その 1 回だけ」変数を変える点で同じ結果になる。一方 (3) の `export` は、それ以降に実行するすべてのコマンドへ影響が残る。「いまの 1 回だけ言語を変えたい」「設定を後に残したくない」場面では (1) か (2) を選ぶ。 ::: highlight **(1) と (2) はどう違うのか** - `LANG=C date` はシェルの構文(前置代入)。シェルが解釈する - `env LANG=C date` は `env` という独立した実行ファイルを起動し、その子プロセスで環境を組み立ててから `date` を `exec` する 通常の対話用途では結果は同じ。`env` が必須になるのは、**シェルを介さない場面**(shebang 行、`exec` 系 API への引数、空環境からの起動など)。 ::: ## 環境変数を一時設定して実行する {#run} > **結論**: `env NAME=value command args...` と書く。複数の変数はスペース区切りで並べられる。 ```bash # タイムゾーンを一時的に UTC にして日時表示 $ env TZ=UTC date # 言語を C ロケールにして英語メッセージを得る(ログ調査で定番) $ env LANG=C ls /nonexistent ``` ```output ls: cannot access '/nonexistent': No such file or directory ``` 複数の変数を同時に設定する場合は続けて並べる。 ```bash $ env LANG=C LC_ALL=C TZ=UTC ./myscript.sh ``` ::: tip **ロケール起因のエラー切り分け** 日本語環境ではエラーメッセージが翻訳されて検索しづらいことがある。`env LANG=C コマンド` で英語メッセージに固定すると、原文での検索や issue 報告がしやすくなる。 ::: ## 空の環境で実行する(env -i) {#clean} > **結論**: `env -i` は**継承した環境変数をすべて消した状態**でコマンドを起動する。「自分の環境でだけ動く / 落ちる」問題の切り分けに効く。 ```bash # 何が残るかを確認(ほぼ空になる) $ env -i env ``` ```output ``` `-i`(`--ignore-environment`)を付けると、`PATH` すら継承されない。そのため `-i` 環境ではコマンドを絶対パスで指定するか、必要な変数を明示的に渡す必要がある。 ```bash # PATH が無いので which 等は失敗しうる。必要な変数だけ渡す $ env -i PATH=/usr/bin:/bin LANG=C ./myscript.sh ``` ::: warning `env -i` 配下では `HOME` / `PATH` / `LANG` などが未定義になる。スクリプトがこれらに依存していると予期せぬ挙動になる。**必要な変数だけを明示的に渡す**のが安全な型。 ::: ## 特定の変数だけ外して実行する(env -u) {#unset} > **結論**: `env -u NAME` は、その環境変数**だけ**を取り除いてコマンドを実行する。`-i` のような全消しではなく、一点だけ無効化したいときに使う。 ```bash # プロキシ設定を一時的に無効化して接続を試す $ env -u http_proxy -u https_proxy curl https://example.com ``` `-u`(`--unset`)は変数ごとに繰り返し指定できる。「この環境変数が悪さしているのでは?」という仮説を、設定ファイルをいじらずに即検証できる。 ## ディレクトリを変えて実行する(env -C) {#chdir} > **結論**: `env -C DIR command` は、指定ディレクトリへ移動してからコマンドを実行する。`cd DIR && command` をサブシェルなしで 1 行にまとめられる。 ```bash $ env -C /var/log tail -n 20 syslog ``` ::: warning `-C`(`--chdir`)は **coreutils 8.28 以降**で利用可能。古い環境では使えない。`env --version` でバージョンを確認できる(後述)。 ::: ## shebang で使う `/usr/bin/env` {#shebang} > **結論**: スクリプト先頭の `#!/usr/bin/env python3` は、インタプリタを `PATH` から探して起動する。インタプリタの絶対パスをハードコードせずに済み、可搬性が高い。 ```bash #!/usr/bin/env python3 print("hello") ``` `#!/usr/bin/python3` と直接書くと、Python が `/usr/local/bin` や仮想環境(venv)など別の場所にある環境で動かない。`#!/usr/bin/env python3` なら `PATH` を探索するため、その人の環境で最初に見つかる `python3` が使われる。 ```bash #!/usr/bin/env -S python3 -u ``` shebang 行でインタプリタに**引数を渡したい**場合は `-S`(`--split-string`)を使う。shebang は本来 1 引数しか渡せないが、`-S` が文字列を分割して複数引数として扱う。 ::: warning `-S` は **coreutils 8.30 以降**。macOS(BSD env)でも対応状況が異なるため、配布スクリプトで使う場合は対象環境を確認する。 ::: ## printenv / set との違い {#vs} > **結論**: 引数なしの `env` は環境変数の一覧だけを表示する。シェル変数を含む全変数は `set`、特定の 1 変数は `printenv NAME` が向く。 | やりたいこと | コマンド | | ---------------------- | --------------------------------------------- | | 環境変数を一覧 | `env` または `printenv` | | 特定の環境変数だけ表示 | `printenv PATH`(`env` 単体では絞り込めない) | | シェル変数も含めて全部 | `set`(bash 組み込み) | | 一時設定して実行 | `env VAR=val cmd` | `env PATH` のように引数を 1 つ渡しても変数値は表示されない(`env` はそれをコマンド名と解釈し「No such file or directory」になる)。値だけ見たいときは `printenv PATH` か `echo "$PATH"` を使う。 ## トラブルシューティング {#trouble} > **結論**: `env` 特有のつまずきは「`-i` 後に PATH が無い」「`-C` / `-S` が古い環境で使えない」の 2 つが大半。バージョン確認で多くが切り分く。 ::: details env: '...': No such file or directory と出る `env` は `=` を含まない最初の引数を**コマンド名**として扱う。`env LANG C date`(`=` を書き忘れ)や `env PATH`(値を見ようとした)でこのエラーになる。`NAME=value` の形になっているか確認する。 ::: ::: details env -i の後でコマンドが見つからない `-i` は `PATH` も消す。`env -i PATH=/usr/bin:/bin コマンド` のように `PATH` を明示するか、コマンドを絶対パスで指定する。 ::: ```bash # 利用中の env のバージョンを確認(-C / -S の可否判断に使う) $ env --version | head -1 ``` ```output env (GNU coreutils) 9.4 ``` ## 次に読む {#next} - [環境変数の基礎](/articles/tutorials/environment-variables) - [.bashrc と .profile の読み込み順](/articles/tutorials/bashrc-profile-order) - [command not found の対処](/articles/tutorials/command-not-found) - [終了ステータスと exit code](/articles/tutorials/exit-codes-and-status) # 環境変数の基礎 - 設定と活用法 Source: https://penguin-gym-linux.com/articles/tutorials/environment-variables ## この記事でできるようになること {#intro} - `echo $VAR` や `env` で **環境変数の中身を確認** できる - `export` で **自分専用の環境変数を設定** できる - `PATH` の意味を理解し、「command not found」を **自力で解決** できる - `.bashrc` で **次のログインでも残す設定** ができる **対象読者**:Linuxの基本コマンド(pwd・cd・ls)は使えるが、`$HOME` や `export` の意味がピンとこない方。 ## 導入:環境変数って何? {#what-is-env} ::: dialogue @lina: ライニー先輩、教科書に `$HOME` や `$PATH` がよく出てきます。これは何でしょうか。`$` マークが付いていて、呪文みたいです。 @linny: いいところに気づいたね。それは「環境変数」と呼ばれるものだよ。**シェルが覚えている「名前付きのメモ書き」** だと考えるといい。 @lina: メモ書き、ですか。 @linny: そう。冷蔵庫に貼ってある付箋を想像してみて。「牛乳は下の段」と書いた付箋があれば、毎回探さなくて済むよね。 @linny: 同じように「私のホームディレクトリは `/home/lina`」という情報を、`HOME` という名前で覚えてあるんだ。だから `$HOME` と書けば、その中身が呼び出される。 ::: ::: tip **言葉の整理** - **変数**:値を入れておく名前付きの箱です。名前を書けば、中身を取り出せます。 - **シェル変数**:今のシェルの中だけで使える変数です。 - **環境変数**:シェル変数のうち、そこから起動したコマンドにも引き継がれるものです。`export` を付けると環境変数になります。 - **子プロセス**:あるコマンドから起動された別のコマンドのことです。 つまり「環境変数はシェル変数の一種で、引き継がれる印が付いたもの」です。この違いは [2 章](#set) で実験して確かめます。 ::: ::: tip **結論(実務の型)** - 中身を見たいだけ → `echo $VAR` または `env | grep VAR` - 自分のシェルだけで使いたい → `export VAR=値` - 次のログインでも残したい → `~/.bashrc` に書く - `command not found` の8割は `PATH` が原因 ::: ::: warning **前提(対象環境)** - OS:Ubuntu / 他 Linux 一般 - シェル:bash(zsh でもほぼ同じ) - ターミナルで `$ ` プロンプトが表示されている状態 ::: ## 1. 環境変数を見てみよう {#view} > **結論**: `echo $VAR` で 1 つ、`env` / `printenv` で全部を確認できる。設定より先に現状を読むのが安全。 ::: dialogue @linny: まずは「読む」ところから。**設定するより先に、今何があるかを見る**のが安全だよ。 ::: ### 1-1. 1 つの変数を見る:echo $VAR ```bash $ echo $HOME ``` ```output /home/lina ``` ```bash $ echo $USER ``` ```output lina ``` ::: tip **ここがポイント** - `$` を付けると、「変数の中身に置きかえる」という意味になります。この置きかえを「展開」と呼びます。 - `$` を付けずに `echo HOME` と打つと、ただの文字として「HOME」が表示されます。 - 変数名は **大文字** にするのが慣例です。必須ではありませんが、揃えると読みやすくなります。 ::: ::: dialogue @lina: `$` を付けるかどうかで、結果が全然違うんですね。 @linny: そう。ここが最初のつまずきポイントなんだ。「`$` は中身を呼び出すスイッチ」と覚えておけば大丈夫だよ。 ::: ### 1-2. 全部の環境変数を見る:env / printenv ```bash $ env ``` ```output SHELL=/bin/bash USER=lina HOME=/home/lina PATH=/usr/local/bin:/usr/bin:/bin LANG=ja_JP.UTF-8 PWD=/home/lina ... ``` `env` は、今使える環境変数をすべて表示します。`printenv` でも同じことができます。 ::: tip 出力が長いときは `env | less` を使います。1 画面ずつ送れます。特定の変数を探すときは `env | grep PATH` のように絞り込みます。 ::: ### 1-3. よく見る環境変数 | 変数名 | 意味 | 例 | | ------- | ---------------------------------- | ------------------------------ | | `HOME` | 自分のホームディレクトリ | `/home/lina` | | `USER` | 現在のユーザー名 | `lina` | | `PATH` | コマンドを探しに行くディレクトリ群 | `/usr/local/bin:/usr/bin:/bin` | | `SHELL` | 使っているシェル | `/bin/bash` | | `LANG` | 言語・文字コードの設定 | `ja_JP.UTF-8` | | `PWD` | 今いるディレクトリ(`pwd` と同じ) | `/home/lina/work` | ::: dialogue @lina: たくさんありますね。全部覚えなければいけませんか。 @linny: その必要はないよ。ふだん意識するのは `HOME` と `PATH` くらい。残りは「そういうのもある」で十分だよ。 ::: ## 2. 自分で設定する:代入と export {#set} > **結論**: 代入だけなら今のシェル限定。`export` を付けると、子プロセスにも引き継がれる環境変数になる。 ::: dialogue @linny: 次は自分で設定する番だよ。ここには **ハマりやすい落とし穴** がある。ゆっくり進めよう。 ::: ### 2-1. ただの代入(シェル変数) ```bash $ MY_NAME=lina $ echo $MY_NAME ``` ```output lina ``` ::: warning **`=` の両側にスペースを入れないでください。** `MY_NAME = lina` と書くと、シェルは「MY_NAME というコマンドを実行する」と解釈します。そのためエラーになります。初心者がよく起こす事故です。 ::: ### 2-2. export で「環境変数」に昇格 ```bash $ MY_NAME=lina $ export MY_NAME ``` または一気に: ```bash $ export MY_NAME=lina ``` ::: dialogue @lina: 代入と `export` は何が違うのでしょうか。同じに見えます。 @linny: 大事な質問だね。代入だけだと、**そのシェルの中でしか使えない**んだ。 @linny: そこから実行したコマンドやスクリプト、つまり子プロセスには引き継がれない。`export` を付けると環境変数になって、子プロセスからも見えるようになるよ。 ::: ### 2-3. 違いを実感する実験 ```bash $ MY_VAR=hello $ bash -c 'echo $MY_VAR' ``` ```output ``` 何も表示されず、空行になります。`bash -c` は新しいシェルを子プロセスとして起動します。そこには `MY_VAR` が届いていません。 ```bash $ export MY_VAR=hello $ bash -c 'echo $MY_VAR' ``` ```output hello ``` 今度は表示されました。`export` を付けたので、子プロセスにも引き継がれたからです。 ::: highlight **鉄則**: スクリプトや別のコマンドに値を渡したいときは、必ず `export` を付けます。自分のメモとして使うだけなら `=` だけでかまいません。 ::: ### 2-4. 変数を消したい ```bash $ unset MY_VAR $ echo $MY_VAR ``` ```output ``` ::: tip `unset` で変数を削除できます。ただし消えるのは今のシェルの分だけです。 `.bashrc` に書いた設定は、そのファイルを編集しないかぎり残ります。 ::: ## 3. PATH:一番大事な環境変数 {#path} > **結論**: `PATH` はコマンドを探すディレクトリ群。追加時は `:$PATH` を末尾に付けないと既存パスが消える。 ::: dialogue @lina: `command not found` というエラーがよく出ます。これも環境変数と関係があるのでしょうか。 @linny: 大いに関係あるよ。`PATH` を理解すれば、そのまま `command not found` の解決につながる。 ::: ### 3-1. PATH の中身を見る ```bash $ echo $PATH ``` ```output /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ``` ::: tip **ここがポイント** - `PATH` の中身は、`:` で区切られた**ディレクトリの並び**です。 - コマンドを打つと、シェルは **左から順に** これらのディレクトリを探します。 - 最初に見つかった同じ名前のコマンドが実行されます。 学校の下駄箱に例えると、`PATH` は「1 組から順に探す」という探し方の指示にあたります。 ::: ### 3-2. 「どこにあるか」を調べる:which ```bash $ which ls ``` ```output /usr/bin/ls ``` ```bash $ which python3 ``` ```output /usr/bin/python3 ``` ::: dialogue @lina: `ls` は `/usr/bin/` の中にあるんですね。 @linny: そう。`ls` と打つだけで動くのは、`PATH` に `/usr/bin` が含まれているからだよ。 @linny: もし `PATH` から `/usr/bin` を消したら、`/usr/bin/ls` とフルパスで打たないと動かなくなる。 ::: ### 3-3. PATH を追加したい(自分のスクリプト置き場) `~/bin` を作って、自作スクリプトを置きたい場合を考えます。 ```bash $ mkdir -p ~/bin $ export PATH="$HOME/bin:$PATH" ``` ::: warning **末尾の `:$PATH` を必ず付けてください。** `export PATH="$HOME/bin"` とだけ書くと、**既存の PATH が全部消えます**。その結果、ほとんどのコマンドが `command not found` になります。事故が最も多い操作です。 もし消してしまっても、新しいターミナルを開けば元に戻ります。設定ファイルに書いていなければ、再起動も必要ありません。 ::: ### 3-4. 順序が結果を変える ```bash # 自作 ls を優先したい $ export PATH="$HOME/bin:$PATH" # 既存の ls を優先したい(自作を後回し) $ export PATH="$PATH:$HOME/bin" ``` ::: highlight **役立つコツ**: `PATH` を編集して動きが変わったときは、`which コマンド名` を実行します。実際にどのファイルが呼ばれているかがわかり、原因に最短でたどり着けます。 ::: ### 3-5. リナの失敗:PATH を上書きしてしまう ::: dialogue @lina: `export PATH="$HOME/bin"` と打ったら、`ls` も `cat` も動かなくなりました。パソコンが壊れたんでしょうか。 @linny: 壊れてないよ。`PATH` を上書きしたから、シェルが `/usr/bin` を探さなくなっただけなんだ。 @lina: えっ、コマンドが消えたわけではないんですか。 @linny: そう。コマンドのファイルはちゃんとある。探しに行く場所の指示が消えただけだよ。 @lina: 場所の指示だけの問題だったんですね。安心しました。 @linny: 直し方は簡単。新しいターミナルを開けば元に戻る。`.bashrc` に書いていなければ、それだけで解決だよ。 ::: ::: tip **その場で直す方法** 新しいターミナルを開かずに直したいときは、次のように打ちます。 ```bash $ export PATH="/usr/local/bin:/usr/bin:/bin:$PATH" ``` これで最低限のコマンドが動くようになります。そのうえで、あらためて正しい書き方に直してください。 ::: ## 4. 永続化する:次のログインでも残す {#persist} > **結論**: `export` は今のシェル限定。次回も残すには `~/.bashrc` に書く。そして `source` で今すぐ反映する。 ::: dialogue @lina: `export` で設定しました。それなのに、ターミナルを閉じたら消えてしまいました。 @linny: それが「永続化」の話だよ。`export` は **今のシェルだけ** で有効なんだ。 @linny: 次に開いたときには忘れられている。残したいなら設定ファイルに書こう。 ::: ::: warning **先に、壊れたときの戻し方** ここからは設定ファイルを編集します。書き方を間違えると、ターミナルを開くたびにエラーが出ることがあります。 編集の前にコピーを取ってください。 ```bash $ cp ~/.bashrc ~/.bashrc.bak ``` おかしくなったら、コピーから戻せます。 ```bash $ cp ~/.bashrc.bak ~/.bashrc $ source ~/.bashrc ``` ターミナルが開いてすぐ閉じてしまう場合は、設定ファイルを読まずに起動できます。別のターミナルから `bash --norc --noprofile` と打てば、上の `cp` で元に戻せます。 ::: ### 4-1. どのファイルに書く? | ファイル | いつ読まれる | 主な用途 | | -------------------------------- | ------------------------------ | ------------------------------ | | `~/.bashrc` | bashの対話シェル起動時(毎回) | **個人の環境変数・エイリアス** | | `~/.profile` / `~/.bash_profile` | ログイン時に1回 | 環境変数(SSH ログイン等) | | `/etc/environment` | システム全体・全ユーザー | システム共通(root権限要) | ::: tip **迷ったら `~/.bashrc` に書いてください。** GUI のターミナルや WSL では、このファイルが最も確実に読まれます。 ::: ### 4-2. .bashrc に書く例 末尾に追記する: ```bash # 自作スクリプト置き場 export PATH="$HOME/bin:$PATH" # 言語設定 export LANG=ja_JP.UTF-8 # エディタの指定 export EDITOR=vim ``` ### 4-3. 編集後すぐ反映させる:source ```bash $ source ~/.bashrc ``` または: ```bash $ . ~/.bashrc ``` ::: warning `source` を忘れて「PATH が反映されない」と悩む人は多いです。 **設定ファイルを編集したら、必ず `source` を実行してください。** 再ログインでも同じ効果があります。 ::: ::: dialogue @lina: `source` は何をしているのでしょうか。 @linny: 「このファイルを今のシェルで読み直す」という命令だよ。 @linny: ふつうにスクリプトを実行すると、別のプロセスで動く。だから `export` しても、その結果は親のシェルに戻ってこないんだ。 @linny: `source` なら今のシェルの中で実行される。だから設定がそのまま反映されるよ。 ::: ## 5. つまずきポイント集 {#pitfalls} > **結論**: `$` の付け忘れ・`=` 前後のスペース・PATH の上書き・クォートの違い。この 4 つが典型的な事故。 ::: dialogue @linny: ここまでで基礎は身についたね。最後に **初心者がよくハマるポイント** をまとめて潰しておこう。 ::: ### 5-1. $ を付け忘れる / 付けすぎる ```bash # NG: ただの文字列「HOME」が表示される $ echo HOME # OK: 中身が展開される $ echo $HOME ``` ### 5-2. = の前後にスペース ```bash # NG: コマンドとして解釈されてエラー $ MY_VAR = hello # OK $ MY_VAR=hello ``` ### 5-3. PATH の上書き事故 ```bash # 大事故!既存PATHが消える $ export PATH="$HOME/bin" # 正しい $ export PATH="$HOME/bin:$PATH" ``` ::: warning 誤って上書きしてしまったときは、**新しいターミナルを開いてください。** それだけで元の PATH に戻ります。あわてて `.bashrc` を編集する必要はありません。 ::: ### 5-4. ダブルクォートとシングルクォートの違い ```bash $ NAME=lina # ダブルクォート:$NAME が展開される $ echo "Hello $NAME" ``` ```output Hello lina ``` ```bash # シングルクォート:そのまま表示される $ echo 'Hello $NAME' ``` ```output Hello $NAME ``` ::: highlight **鉄則**: 変数を展開したいときは **ダブルクォート** `"..."` を使います。文字をそのまま出したいときは **シングルクォート** `'...'` を使います。 ::: ### 5-5. export したのにスクリプトで使えない シェルスクリプトを `./script.sh` の形で実行した場合、親シェルで `export` した変数は引き継がれます。 ただし新しいターミナルを開くと、シェルは `.bashrc` を読み直します。そのため `.bashrc` に書いていない変数は消えています。 ::: tip スクリプトに渡したい変数は、`.bashrc` などに書いて永続化しておくのが確実です。 ::: ## 6. ミニ課題で手を動かそう {#practice} > **結論**: 変数の表示・PATH への `~/bin` 追加・自作コマンドの実行。3 問で設定から永続化までを体験する。 ::: dialogue @linny: 知識だけでは身につかないよ。3 問だけ手を動かしてみよう。 ::: **課題 1**: 自分の名前を変数に入れて、「Hello, 名前」の形で表示しよう。 :::details ヒント 1(方向づけ)を見る まず名前を箱に入れます。次にその箱の中身を、文の一部として画面に出します。文全体はクォートで囲みます。 ::: :::details ヒント 2(コマンド名)を見る 代入は `変数名=値` の形です。表示は `echo` です。中身を展開したいので、囲むのはダブルクォート `"..."` です。 ::: :::details 答えを見る ```bash $ MY_NAME=lina $ echo "Hello, $MY_NAME!" ``` ```output Hello, lina! ``` `MY_NAME` の部分は、あなたの名前に置きかえてかまいません。`=` の前後にスペースを入れないでください。 ::: **課題 2**: `~/bin` というディレクトリを作り、次に開くターミナルでも `PATH` に含まれるようにしよう。 :::details ヒント 1(方向づけ)を見る ディレクトリを作ります。そのあと、設定ファイルの末尾に `PATH` を足す 1 行を書き加えます。最後にその設定ファイルを読み直します。 ::: :::details ヒント 2(コマンド名)を見る 作るのは `mkdir -p` です。書き足すのは `echo` と `>>` です。読み直すのは `source` です。ファイルは `~/.bashrc` です。 ::: :::details 答えを見る ```bash $ mkdir -p ~/bin $ echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc $ source ~/.bashrc $ echo $PATH ``` ```output /home/lina/bin:/usr/local/bin:/usr/bin:/bin ``` 先頭のほうに `/home/あなたの名前/bin` が入っていれば成功です。 ここでシングルクォートを使う理由があります。`.bashrc` に書く時点では展開させず、読み込むときに展開させたいからです。 ::: **課題 3**: `~/bin` に自作コマンドを置いて、どこからでも呼び出せるようにしよう。 :::details ヒント 1(方向づけ)を見る 3 段階です。ファイルを作り、実行できる印を付け、名前だけで呼び出します。 ::: :::details ヒント 2(コマンド名)を見る ファイル作成は `cat > ファイル名` です。中身を打ち終えたら `Ctrl+D` で確定します。実行できる印を付けるのは `chmod +x` です。あとはファイル名を打つだけです。 ::: :::details 答えを見る ```bash $ cat > ~/bin/hello #!/bin/bash echo "Hello from my own script!" ``` 2 行を打ったら `Ctrl+D` を押します。これで保存されます。 1 行目の `#!/bin/bash` は「このファイルは bash で実行する」という決まり文句です。シェバンと呼びます。 ```bash $ chmod +x ~/bin/hello $ hello ``` ```output Hello from my own script! ``` `command not found` が出る場合は、課題 2 の `source ~/.bashrc` を実行したか確認してください。 作ったファイルを消したいときは `rm ~/bin/hello` を実行します。`rm` で消したファイルはゴミ箱に入らず、その場で完全に消えます。消す前に `ls ~/bin` で中身を確かめてください。 ::: ::: dialogue @lina: できました。自分で作ったコマンドがどこからでも呼べるのは、うれしいです。 @linny: それが `PATH` の力だよ。これで `command not found` も怖くないね。 ::: ## 7. 振り返り {#review} ::: dialogue @lina: 整理します。`$` は中身を呼び出すスイッチ。`export` を付けると子プロセスにも届く。 @linny: そのとおり。そして `.bashrc` に書けば、次に開いたターミナルでも残るね。 @lina: `PATH` は探しに行く場所の並び。`command not found` が出たら、まず `echo $PATH` と `which` で確かめます。 @linny: 完璧だね。`PATH` を書きかえるときは、末尾の `:$PATH` を忘れないこと。それだけ覚えておけば大丈夫だよ。 ::: ## 8. 今日のまとめ {#summary} ::: tip **コピペ用:環境変数チートシート** ```bash # 見る echo $HOME # 1つだけ env # 全部 env | grep PATH # 絞り込み which コマンド名 # コマンドの場所 # 設定する MY_VAR=値 # シェル変数(このシェル内のみ) export MY_VAR=値 # 環境変数(子プロセスにも継承) unset MY_VAR # 削除 # PATH 追加(事故防止) export PATH="$HOME/bin:$PATH" # 必ず :$PATH を末尾に # 永続化 vim ~/.bashrc # 末尾に export を追記 source ~/.bashrc # 即反映 ``` ::: ::: warning **やってはいけないこと** - `export PATH="$HOME/bin"` で既存 PATH を吹っ飛ばす - `MY_VAR = 値` のように `=` 前後にスペースを入れる - `'$HOME'` のシングルクォートで括って展開されないと悩む - `.bashrc` を編集したのに `source` を忘れて「反映されない」と悩む ::: ## 今日の 3 行まとめ {#three-lines} 1. `$VAR` は変数の中身を呼び出す書き方。`echo $HOME` や `env` で今の状態を読める 2. `export` を付けた変数だけが、子プロセスに引き継がれる 3. `PATH` を追加するときは、末尾に `:$PATH` を必ず付ける ## 次に読む {#next} - [シェルスクリプトの書き方](/articles/tutorials/shell-scripting-basics) - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) # /etc/hosts と /etc/resolv.conf - 名前解決の優先順位 Source: https://penguin-gym-linux.com/articles/tutorials/etc-hosts-resolv ## /etc/hosts と /etc/resolv.conf とは? {#intro} `/etc/hosts` はホスト名と IP アドレスの静的なマッピングを記述するローカルファイルで、DNS を経由せず即座に解決できる。`/etc/resolv.conf` は DNS リゾルバの設定ファイルで、参照するネームサーバを指定する。どちらを先に参照するかは `/etc/nsswitch.conf` の `hosts:` 行が制御し、デフォルトは `files dns` でホストファイルが DNS より優先される。 ::: tip **結論(名前解決の優先順位)** `/etc/nsswitch.conf` の `hosts: files dns` が意味するもの: 1. `/etc/hosts` を検索(一致があれば即返す) 2. DNS(`/etc/resolv.conf` に従う)を問い合わせ ::: ## 名前解決はどのような流れで行われるのか? {#resolution-flow} Linux の名前解決は NSS(Name Service Switch)が統括する。`/etc/nsswitch.conf` を見ると、`hosts:` 行に解決ソースが優先順位付きで列挙されている。 ```bash $ grep ^hosts /etc/nsswitch.conf hosts: files mdns4_minimal [NOTFOUND=return] dns ``` - `files` = `/etc/hosts` - `mdns4_minimal` = mDNS(Avahi)による `.local` ドメイン解決 - `dns` = `/etc/resolv.conf` で指定したネームサーバ 解決フロー(デフォルト設定): ``` アプリが getaddrinfo("example.com") を呼ぶ ↓ 1. /etc/hosts を検索 → 一致あり: 即座にその IP を返す → 一致なし: 次へ ↓ 2. DNS リゾルバ (/etc/resolv.conf の nameserver) に問い合わせ → 応答を返す ``` ## /etc/hosts の書式と活用パターン {#etc-hosts} ### 書式 ``` IPアドレス ホスト名 [エイリアス ...] ``` ```bash $ cat /etc/hosts 127.0.0.1 localhost 127.0.1.1 myhost.local myhost ::1 localhost ip6-localhost ip6-loopback ``` ### 開発環境でのドメイン上書き 本番ドメインをローカル IP に向けてテストする典型パターン: ```bash # example.com をローカル開発サーバに向ける 127.0.0.1 example.com 127.0.0.1 api.example.com ``` ### 不要なドメインのブロック `0.0.0.0` に向けるとそのドメインへの接続をブロックできる: ```bash 0.0.0.0 ads.example.com ``` ::: warning `/etc/hosts` の変更は即座に反映される(DNS キャッシュのフラッシュ不要)。 ::: ## /etc/resolv.conf の設定 {#etc-resolv-conf} ### 主なディレクティブ | ディレクティブ | 役割 | | -------------- | --------------------------------------------------- | | `nameserver` | 参照する DNS サーバの IP(最大3件) | | `search` | ホスト名補完ドメイン(`db01` → `db01.example.com`) | | `domain` | `search` の単一ドメイン版(旧来の設定) | ```bash $ cat /etc/resolv.conf nameserver 8.8.8.8 nameserver 8.8.4.4 search example.com ``` ### Ubuntu での注意点 - systemd-resolved Ubuntu 20.04 以降では `/etc/resolv.conf` が `systemd-resolved` のスタブリゾルバへのシンボリックリンクになっている場合がある: ```bash $ ls -la /etc/resolv.conf lrwxrwxrwx 1 root root 39 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf ``` この場合、`resolv.conf` を直接編集しても再起動で上書きされる。DNS 設定の変更は `systemd-resolved` 経由で行う: ```bash # 現在の DNS 設定を確認 $ resolvectl status # 特定インターフェースの DNS を設定 $ resolvectl dns eth0 8.8.8.8 ``` ::: tip `/etc/resolv.conf` が symlink かどうかは `ls -la /etc/resolv.conf` で確認できる。symlink なら直接編集せず `resolvectl` を使う。 ::: ## 優先順位を制御する /etc/nsswitch.conf {#nsswitch} `/etc/nsswitch.conf` の `hosts:` 行を編集すると解決順序を変更できる。 ```bash # デフォルト(Ubuntu) hosts: files mdns4_minimal [NOTFOUND=return] dns # DNS を先にしたい場合(非推奨) hosts: dns files ``` ::: warning `hosts:` 行の変更はシステム全体の名前解決に影響する。変更前に元の値を記録しておくこと。 ::: ## デバッグコマンド {#debug} ### getent hosts - nsswitch.conf を通じた解決確認 `dig` や `nslookup` は DNS のみ問い合わせる(`/etc/hosts` を経由しない)。`getent hosts` は `nsswitch.conf` の設定に従って解決するため、OS が実際に何を返すかを確認できる。 ```bash # nsswitch.conf の設定に従って解決(hosts ファイルも含む) $ getent hosts example.com 93.184.216.34 example.com # /etc/hosts に書いたエントリが機能しているか確認 $ getent hosts myapp.local 127.0.0.1 myapp.local ``` ```bash # DNS のみ問い合わせ(hosts ファイルをバイパス) $ dig example.com $ nslookup example.com # nsswitch.conf 経由(hosts ファイルも含む) $ getent hosts example.com ``` ### resolvectl - systemd-resolved の状態確認 ```bash # DNS サーバと検索ドメインをインターフェース別に表示 $ resolvectl status # 特定ドメインの DNS 解決をテスト $ resolvectl query example.com ``` ## 実践シナリオ {#use-cases} ### シナリオ 1: 開発環境で本番ドメインをローカルに向ける ```bash # /etc/hosts に追記 $ sudo sh -c 'echo "127.0.0.1 api.myapp.com" >> /etc/hosts' # 動作確認 $ getent hosts api.myapp.com 127.0.0.1 api.myapp.com ``` テスト後は削除する: ```bash $ sudo sed -i '/api.myapp.com/d' /etc/hosts ``` ### シナリオ 2: カスタム DNS サーバに切り替える `systemd-resolved` を使用していない環境(静的な `resolv.conf` の場合): ```bash $ sudo nano /etc/resolv.conf # 以下を追加または変更 nameserver 1.1.1.1 # Cloudflare DNS nameserver 8.8.8.8 # Google DNS ``` 確認: ```bash $ dig @1.1.1.1 example.com ``` ## まとめ - 名前解決チェックリスト {#summary} | 確認項目 | コマンド | | ---------------------------- | -------------------------------- | | 解決順序 | `grep ^hosts /etc/nsswitch.conf` | | hosts ファイルの内容 | `cat /etc/hosts` | | DNS リゾルバ設定 | `cat /etc/resolv.conf` | | resolv.conf の symlink 確認 | `ls -la /etc/resolv.conf` | | DNS 設定の確認(Ubuntu) | `resolvectl status` | | 名前解決テスト(hosts 含む) | `getent hosts <ドメイン>` | | DNS のみテスト | `dig <ドメイン>` | ::: tip **よくあるトラブル** - `getent hosts` では解決できるが `dig` / `nslookup` では解決できない → `/etc/hosts` に書いたエントリが有効 - 両方で解決できない → DNS 設定か `resolv.conf` の問題 - Ubuntu で `resolv.conf` を編集しても再起動後に戻る → `systemd-resolved` が管理しているため `resolvectl` で変更する ::: ## 次に読む {#next} - [nc (netcat) 入門 - ポート疎通とデバッグの万能ツール](/articles/tutorials/netcat-basics) - [iptables/nftables 入門 - パケットフィルタの基礎](/articles/tutorials/iptables-nftables) # 終了ステータス(exit status)入門 - $? と && || で処理を分岐する Source: https://penguin-gym-linux.com/articles/tutorials/exit-codes-and-status ## この記事で学べること {#intro} - コマンドの **成功・失敗を表す「終了ステータス(exit status)」** という考え方がわかります - `$?` で **直前のコマンドの結果** を確認できるようになります - `&&` と `||` で **「成功したら」「失敗したら」** の処理を書き分けられます - シェルスクリプトで **エラーをきちんと扱う第一歩** を踏み出せます ::: tip **言葉の整理** - **終了ステータス**:コマンドが終わるときに返す数字です。「終了コード」「exit status」「exit code」は、すべて同じものを指す別の呼び方です。 - **変数**:値を入れておく名前付きの箱です。`$?` はシェルが自動で用意する特別な変数です。 - **シェルスクリプト**:コマンドを順番に並べて書いたファイルです。 この記事で使う `ls` / `echo` / `cd` / `mkdir` は、既存のファイルを消しません。安心して試してください。 ::: ::: tip **結論(先に覚えるべき型)** - コマンドの結果は **数字** で返ってくる(`0` が成功、それ以外が失敗) - 直前の結果を見たい → `echo $?` - **成功したら次へ** → `&&` - **失敗したら次へ** → `||` ::: ## 1. 終了ステータスとは何か? {#what} > **結論**: 終了ステータスはコマンドが返す数字。`0` が成功、`1` 〜 `255` が失敗を表す。 ::: dialogue @lina: 先輩、コマンドを実行すると結果が画面に出ますよね。でも「成功したのか失敗したのか」は、どうすればわかるのでしょうか。 @linny: いいところに気づいたね。実は **コマンドは終わるときに、必ず数字を 1 つ返している** んだ。それを **終了ステータス** と呼ぶよ。 @lina: 数字ですか。画面には出ていないようですが。 @linny: ふだんは見えないところに隠れているんだ。宅配便の伝票を想像してみて。荷物そのものとは別に、「届いた」「不在だった」という記録が残るよね。 @linny: それと同じで、コマンドも結果とは別に記録を残す。ルールは単純で、**`0` なら成功、`0` 以外なら失敗** だよ。 ::: ::: highlight **終了ステータスの基本ルール** | 数字 | 意味 | | ---------- | -------------------- | | `0` | 成功 | | `1`〜`255` | 失敗(何らかの異常) | 「`0` が成功」という点は直感に反します。最初に必ず覚えてください。 ::: ## 2. なぜ終了ステータスが重要なのか? {#why} > **結論**: 終了ステータスがあるから「成功したときだけ次を実行」といった自動化ができる。 ::: dialogue @lina: 数字が返っているのはわかりました。でも、それは何の役に立つのでしょうか。 @linny: たとえば「**バックアップが成功したときだけ、古いファイルを消す**」という処理を考えてみて。失敗したのに消したら大事故だよね。 @lina: たしかにそうですね。成功したかどうかで次の動きを変えたいです。 @linny: そう。その判断に使うのが終了ステータスなんだ。だから自動化やシェルスクリプトを書くときの **土台** になる。 ::: ::: warning 画面の表示だけで成否を判断するのは危険です。「Error」という文字が出ていなくても、失敗していることがあります。 **機械が判断できる終了ステータスを使う** ほうが確実です。 ::: ## 3. $? で結果を確認する {#check} > **結論**: `$?` には直前のコマンドの終了ステータスが入る。`echo $?` で中身を見られる。 直前に実行したコマンドの終了ステータスは、`$?` という特別な変数に入っています。この変数はシェルが自動で用意します。 ```bash $ ls /etc ``` ```output hosts passwd ... ``` ```bash $ echo $? ``` ```output 0 ``` ::: dialogue @lina: `0` が出ました! `ls` が成功したってことですね。 @linny: その通り。じゃあ次は、わざと失敗させてみよう。存在しないファイルを `ls` してみて。 ::: ```bash $ ls /not-exist ``` ```output ls: '/not-exist' にアクセスできません: そのようなファイルやディレクトリはありません ``` ```bash $ echo $? ``` ```output 2 ``` ::: dialogue @lina: 今度は `2` が出ました。さきほどと数字が違います。 @linny: 失敗のときは `0` 以外が返る。`ls` は「ファイルが見つからない」というエラーのとき `2` を返す決まりなんだ。 @linny: **数字の中身までは覚えなくていい。`0` か、それ以外か** だけ見れば十分だよ。 ::: ::: warning `$?` は **直前のコマンドの結果** しか覚えていません。 `echo $?` を 2 回続けると、2 回目は 1 回目の `echo` の結果を表示します。`echo` は成功するので `0` になります。確認は 1 回だけにしてください。 ::: ## 4. && で「成功したら次」をつなぐ {#and} > **結論**: `cmd1 && cmd2` は cmd1 が成功(`0`)したときだけ cmd2 を実行する。 `&&` は **「左が成功したら、右も実行する」** という意味の記号です。 ```bash $ mkdir backup && cp data.txt backup/ ``` ::: dialogue @lina: これは `mkdir` が成功したら `cp` する、という意味ですか。 @linny: そのとおり。もし `mkdir backup` が失敗したら、`cp` は **実行されない**。権限がなくて作れなかった場合などだね。 @linny: 「前の処理が成功していることが前提」のときに使うんだ。 ::: ```bash # ビルドが成功したときだけテストを走らせる $ make && make test # ディレクトリへ移動できたときだけ中身を表示 $ cd /var/log && ls ``` ::: tip `&&` は「前提が満たされたら進む」というイメージです。**失敗したらそこで止まってほしい** 処理を、安全につなげられます。 ::: ## 5. || で「失敗したら次」をつなぐ {#or} > **結論**: `cmd1 || cmd2` は cmd1 が失敗したときだけ cmd2 を実行する。エラー時の対処に使う。 `||` は `&&` の逆です。**「左が失敗したら、右を実行する」** という意味になります。 ```bash $ cd /var/log || echo "ディレクトリへ移動できませんでした" ``` ::: dialogue @lina: `cd` が失敗したらメッセージを出す、ということですね。成功したときは出ないのでしょうか。 @linny: そのとおり。`cd` が成功すれば `echo` は実行されない。`||` は **うまくいかなかったときの保険** として使うことが多いよ。 ::: ```bash # コマンドが無ければインストールを促す $ which jq || echo "jq が入っていません。インストールしてください" # 処理に失敗したらスクリプトを終了 $ cp data.txt backup/ || exit 1 ``` ::: highlight **&& と || の対比** ``` cmd1 && cmd2 → cmd1 が【成功】したら cmd2 cmd1 || cmd2 → cmd1 が【失敗】したら cmd2 ``` 「`&&` は成功で進む」「`||` は失敗で進む」と覚えてください。 ::: ## 6. && と || を組み合わせる {#combine} > **結論**: `cmd && 成功時 || 失敗時` で「成功なら A、失敗なら B」を1行で書ける。 `&&` と `||` をつなげると、成功したときと失敗したときで別々の処理を書けます。 ```bash $ ping -c1 example.com > /dev/null && echo "接続OK" || echo "接続NG" ``` ::: dialogue @lina: 「成功したら接続OK、失敗したら接続NG」を 1 行で書けるんですね。 @linny: そう。ただし注意点がある。この書き方には落とし穴があるんだ。 @linny: 成功時の処理、つまり `echo "接続OK"` 自体が失敗すると、失敗時の処理まで動いてしまう。 @linny: 簡単なメッセージ表示なら問題ない。複雑な処理では `if` 文を使うほうが安全だよ。 ::: ::: warning `A && B || C` は、`if A then B else C` と完全に同じではありません。**B が失敗すると C も実行されます。** 確実に分岐したいときは `if` 文を使ってください。書き方は「次に読む」の記事で説明します。 ::: ## 7. よくある初心者のつまずき {#pitfalls} > **結論**: `0` が成功という点と、`$?` が直前専用という点を取り違えやすい。 ### 7-1. 「0 が成功」を逆に覚える ::: dialogue @lina: 正直に言うと、`0` が成功というのは今でも違和感があります。 @linny: みんなそうだよ。**「0 = エラーなし = 問題ゼロ」** と考えると覚えやすい。 @linny: テストの点数ではなく、「エラーの個数」だとイメージするといいよ。 ::: ### 7-2. リナの失敗:$? を取るタイミングが遅い ```bash $ ls /not-exist $ pwd # ← 余計なコマンドを挟んでしまった $ echo $? # これは pwd(成功)の結果 0 になる ``` ::: dialogue @lina: 失敗したはずのコマンドなのに `0` が出ました。終了ステータスが壊れているのでしょうか。 @linny: 壊れてないよ。`$?` は直前の 1 つ分しか覚えていないんだ。 @lina: えっ、間に挟んだ `pwd` の結果に置きかわっていたんですか。 @linny: そう。`pwd` は成功するから `0` になる。`ls` の結果はもう上書きされているんだ。 @lina: 見たかった結果が消えていたんですね。納得しました。すぐ次で見るようにします。 ::: ::: warning 確認したいコマンドの **すぐ次** で `$?` を見てください。間に別のコマンドを挟むと、`$?` の中身が上書きされます。 ::: ### 7-3. 終了ステータスを自分で返したい スクリプトの中で、成功したか失敗したかを呼び出し元に伝えたいときは `exit` を使います。 ```bash exit 0 # 成功として終了 exit 1 # 失敗として終了 ``` `exit` に数字を付けない場合は、**直前のコマンドの終了ステータス** がそのまま返ります。 ## 8. ミニ課題:実際にやってみよう {#exercise} > **結論**: 確認・成功時実行・失敗時実行の3問で、$? と && || を手で確かめる。 ::: dialogue @lina: 知識は入りました。手を動かしてみたいです。 @linny: いいね、3 問用意したよ。ターミナルで試してみて。 ::: **課題 1**: コマンドを 1 つ実行した直後に、その終了ステータスを画面に表示しよう。 :::details ヒント 1(方向づけ)を見る 直前の結果は、シェルが自動で用意する特別な変数に入っています。その中身を画面に出します。 ::: :::details ヒント 2(コマンド名)を見る 変数は `$?` です。表示は `echo` です。 ::: :::details 答えを見る ```bash $ ls $ echo $? ``` ```output 0 ``` `ls` が成功したので `0` が出ます。存在しないディレクトリを指定すると、`0` 以外の数字に変わります。 ::: **課題 2**: `mkdir test-dir` が成功したときだけ「作成成功」と表示しよう。 :::details ヒント 1(方向づけ)を見る 2 つのコマンドを 1 行につなぎます。左が成功したときだけ右が動く記号を使います。 ::: :::details ヒント 2(コマンド名)を見る 使う記号は `&&` です。表示は `echo` です。 ::: :::details 答えを見る ```bash $ mkdir test-dir && echo "作成成功" ``` ```output 作成成功 ``` 2 回目に同じコマンドを実行すると、`mkdir` が失敗します。そのため「作成成功」は表示されません。試したディレクトリは `rmdir test-dir` で削除できます。 ::: **課題 3**: 存在しないディレクトリへ `cd` を試み、失敗したときだけ「移動できません」と表示しよう。 :::details ヒント 1(方向づけ)を見る 課題 2 と同じ形ですが、記号が逆になります。左が失敗したときだけ右が動く記号を使います。 ::: :::details ヒント 2(コマンド名)を見る 使う記号は `||` です。移動は `cd` です。 ::: :::details 答えを見る ```bash $ cd /not-exist || echo "移動できません" ``` ```output bash: cd: /not-exist: そのようなファイルやディレクトリはありません 移動できません ``` `cd` 自体のエラーメッセージと、自分で出したメッセージの両方が表示されます。 ::: ## 9. 振り返り {#review} ::: dialogue @lina: 整理します。コマンドは終わるときに数字を返す。`0` が成功で、それ以外が失敗ですね。 @linny: そのとおり。その数字は `echo $?` で見られる。ただし直前の 1 回分だけだよ。 @lina: そして `&&` は成功したら次へ、`||` は失敗したら次へ。ここまで覚えました。 @linny: 完璧だね。あとは自分のスクリプトの最後に `exit 0` と `exit 1` を書き分けてみて。 ::: ## 10. コピペ用テンプレート {#templates} > **結論**: 確認・成功時・失敗時・成否分岐・終了のよく使う型をまとめて手元に置いておく。 ::: tip **よく使う型をまとめておく** ```bash # 直前の終了ステータスを確認 echo $? # 成功したときだけ次を実行 コマンドA && コマンドB # 失敗したときだけ次を実行 コマンドA || コマンドB # 成功なら A、失敗なら B(簡易分岐) コマンド && echo "OK" || echo "NG" # 失敗したらスクリプトを終了 コマンド || exit 1 # スクリプトを明示的に成功 / 失敗で終わらせる exit 0 # 成功 exit 1 # 失敗 ``` ::: ## 今日の 3 行まとめ {#three-lines} 1. コマンドは終わるときに数字を返す。`0` が成功、それ以外は失敗 2. 直前の結果は `echo $?` で確認する。間に別のコマンドを挟まない 3. `&&` は成功したら次へ進む。`||` は失敗したら次へ進む ## 次に読む {#next} - [test / [[]] 入門 - シェルスクリプトの条件判定](/articles/tutorials/test-conditionals) - [パイプとリダイレクト入門 - データの流れを理解する](/articles/tutorials/pipe-redirect-basics) - [コマンド置換入門 - $(...) でコマンド結果を変数に取り込む](/articles/tutorials/command-substitution) - [仮想ターミナルで練習する](/terminal) # fd 入門 - find より直感的なファイル検索ツール Source: https://penguin-gym-linux.com/articles/tutorials/fd-find-alternative ## この記事で解決できること {#intro} - `find` の複雑な構文から解放され、`fd PATTERN` だけで再帰検索できるようになる - Ubuntu/Debian 特有の **`fdfind` 問題**(コマンド名が `fd` でない)を解決できる - 拡張子・型・gitignore 連携・`-x` 実行など **実務で使う型** が身につく ::: tip **結論(使い分けの型)** - **日常の「あのファイルどこ?」** → `fd PATTERN`(速い・短い・色付き) - **`.gitignore` を無視して全部探す** → `fd -H -I PATTERN` - **find が必要なケース** → 複雑な `-newer` 条件、`-printf` 整形、POSIX 限定環境 ::: ::: warning **前提(対象環境)** - OS: Ubuntu 22.04 / 24.04(Debian 系)を主対象。他ディストリは適宜読み替え - fd 8.x 以降を想定 ::: ## fd とは何か? {#what} > **結論**: fd は Rust 製のモダンなファイル検索ツール。`find` の代替として、短い構文・高速・gitignore 自動尊重・カラー出力を最初から備える。 `fd` は [sharkdp/fd](https://github.com/sharkdp/fd) が開発する、`find` の使いやすい代替コマンド。`find` の全機能を置き換えるものではないが、日常的なファイル検索の **9 割** は fd の方が速く・短く書ける。 `find` との主な違い: | 観点 | find | fd | | ------------ | ------------------------------- | --------------------------------- | | 基本構文 | `find . -name '*.txt'` | `fd '\.txt$'` / `fd -e txt` | | マッチ方式 | デフォルトは完全一致(`-name`) | 部分一致の正規表現 | | 大文字小文字 | 区別する | スマートケース(小文字なら無視) | | 隠しファイル | 検索する | デフォルトで除外(`-H` で含める) | | `.gitignore` | 無視しない | 自動で尊重(`-I` で無効化) | | 出力 | 単色 | カラー(型ごとに色分け) | | 速度 | 標準 | 並列処理で高速 | ## fd をインストールするには? {#install} > **結論**: Ubuntu/Debian では `apt install fd-find` でインストールするが、コマンド名は `fd` ではなく `fdfind` になる。シンボリックリンクか alias で `fd` に揃えるのが定番。 ディストリごとのインストール: ```bash # Ubuntu / Debian sudo apt install fd-find # Fedora / RHEL 系 sudo dnf install fd-find # Arch Linux sudo pacman -S fd # macOS (Homebrew) brew install fd # Rust の cargo から cargo install fd-find ``` ::: warning **Ubuntu/Debian の罠: コマンド名が `fdfind`** Debian には別パッケージ(`fdclone`)との名前衝突があるため、バイナリ名が `fd` ではなく `fdfind` になっている。そのまま `fd` と打つと `command not found` になる。 ::: `fd` という名前で使いたい場合は、`~/.local/bin` にシンボリックリンクを張る: ```bash mkdir -p ~/.local/bin ln -s "$(which fdfind)" ~/.local/bin/fd ``` `~/.local/bin` が `PATH` に含まれていない場合は、`~/.bashrc` に以下を追記して再読み込みする: ```bash echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc source ~/.bashrc ``` インストールとバージョンの確認: ```bash fd --version ``` ```output fd 9.0.0 ``` ## fd の基本的な使い方は? {#basic} > **結論**: `fd PATTERN` でカレントディレクトリ以下を再帰検索する。パターンは部分一致の正規表現で、検索対象のパスは第 2 引数で指定できる。 最小の使い方は、探したい名前の一部を渡すだけ: ```bash fd readme ``` ```output README.md docs/readme-ja.md src/readme.txt ``` `find` だと `find . -iname '*readme*'` と書くところが、`fd readme` で済む。大文字小文字はスマートケース(パターンが全部小文字なら区別しない)。 検索を始めるディレクトリは第 2 引数で指定する: ```bash # src/ 以下から config を探す fd config src/ ``` パターンは正規表現として解釈される。ドットやアンカーをそのまま使える: ```bash # .log で終わるファイル fd '\.log$' ``` ::: tip 引数を 1 つも渡さない `fd` は、カレント以下の全ファイル・ディレクトリを一覧する(`.gitignore` と隠しファイルは除外)。`ls -R` の代わりに使える。 ::: ## 拡張子・型で絞り込むには? {#filter} > **結論**: 拡張子は `-e EXT`、種類は `-t TYPE`(f=ファイル / d=ディレクトリ / l=シンボリックリンク / x=実行可能)で絞り込む。複数指定は OR 条件になる。 拡張子での絞り込みは `-e`(`--extension`)。ドットは不要: ```bash # .jpg と .png を探す(複数指定は OR) fd -e jpg -e png ``` 種類での絞り込みは `-t`(`--type`): ```bash # ディレクトリだけを探す fd -t d node_modules # 実行可能ファイルだけ fd -t x # 空のファイル / ディレクトリ fd -t empty ``` 主な型の値: - `f` … 通常ファイル - `d` … ディレクトリ - `l` … シンボリックリンク - `x` … 実行可能ファイル - `e` … 空(empty) パターンと組み合わせれば「src 以下の .rs ファイル」のような検索も一発: ```bash fd -e rs main src/ ``` ## 隠しファイルや .gitignore 対象も検索するには? {#hidden} > **結論**: fd はデフォルトで隠しファイルと `.gitignore` 記載のファイルを除外する。`-H` で隠しファイルを含め、`-I` で無視設定を無効化、`-u`(unrestricted)で両方を一度に解除できる。 fd の「気が利く」挙動が、状況によっては邪魔になる。`.env` や `node_modules/` を探したいのに出てこないのはこのため。 ```bash # 隠しファイル(ドット始まり)も含める fd -H '\.env' # .gitignore / .ignore を無視してすべて検索 fd -I node_modules # 上記2つを同時に解除(unrestricted) fd -u secret ``` ::: tip `-u` は `--no-ignore --hidden` の短縮(`-HI` と等価)。 ::: 逆に、特定のディレクトリを明示的に除外したいときは `-E`(`--exclude`): ```bash # .git とビルド成果物を除外 fd -E .git -E dist -e js ``` ## 見つけたファイルにコマンドを実行するには? {#exec} > **結論**: `-x`(`--exec`)は結果 1 件ごとにコマンドを並列実行、`-X`(`--exec-batch`)は全結果をまとめて 1 回だけ実行する。`find -exec` より速く構文も短い。 `find ... -exec` の置き換えが `-x` / `-X`。プレースホルダで結果を埋め込める。 ```bash # .tmp ファイルを1件ずつ削除(並列実行) fd -e tmp -x rm # すべての .png をまとめて optipng に渡す(1プロセス) fd -e png -X optipng ``` プレースホルダ(`-x` 内で使える): - `{}` … マッチしたパス全体 - `{/}` … ファイル名のみ(basename) - `{//}` … 親ディレクトリ - `{.}` … 拡張子を除いたパス - `{/.}` … 拡張子を除いたファイル名 実例: 全 `.jpg` を同名の `.png` に変換する: ```bash fd -e jpg -x convert {} {.}.png ``` ::: warning `-x rm` のような破壊的操作は、まず実行コマンドを付けずに `fd -e tmp` で **対象一覧を目視確認** してから流すこと。fd は `.gitignore` を尊重する分、想定外のファイルが漏れる/含まれる可能性がある。 ::: ## find と fd の使い分けは? {#compare} > **結論**: 日常検索は fd、複雑な条件・整形出力・POSIX 限定環境は find。両者は排他ではなく、速さと手軽さで fd を主、特殊条件で find を従とするのが実務の型。 fd で十分なケース: ```bash # 直近10分以内に変更されたファイル fd --changed-within 10min # 1MB を超えるファイル fd -S +1M # glob で書きたいとき fd -g '*.config.js' ``` find を選ぶべきケース: - `-printf` で出力を細かく整形したい - `-newer fileA ! -newer fileB` のような複合的な時刻比較 - fd を入れられない / 入れたくない本番サーバ(fd は標準では入っていない) ::: tip **コピペ用: よく使う型** ```bash fd PATTERN # 名前で再帰検索 fd -e log # 拡張子で絞る fd -t d PATTERN # ディレクトリのみ fd -H -I PATTERN # 隠し/除外も含めて全検索 fd -e tmp -x rm # 結果ごとに実行 fd --changed-within 1d # 直近1日の変更 ``` ::: ## 次に読む {#next} - [find・grep・awk の使い方](/articles/tutorials/find-grep-awk-mastery) - [locate/which/whereis でコマンドの場所を探す](/articles/tutorials/locate-which-whereis) - [fzf 入門 - あいまい検索でファイルを絞り込む](/articles/tutorials/fzf-fuzzy-finder) # file コマンド入門 - 拡張子に頼らずファイル種別を見抜く Source: https://penguin-gym-linux.com/articles/tutorials/file-command-type ## この記事でわかること {#intro} - `file` コマンドが **拡張子ではなく中身を見て** ファイル種別を判定する仕組み - 拡張子を書き換えても `file` が **本当の正体を見抜く** 理由 - `file -i` で **MIME タイプ** を調べる方法 - `-b` や圧縮ファイル対応など **よく使うオプション** ::: tip **結論(先に要点)** - `file ファイル名` で、そのファイルが何者かを一発で判定できる - 判断材料は **拡張子ではなく中身の先頭バイト列(マジックナンバー)** - 拡張子を `.txt` に変えても、中身が JPEG なら `file` は JPEG と答える ::: ::: warning **前提(対象環境)** - OS: Ubuntu / 一般的な Linux - `file` は標準でインストール済み(`coreutils` とは別パッケージ `file`) ::: ## 1. file コマンドとは?拡張子と何が違う? {#what} > **結論**: `file` はファイルの中身を読んで種別を判定するコマンド。拡張子という「自己申告」ではなく、実体で判断する。 ::: dialogue @lina: ファイルの種類って、`photo.jpg` みたいに拡張子を見ればわかりますよね? @linny: 普段はそれでいい。でも拡張子は「自己申告」なんだ。名前を変えれば中身と食い違うこともある。`file` は中身そのものを読んで判定するコマンドだよ。 @lina: 中身を読む…?ファイルを開かないと中身ってわからないんじゃ? @linny: 多くのファイル形式は、先頭の数バイトに「自分は何者か」を示す目印を持っているんだ。これを **マジックナンバー** と呼ぶ。`file` はそこを読んでいる。 ::: ```bash file report.pdf ``` ```output report.pdf: PDF document, version 1.7 ``` ::: tip `file` の判定は `/usr/share/misc/magic`(コンパイル済みは `magic.mgc`)に登録されたパターン集(libmagic)に基づく。何千もの形式の目印が登録されている。 ::: ## 2. なぜ拡張子は当てにならないのか? {#why} > **結論**: 拡張子は名前の一部にすぎず、中身を保証しない。`file` は中身で判断するため、偽装された拡張子に騙されない。 ::: dialogue @lina: 拡張子が当てにならないって、どういう場面で困るんですか? @linny: たとえば画像ファイルの名前を間違えて `.txt` にしてしまった場合。Windows からもらったファイルで拡張子が消えている場合もある。そんなとき中身を確認したいよね。 @lina: 試してみます。画像の名前を `.txt` に変えてみますね。 ::: ```bash mv penguin.jpg penguin.txt file penguin.txt ``` ```output penguin.txt: JPEG image data, JFIF standard 1.01, resolution (DPI), 72x72 ``` ::: dialogue @lina: 名前は `.txt` なのに、ちゃんと JPEG だって見抜いた! @linny: そう。名前ではなく中身を見ているから、こうなる。逆に拡張子のないファイルでも種別がわかるんだ。 ::: ## 3. file コマンドの基本的な使い方は? {#basic} > **結論**: `file ファイル名` が基本形。複数指定やワイルドカードで、まとめて種別を確認できる。 ::: dialogue @lina: 基本の使い方を教えてください。 @linny: ファイル名を渡すだけ。複数並べてもいいし、`*` でまとめても判定してくれるよ。 ::: ```bash file notes.txt file * ``` ```output notes.txt: ASCII text archive.tar.gz: gzip compressed data, original size modulo 2^32 10240 script.sh: Bourne-Again shell script, ASCII text executable image.png: PNG image data, 800 x 600, 8-bit/color RGBA, non-interlaced ``` ::: tip ディレクトリやシンボリックリンク、空ファイルもそれぞれ `directory` / `symbolic link to ...` / `empty` と表示される。 ::: ## 4. MIME タイプを調べるには?(-i) {#mime} > **結論**: `file -i` で `text/plain; charset=us-ascii` のような MIME タイプと文字コードを取得できる。スクリプトでの判定に向く。 ::: dialogue @lina: ときどき見る `text/plain` みたいな表記は、`file` で出せますか? @linny: `-i`(`--mime`)を付けると MIME タイプ形式で出る。プログラムから扱いやすい形だよ。文字コード(charset)も一緒に表示される。 ::: ```bash file -i notes.txt file -i report.pdf ``` ```output notes.txt: text/plain; charset=us-ascii report.pdf: application/pdf; charset=binary ``` ::: tip MIME タイプだけ・文字コードだけが欲しいときは `--mime-type` / `--mime-encoding` を使う。 ```bash file --mime-type photo.png ``` ```output photo.png: image/png ``` ::: ## 5. よく使うオプションは? {#options} > **結論**: `-b`(ファイル名省略)、`-L`(リンク先を辿る)、`-z`(圧縮の中身)、`-s`(デバイスファイル)あたりを押さえれば十分。 | オプション | 意味 | | ---------------- | ----------------------------------------- | | `-b` / `--brief` | ファイル名を出力せず種別だけ表示 | | `-i` / `--mime` | MIME タイプ形式で出力 | | `-L` | シンボリックリンクのリンク先を判定 | | `-z` | 圧縮ファイルの中身まで覗いて判定 | | `-s` | ブロック / キャラクタデバイスを読んで判定 | | `-k` | 最初の一致で止めず候補を出し続ける | ::: dialogue @lina: `-b` はどんなときに便利ですか? @linny: 出力をスクリプトで受け取るとき。ファイル名が前に付くと邪魔なので、種別だけ欲しいなら `-b` が楽だよ。 ::: ```bash file -b script.sh ``` ```output Bourne-Again shell script, ASCII text executable ``` ::: tip `-z` を使うと `.gz` などを展開せずに「中身が何の圧縮か+元ファイルの種別」まで確認できる。 ```bash file -z logs.tar.gz ``` ::: ## 6. 実務ではどんな場面で使う? {#usecase} > **結論**: ダウンロードファイルの正体確認、テキストかバイナリかの判別、展開前のアーカイブ確認などで活躍する。 ::: dialogue @lina: 実際の作業だと、どこで使うんでしょう? @linny: たとえば `curl` で落としたファイルが本当に目的のものか確認するとき。HTML のエラーページが落ちてきていないか `file` で一発チェックできる。 ::: ```bash curl -sL https://example.com/app.tar.gz -o app.tar.gz file app.tar.gz ``` ```output app.tar.gz: gzip compressed data, ... ``` ::: warning ここで `HTML document` と出たら、アーカイブではなくエラーページを落としている。展開前に気づけるのが `file` の価値。 ::: ## 7. つまずきやすいポイントは? {#pitfalls} > **結論**: `file` の判定は推定であり 100% ではない。シンボリックリンクは `-L`、デバイスファイルは `-s` を忘れると意図と違う結果になる。 ::: danger `file` の出力はあくまで **推定**。マジックナンバーを持たない独自形式や、内容が短すぎるファイルは `data` や `ASCII text` と曖昧に出ることがある。重要な判定は他の手段(`stat` / チェックサム等)と併用する。 ::: ::: dialogue @lina: シンボリックリンクを `file` したら、リンクそのものの情報しか出ませんでした。 @linny: デフォルトはリンク自体を見るからね。リンク先の実体を判定したいときは `-L` を付ける。 ::: ```bash file -L /usr/bin/python3 ``` ## 8. ミニ課題 {#practice} > **結論**: 手を動かすと定着する。拡張子を変えても `file` が見抜くことを自分で確かめよう。 ::: tip **やってみよう** 1. 適当なテキストを作る: `echo "hello" > sample.txt` 2. 種別を確認: `file sample.txt` 3. 名前を変える: `mv sample.txt sample.bin` 4. もう一度判定: `file sample.bin` —— 拡張子が変わっても結果はどうなる? ::: :::details 解答例 `sample.bin: ASCII text` と表示される。中身は変わっていないので、`file` の判定(ASCII text)も変わらない。拡張子に依存していない証拠。 ::: ## 次に読む {#next} - [stat でファイルの詳細情報を調べる](/articles/tutorials/stat-file-inspection) - [チェックサムでファイルの整合性を確認する](/articles/tutorials/checksum-md5-sha256) - [less / more / tail でファイルの中身を読む](/articles/tutorials/less-more-tail) # mkdir・touch・echoの使い方 - Linuxファイル作成コマンド入門 Source: https://penguin-gym-linux.com/articles/tutorials/file-creation-basics ## この記事でできるようになること {#intro-list} > **結論**: mkdir・touch・echo・catの4コマンドでファイルとディレクトリの作成と確認が一通りできる。 - `mkdir` で新しいディレクトリを作成できます - `touch` で空のファイルを作成できます - `echo` で文字を出力できます。ファイルに書き込むこともできます - `cat` でファイルの内容を表示できます **対象読者**:pwd・cd・ls を習得した方。ファイル作成の次のステップに進みたい方。 ::: tip **言葉の整理** - **ディレクトリ**:Windows や Mac でいうフォルダのことです。「フォルダ」「ディレクトリ」は同じものを指す別の呼び方です。 - **ターミナル**:コマンドを打ち込む画面です。「シェル」「コンソール」「コマンドライン」もほぼ同じものを指します。 - **リダイレクト**:コマンドの出力先を画面からファイルへ切りかえる仕組みです。記号は `>` と `>>` を使います。 この記事の `mkdir` / `touch` / `cat` は、ファイルを消すコマンドではありません。ただし `echo` と組み合わせる `>` は、ファイルの中身を置きかえます。詳しくは「ファイルに書き込む」の章で説明します。記事の最後では、練習用ディレクトリを消す `rm` も使います。 本サイトの仮想ターミナルは学習用です。あなたのパソコンは壊れません。安心して試してください。 ::: ## 導入:リナの次の一歩 {#intro} > **結論**: pwd・cd・lsの次のステップとしてファイルを作り書き込み確認する4つの操作を習得する。 ::: dialogue @lina: ライニー先輩!この前教わった `pwd`、`cd`、`ls` はバッチリ使えるようになりました! @linny: お、すごいね!じゃあ次のステップに進もう。今度は「ファイルを作る」方法を覚えよう。 @lina: ファイルを作る、ですか。Windows だと右クリックで「新規作成」ですけど、コマンドでもできるんですか? @linny: もちろんできるよ。今日は 4 つのコマンドを覚えよう。`mkdir`(ディレクトリ作成)、`touch`(ファイル作成)、`echo`(文字出力)、`cat`(ファイル表示)だね。 @linny: この 4 つがそろえば、自分でファイルを作れる。中身を書き込むところまでできるようになるよ。 ::: ## mkdir - ディレクトリを作ろう {#mkdir} > **結論**: mkdirでディレクトリを作り-pオプションで複数階層を一度に作成しlsで結果を確認する。 ::: dialogue @linny: まずは `mkdir` コマンドだよ。「Make Directory」の略だね。新しいディレクトリを作るコマンドだよ。 @lina: ディレクトリって、ファイルを入れる箱みたいなものですよね? @linny: そのとおり。まずは練習用のディレクトリを作ってみよう。 ::: ### 基本的な使い方 {#mkdir-basic} ```bash $ mkdir practice ``` ::: dialogue @lina: あれ?何も表示されないですけど、大丈夫ですか? @linny: 大丈夫だよ。Linux のコマンドは「成功したら何も言わない」のが基本なんだ。 @linny: 確認したいときは `ls` を使おう。 ::: ```bash $ ls ``` ```output documents downloads pictures practice ``` ::: tip **ここがポイント**:`mkdir` の結果は `ls` で確認する習慣をつけましょう。Linux には「沈黙は成功の証」という考え方があります。エラーがなければ、何も表示されないことが多いのです。 ::: ### 複数階層のディレクトリを一度に作る(-p オプション) {#mkdir-p} ::: dialogue @lina: `practice` の中に、さらに `lesson1` を作りたいんですけど。 @linny: いい質問だね。`-p` オプションを使おう。途中のディレクトリも一緒に作ってくれるよ。 ::: ```bash $ mkdir -p practice/lesson1/exercises ``` ::: dialogue @lina: `practice`、`lesson1`、`exercises` が一気にできるんですね。 @linny: そう。`-p` なしだと、`practice` がない場合にエラーになる。`-p` は「途中の階層も作ってよい」という指示だよ。 ::: ## touch - 空のファイルを作ろう {#touch} > **結論**: touchで空ファイルを作成しスペース区切りで複数ファイルを一度に作ることもできる。 ::: dialogue @linny: 次は `touch` コマンドだよ。これは空のファイルを作るコマンドだね。 @lina: 「タッチ」って触るという意味ですよね。ファイルを触る、ですか? @linny: 本来の用途は「タイムスタンプを更新する」ことなんだ。タイムスタンプは、ファイルの最終更新日時のことだよ。 @linny: ただし、指定したファイルが存在しないときは新しく作ってくれる。だからファイル作成にもよく使われるんだ。 ::: ### 基本的な使い方 {#touch-basic} ```bash $ touch memo.txt $ ls ``` ```output documents downloads memo.txt pictures practice ``` ::: dialogue @lina: `memo.txt` ができました!でも中身は空っぽなんですよね? @linny: そう。中身を書き込むには次の `echo` コマンドを使うよ。 ::: ### 複数ファイルを一度に作成 {#touch-multiple} ```bash $ touch file1.txt file2.txt file3.txt $ ls ``` ```output file1.txt file2.txt file3.txt memo.txt ... ``` ::: tip **ここがポイント**:`touch` は複数のファイル名をスペースで区切って指定できます。こうすると、一度に複数のファイルを作成できます。 ::: ## echo - 文字を出力しよう {#echo} > **結論**: echoで文字を出力しリダイレクト>で上書き>>で追記してファイルに内容を書き込む。 ::: dialogue @linny: `echo` コマンドは文字列を出力するコマンドだよ。英語で「こだま」という意味だね。入力した文字をそのまま返してくれるよ。 ::: ### 画面に文字を表示 {#echo-display} ```bash $ echo Hello ``` ```output Hello ``` ```bash $ echo "Hello, Linux World!" ``` ```output Hello, Linux World! ``` ::: dialogue @lina: 入力した文字がそのまま表示されますね。でも、これだけだと使い道が少なそうです。 @linny: そう思うよね。`echo` の本領発揮は「ファイルへの書き込み」なんだ。 ::: ### ファイルに書き込む(リダイレクト) {#echo-redirect} ```bash $ echo "これはメモです" > memo.txt ``` ::: dialogue @lina: `>` って何ですか? @linny: これは「リダイレクト」と呼ぶ記号だよ。ふだん画面に出る文字を、ファイルの中に書き込むように行き先を変える機能だね。 @linny: 水道の蛇口を別の場所に向けるイメージだよ。`>` は「上書き」、`>>` は「追記」だね。 ::: ```bash $ echo "1行目のメモ" > memo.txt $ echo "2行目を追加" >> memo.txt ``` ::: danger **`>` はファイルの中身を消してから書きます** `>` を使うと、そのファイルの中身は完全に置きかわります。GUI と違って、消えた中身はゴミ箱に行きません。すぐに元へは戻せません。 安全に試すための 3 つの型を覚えてください。 1. 練習用のディレクトリを作り、その中だけで試す(例:`mkdir -p ~/file-practice && cd ~/file-practice`) 2. 書き込む前に `cat ファイル名` で今の中身を見る 3. 中身を残したいときは `>`(1 つ)ではなく `>>`(2 つ)を使う 本サイトの仮想ターミナルは学習用です。ここで何度失敗しても、あなたのパソコンのファイルは消えません。安心して試してください。 ::: ## cat - ファイルの中身を見よう {#cat} > **結論**: catでファイル全体を表示し-nで行番号付き表示や複数ファイルの連結表示もできる。 ::: dialogue @linny: 最後は `cat` コマンドだよ。「concatenate(コンカテネイト、連結する)」の略だね。ファイルの中身を表示するコマンドだよ。 @lina: さっき `echo` で書き込んだ内容を見てみたいです。 ::: ### 基本的な使い方 {#cat-basic} ```bash $ cat memo.txt ``` ```output 1行目のメモ 2行目を追加 ``` ::: dialogue @lina: ちゃんと 2 行とも表示されてますね。 @linny: `cat` はファイル全体を一気に表示するよ。短いファイルの確認に便利だね。 ::: ### リナの失敗:`>` で 2 行が 1 行になった {#cat-failure} ::: dialogue @lina: メモに 3 行目を足そうとして `echo "3行目のメモ" > memo.txt` と打ちました。そのあと `cat` で見たら、1 行しかありません。 ::: ```bash $ echo "3行目のメモ" > memo.txt $ cat memo.txt ``` ```output 3行目のメモ ``` ::: dialogue @lina: えっ、前の 2 行はどこへ行ったんですか。追加したつもりだったのに。 @linny: `>` は「上書き」だからね。ファイルの中身をいったん空にしてから、新しい行を書いたんだ。 @lina: 消えたのは打ち間違いのせいじゃなかったんですね。記号 1 つで意味が変わるとは思いませんでした。 @linny: そう。追記したいときは `>>` を使う。「記号が 2 つなら追加」と覚えるといいよ。 @lina: 納得しました。書き込む前に `cat` で中身を見る癖もつけます。 ::: ```bash $ echo "1行目のメモ" > memo.txt $ echo "2行目を追加" >> memo.txt $ echo "3行目のメモ" >> memo.txt $ cat memo.txt ``` ```output 1行目のメモ 2行目を追加 3行目のメモ ``` ### 複数ファイルを連結して表示 {#cat-concat} ```bash $ echo "ファイル1の内容" > file1.txt $ echo "ファイル2の内容" > file2.txt $ cat file1.txt file2.txt ``` ```output ファイル1の内容 ファイル2の内容 ``` ::: tip **ここがポイント**:`cat` は複数のファイルを指定できます。指定した順につなげて表示します。これが「concatenate(連結する)」という名前の由来です。 ::: ### 行番号を付けて表示(-n オプション) {#cat-n} ```bash $ cat -n memo.txt ``` ```output 1 1行目のメモ 2 2行目を追加 3 3行目のメモ ``` ## ミニ課題で手を動かそう {#practice} > **結論**: mkdir・touch・echo・catを組み合わせてディレクトリ作成からファイル整理まで実践する。 ::: dialogue @linny: じゃあ、今日覚えた 4 つのコマンドを使って課題をやってみよう。 @linny: まず練習用のディレクトリを作って、その中だけで試そう。こうすれば、もとからあるファイルにはふれないよ。 ::: ```bash $ mkdir -p ~/file-practice && cd ~/file-practice ``` ### 課題1:練習用ディレクトリを作成して移動しよう {#challenge-1} **やること**:「my-project」というディレクトリを作ります。その中に移動して、今いる場所を表示しましょう。 :::details ヒント 1(方向づけ)を見る 3 つの動作に分けます。「作る」「移動する」「今いる場所を確かめる」です。1 つずつ別のコマンドを使います。 ::: :::details ヒント 2(コマンド名)を見る 作るのは `mkdir` です。移動するのは `cd` です。今いる場所を出すのは `pwd` です。この順に使います。 ::: :::details 答えを見る ```bash $ mkdir my-project $ cd my-project $ pwd ``` ```output /home/user/file-practice/my-project ``` `/home/user` の部分は、あなたのユーザー名によって変わります。自分のユーザー名が出ていれば成功です。 ::: ### 課題2:READMEファイルを作成して内容を書き込もう {#challenge-2} **やること**:「README.txt」という空ファイルを作ります。プロジェクト名と作成日の 2 行を書き込み、内容を表示しましょう。 :::details ヒント 1(方向づけ)を見る 4 つの動作に分けます。「空ファイルを作る」「1 行目を書く」「2 行目を足す」「中身を見る」です。 2 行目では、1 行目を消さない記号を選びます。 ::: :::details ヒント 2(コマンド名)を見る 作るのは `touch` です。書き込むのは `echo` です。1 行目は `>`、2 行目は `>>` を使います。表示は `cat` です。 ::: :::details 答えを見る ```bash $ touch README.txt $ echo "プロジェクト名: My First Project" > README.txt $ echo "作成日: 2026-02-02" >> README.txt $ cat README.txt ``` ```output プロジェクト名: My First Project 作成日: 2026-02-02 ``` 2 行目を `>` で書くと、1 行目が消えます。ここが `>` と `>>` の分かれ目です。 ::: ### 課題3:サブディレクトリを作成してファイルを整理しよう {#challenge-3} **やること**:「docs/notes」という 2 階層のディレクトリを一度に作ります。その中に「memo1.txt」と「memo2.txt」を作り、一覧を表示しましょう。 :::details ヒント 1(方向づけ)を見る `docs` と `notes` を 2 回に分けて作る必要はありません。途中の階層もまとめて作るオプションがあります。 ファイルも 2 回に分けなくて済みます。 ::: :::details ヒント 2(コマンド名)を見る ディレクトリは `mkdir -p` で一度に作れます。ファイルは `touch` に名前をスペースで区切って並べます。一覧は `ls` です。 ::: :::details 答えを見る ```bash $ mkdir -p docs/notes $ touch docs/notes/memo1.txt docs/notes/memo2.txt $ ls docs/notes ``` ```output memo1.txt memo2.txt ``` ::: ::: dialogue @lina: できました。ディレクトリを作って、ファイルを作って、中身を書いて、確認する。この流れがわかってきました。 @linny: 完璧だね。この流れは実務でも本当によく使うよ。手を動かして覚えておこう。 ::: ## よくあるミスと対処法 {#mistakes} > **結論**: スペルミスやリダイレクトの向き違いなど初心者がよく引っかかるミスと対処を把握する。 ### ミス1:ディレクトリ名のスペルミス {#mistake-1} ::: dialogue @lina: `mkdir documets` と打ってしまいました。「documents」にしたかったのに。 @linny: よくあるミスだね。作り直してもいいし、`mv` コマンドで名前を変えてもいい。 @linny: まずは `ls` で結果を確認する癖をつけよう。 ::: ### ミス2:リダイレクトの向きを間違える {#mistake-2} ::: dialogue @lina: `echo "追加" > memo.txt` で追記したかったのに、上書きしてしまいました。 @linny: これは本当によくあるミスだよ。追記は `>>`(2 つ)、上書きは `>`(1 つ)だね。 @linny: 迷ったら「2 つは追加」と覚えよう。心配なときは、先に `cat` で中身を見ておくといいよ。 ::: ### ミス3:存在しないディレクトリにファイルを作ろうとする {#mistake-3} ```bash $ touch newdir/file.txt ``` ```output touch: 'newdir/file.txt' にアクセスできません: そのようなファイルやディレクトリはありません ``` ::: dialogue @linny: `touch` はディレクトリを自動では作らないよ。先に `mkdir newdir` でディレクトリを作ろう。そのあとで `touch` を実行するんだ。 ::: ### ミス4:ファイルとディレクトリを混同する {#mistake-4} ::: dialogue @lina: `cat` でディレクトリの中身を見ようとしたらエラーが出ました。 @linny: `cat` はファイルの中身を見るコマンドだよ。ディレクトリの中身を見るのは `ls` だね。 @linny: 見分け方もあるよ。`ls -la` の行頭が `d` ならディレクトリ、`-` ならファイルだよ。 ::: ### ミス5:日本語ファイル名を使う {#mistake-5} ::: dialogue @lina: `mkdir メモ` のように、日本語でも作れるんですか? @linny: 作れるよ。ただし避けたほうがいいね。文字化けの原因になることがある。スクリプトでも扱いにくいんだ。 @linny: ファイル名は英数字とハイフン、アンダースコアを使おう。 ::: ## 振り返り {#review} ::: dialogue @lina: 整理します。`mkdir` でディレクトリ、`touch` でファイルを作るんですね。 @linny: そのとおり。`-p` を付ければ、途中の階層もまとめて作れるよ。 @lina: 書き込みは `echo` で、`>` は上書き、`>>` は追記でした。1 行しか残らなかったのは `>` を使ったからですね。 @linny: よく覚えていたね。書き込む前に `cat` で中身を見る。この 1 手間で失敗はほとんど防げるよ。 ::: ::: danger **片づけには `rm` を使います。ここだけ扱いが違います** `rm` で消したファイルは、GUI と違ってゴミ箱に行きません。その場で完全に消えます。元には戻せません。 - `-r`:ディレクトリを中身ごと消します - `-i`:1 つずつ「消してよいか」を聞きます `-i` を付けると、対象ごとに確認が表示されます。消してよければ `y` を入力して `Enter` を押します。やめるときは `n` を入力します。 消す前に、必ず `ls` でパスと中身を目で確かめてください。 ```bash $ cd ~ $ ls ~/file-practice $ rm -ri ~/file-practice ``` 本サイトの仮想ターミナルは学習用です。ここで実行しても、あなたのパソコンのファイルは消えません。安心して試してください。 ::: ## 今日の3行まとめ {#summary} > **結論**: mkdir・touch・echo・catの4コマンドの役割と使い分けを3行で整理して定着させる。 1. `mkdir` でディレクトリを作る。`-p` を付けると途中の階層もまとめて作れる 2. `touch` で空ファイルを作る。`echo "内容" > file` で中身を書き込む 3. `>` は上書き、`>>` は追記。書く前に `cat` で中身を確かめる ## 次に学ぶこと {#next} > **結論**: 次はcp・mv・rmでファイルのコピー移動削除を学びファイル操作の基本を完成させる。 ::: dialogue @lina: ファイルの作り方がわかりました。次は何を学べばいいですか? @linny: 次はファイルの「コピー」「移動」「削除」だね。`cp`、`mv`、`rm` の 3 つだよ。 @linny: この 3 つを覚えれば、ファイル操作の基本はそろう。[ファイル操作の基礎編](/articles/tutorials/file-operations-basics)で詳しく解説しているよ。 ::: # head・tail・パイプの使い方 - Linuxファイル操作の応用 Source: https://penguin-gym-linux.com/articles/tutorials/file-operations-advanced 基本的なファイル操作をマスターしたら、次は高度なテクニックを身につけよう。この応用編では、**head・tail・file・stat・パイプ・リダイレクト**を使った実践的なファイル分析と操作技術を解説する。 ## この記事でわかること {#what-you-will-learn} - head・tail でログの先頭・末尾・リアルタイム監視をする方法 - file・stat でファイル種別や詳細情報を調査する方法 - パイプ(|)とリダイレクト(>・>>・<)の使い分け - grep・sort・uniq・find を組み合わせた実践的なコマンド技 - サーバーメンテナンス・デプロイ・インシデント対応の業務シナリオ ## head・tail - ログ解析とファイル分析術 {#head-tail-analysis} > **結論**: headで先頭行、tailで末尾行、tail -fでリアルタイム監視ができ、ログ解析の基本操作となる。 `head`と`tail`は、**実務で最も頻繁に使用されるファイル分析コマンド**だ。特にサーバーのログ解析やデバッグ作業で欠かせない。 ### head - ファイル先頭のスマート表示 {#head-basic} **基本的な使用方法** ```bash $ head access.log ``` デフォルトで先頭10行を表示。ファイルの構造把握に最適。 **行数指定で柔軟な表示** ```bash $ head -n 5 error.log # 先頭5行 $ head -5 error.log # 短縮形 $ head -n 100 config.txt # 先頭100行 ``` **複数ファイルの一括確認** ```bash $ head -n 3 *.log ``` ``` ==> access.log <== 192.168.1.10 - - [11/Jan/2025:10:00:01] "GET /" 192.168.1.11 - - [11/Jan/2025:10:00:02] "GET /api" 192.168.1.12 - - [11/Jan/2025:10:00:03] "POST /login" ==> error.log <== [2025-01-11 10:00:01] ERROR: Database connection failed [2025-01-11 10:00:05] WARN: Slow query detected [2025-01-11 10:00:10] ERROR: Authentication failed ``` 複数ログファイルの内容を一度に確認できる。 ### tail - 最新情報とリアルタイム監視 {#tail-basic} **最新エラーの確認** ```bash $ tail error.log ``` デフォルトで末尾10行。最新のエラーやイベントを素早く特定できる。 **リアルタイムログ監視(最重要)** ```bash $ tail -f /var/log/app.log ``` **実務で最も使用頻度の高い機能**。ログをリアルタイムで監視し、新しい行が追加されると自動表示する。終了方法:Ctrl+C **特定位置からの表示** ```bash $ tail -n +50 large_file.txt # 50行目から最後まで $ tail -n 20 access.log # 末尾20行 ``` ### 実務での高度なログ解析テクニック {#head-tail-log-tips} **エラー発生時刻の特定と追跡** ```bash # エラー発生直前のコンテキストを確認 $ grep -n "ERROR" app.log | tail -1 # 最新エラーの行番号取得 47:ERROR: Connection timeout # その周辺を詳細確認 $ head -n 50 app.log | tail -n 10 # 41-50行目表示 ``` **ログローテーション対応の監視** ```bash # ログファイルがローテートしても監視継続 $ tail -F /var/log/app.log # 大文字Fでファイル再作成に対応 ``` **複数ログの同時監視** ```bash # 複数のログファイルを同時に監視 $ tail -f /var/log/app.log /var/log/error.log # さらに高度な方法:multitailコマンド $ multitail /var/log/app.log /var/log/error.log /var/log/access.log ``` ### プロのhead/tail使用テクニック {#head-tail-tips} **パフォーマンス問題の追跡** ```bash # アクセスログの最新トレンドを確認 $ tail -f access.log | grep "slow\|timeout\|error" ``` **デプロイ時のリアルタイムモニタリング** ```bash # デプロイ実行中に別ターミナルで $ tail -f /var/log/deploy.log | tee deploy_$(date +%Y%m%d).log ``` **ファイルサイズに応じた適切な選択** ```bash # 小さなファイルはcat、大きなファイルはhead/tail $ wc -l logfile.txt # 行数確認 $ [[ $(wc -l < file.txt) -gt 50 ]] && head file.txt || cat file.txt ``` ## file・stat - ファイル情報の詳細調査 {#file-info} > **結論**: fileコマンドでファイル種別、statで権限やタイムスタンプ等の詳細情報を確認でき、セキュリティ調査に役立つ。 ファイルの正体や詳細情報を調べるコマンドだ。**セキュリティチェックやデバッグ作業**で非常に有用。 ### file - ファイルタイプの特定 {#file-basic} **基本的なファイル診断** ```bash $ file mysterious_file mysterious_file: UTF-8 Unicode text, with CRLF line terminators ``` ファイルの種類、エンコーディング、改行コードを特定できる。 **複数ファイルの一括診断** ```bash $ file * config.txt: ASCII text data.bin: data image.jpg: JPEG image data, JFIF standard script.sh: Bourne-Again shell script, ASCII text executable archive.tar.gz: gzip compressed data ``` **セキュリティチェックでの活用** ```bash # 拡張子偽装の検出 $ file suspicious_file.txt suspicious_file.txt: PE32 executable (console) Intel 80386, for MS Windows # .txtだが実際はWindows実行ファイル! ``` :::warning 拡張子と実際のファイルタイプが異なる場合は、悪意あるファイルの可能性がある。 ::: ### stat - 詳細なファイル情報 {#stat-basic} **完全なファイル情報表示** ```bash $ stat important_file.txt File: important_file.txt Size: 1024 Blocks: 8 IO Block: 4096 regular file Device: 801h/2049d Inode: 1234567 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 1000/username) Gid: ( 1000/usergroup) Access: 2025-01-11 10:30:45.123456789 +0900 Modify: 2025-01-11 10:25:30.987654321 +0900 Change: 2025-01-11 10:25:30.987654321 +0900 Birth: 2025-01-11 10:20:15.555666777 +0900 ``` ファイルサイズ、権限、タイムスタンプの詳細情報を確認できる。 **カスタムフォーマットで表示** ```bash # サイズと更新日時のみ表示 $ stat --format="%n: %s bytes, modified %y" *.txt config.txt: 2048 bytes, modified 2025-01-11 09:15:22.123456789 +0900 log.txt: 51200 bytes, modified 2025-01-11 10:45:33.987654321 +0900 ``` ### 実務での活用シナリオ {#file-stat-use-cases} **ファイル変更の追跡調査** ```bash # セキュリティインシデント対応 $ stat --format="%n - Last modified: %y, Last accessed: %x" /etc/passwd /etc/passwd - Last modified: 2025-01-11 08:30:15.123456789 +0900, Last accessed: 2025-01-11 10:45:22.987654321 +0900 # 不正アクセスがあったかどうか確認 ``` **ディスクスペースの診断** ```bash # 大きなファイルの詳細情報 $ stat --format="%n: %s bytes (%S blocks)" large_files/* database.db: 1073741824 bytes (262144 blocks) backup.tar.gz: 536870912 bytes (131072 blocks) ``` **シンボリックリンクの診断** ```bash $ file suspicious_link suspicious_link: symbolic link to /tmp/malicious_file $ stat suspicious_link File: suspicious_link -> /tmp/malicious_file Size: 18 Blocks: 0 IO Block: 4096 symbolic link ``` リンク先の特定と安全性確認に使う。 ### file/stat コマンドのプロ活用術 {#file-stat-tips} **バッチ処理でのファイルタイプフィルタリング** ```bash # 実行ファイルだけを抽出 for f in *; do [[ $(file "$f") == *"executable"* ]] && echo "$f" done ``` **ファイルサイズでの一括ソート** ```bash # サイズ順でファイル一覧 stat --format="%s %n" * | sort -n | tail -10 ``` ## パイプとリダイレクト {#pipe} > **結論**: パイプ(|)でコマンドの出力を次のコマンドへ渡し、>(上書き)や>>(追記)でファイルに保存できる。 複数のコマンドを組み合わせて、強力な処理を実現する。 ### パイプ(|) {#pipe-basic} あるコマンドの出力を別のコマンドの入力に渡す。 ```bash $ ls -la | grep ".txt" ``` .txtファイルのみを表示できる。 ### リダイレクト {#redirect} | 記号 | 説明 | 例 | | ---- | ------------------------------------ | ------------------------- | | `>` | 出力をファイルに上書き | `ls > list.txt` | | `>>` | 出力をファイルに追記 | `echo "text" >> file.txt` | | `<` | ファイルから入力 | `sort < data.txt` | | `2>` | エラー出力をリダイレクト | `command 2> error.log` | | `&>` | 標準出力とエラー出力を同じファイルに | `command &> all.log` | ## 実践的な組み合わせ技 {#practical} > **結論**: grep・cut・sort・uniq・findをパイプでつなぐことでログ集計やファイル一括処理を効率化できる。 実際の作業でよく使用するコマンドの組み合わせ例を紹介する。 ### 例1:ログファイルからエラーを抽出して集計 {#practical-example1} ```bash $ grep "ERROR" app.log | cut -d' ' -f3 | sort | uniq -c | sort -rn ``` エラーの種類別に出現回数を降順で表示する。 ### 例2:大きなファイルTOP10を見つける {#practical-example2} ```bash $ find . -type f -exec ls -lh {} \; | sort -k5 -rh | head -10 ``` カレントディレクトリ以下で最も大きい10個のファイルを表示する。 ### 例3:特定の拡張子のファイル数を数える {#practical-example3} ```bash $ find . -name "*.txt" | wc -l ``` .txtファイルの総数を表示する。 ### 例4:プロセスのメモリ使用量TOP5 {#practical-example4} ```bash $ ps aux | sort -k4 -rn | head -5 ``` メモリ使用率が高い順に5つのプロセスを表示する。 ### 例5:ファイルのバックアップを作成 {#practical-example5} ```bash $ find . -name "*.conf" -exec cp {} {}.backup \; ``` すべての.confファイルのバックアップを作成する。 ## 実践演習:日常業務シナリオ {#practical-workflows} > **結論**: ログローテーション・デプロイ・インシデント対応・ディスク整理の4シナリオで実務コマンド組合せを体験できる。 実際の業務でよく発生するシナリオを通じて、ファイル操作コマンドの実践的な使い方を学ぶ。 ### シナリオ1:サーバーメンテナンス作業 {#scenario1} **タスク:ログファイルのローテーションとアーカイブ** ```bash # 1. 現在のログファイルサイズ確認 $ ls -lah /var/log/app.log # 2. 最新エラーの確認 $ tail -20 /var/log/app.log | grep "ERROR" # 3. 安全なアーカイブ作成 $ cp /var/log/app.log /var/log/archive/app.log.$(date +%Y%m%d) # 4. ログのクリア(サービス停止中に実行、root権限が必要) $ sudo sh -c '> /var/log/app.log' # ファイルを空にする ``` ### シナリオ2:アプリケーションデプロイ {#scenario2} **タスク:新バージョンの安全なデプロイ** ```bash # 1. 現在のバージョンをバックアップ $ cp -rp /opt/myapp /opt/myapp.backup.$(date +%Y%m%d_%H%M%S) # 2. 新バージョンの展開と確認 $ tar -tf new_version.tar.gz | head -10 # 中身確認 $ tar -xzf new_version.tar.gz -C /tmp/ # 一時展開 # 3. 設定ファイルのマージ $ cp /opt/myapp/config.ini /tmp/myapp/config.ini $ diff /opt/myapp/config.ini /tmp/myapp/config.ini # 差分確認 # 4. 本番環境への安全な上書き $ mv /opt/myapp /opt/myapp.old $ mv /tmp/myapp /opt/myapp ``` ### シナリオ3:セキュリティインシデント対応 {#scenario3} **タスク:不正アクセスの調査と証拠保全** ```bash # 1. アクセスログの緊急バックアップ $ cp /var/log/access.log /home/incident/access.log.$(date +%Y%m%d_%H%M%S) # 2. 不正アクセスの日時特定 $ grep "suspicious_pattern" /var/log/access.log | head -1 $ grep "suspicious_pattern" /var/log/access.log | tail -1 # 3. 該当時間帯のログ抽出 $ awk '/2025-01-11 10:30:/,/2025-01-11 11:00:/' /var/log/access.log > incident_logs.txt # 4. 関連ファイルの情報収集 $ stat /var/log/access.log > file_metadata.txt $ file /var/log/access.log >> file_metadata.txt ``` ### シナリオ4:ディスクスペースクリーンアップ {#scenario4} **タスク:安全な不要ファイルの削除** ```bash # 1. ディスク使用量の確認 $ df -h $ du -sh /var/log/* | sort -hr | head -10 # 2. 古いログファイルの特定 $ find /var/log -name "*.log" -mtime +30 -type f # 3. 安全な削除順序 $ find /var/log -name "*.log" -mtime +30 -type f -exec ls -la {} \; # 確認 $ find /var/log -name "*.log" -mtime +30 -type f -ok rm {} \; # 確認付き削除 # 4. 削除後の確認 $ df -h # スペースが増えたか確認 ``` ### 練習問題:安全環境で試してみよう {#exercises} **基礎編:ファイル操作の基本** 1. test.txt というファイルを作成し、「初期データ」と書き込み 2. test.txt を test_backup.txt としてコピー 3. test.txt を test_renamed.txt にリネーム 4. test_backup.txt の内容を表示 5. 不要なファイルを安全に削除 **中級編:実務シミュレーション** 1. 「project」ディレクトリを作成し、その中に `config.ini`, `app.py`, `README.md` を作成 2. project ディレクトリ全体をタイムスタンプ付きでバックアップ 3. config.ini の内容を変更し、元のファイルとの差分を確認 4. 問題があった場合のロールバック手順を実行 ## まとめ:安全なファイル操作マスターへ {#summary} この記事で学んだ重要ポイント: - **head/tail**:ログ解析とリアルタイム監視の必須テクニック - **file/stat**:セキュリティチェックとデバッグの強い味方 - **パイプ・リダイレクト**:複数コマンドの強力な組み合わせ - **実践演習**:実務シナリオでの総合活用 :::warning 実務では「安全第一」で操作し、常にバックアップと確認を欠かさずに。一度のミスが取り返しのつかない結果をもたらす可能性を常に意識すること。 ::: ## 次に読む {#next} - [ファイル操作(基礎編)](/articles/tutorials/file-operations-basics) — cp・mv・rmの基本操作と安全対策 - [権限管理(基礎編)](/articles/tutorials/permissions-basics) — chmod・chownの基本操作 - [find・grep・awkマスターガイド](/articles/tutorials/find-grep-awk-mastery) — 検索・抽出・処理の3部作シリーズ - [シェルスクリプト(基礎編)](/articles/tutorials/shell-scripting-basics) — 自動化の第一歩 # cp・mv・rmの使い方 - Linuxファイル操作の基本 Source: https://penguin-gym-linux.com/articles/tutorials/file-operations-basics ## この記事でわかること {#what-you-will-learn} - `cp`(コピー)・`mv`(移動・リネーム)・`rm`(削除)の基本的な使い方がわかる - `-i` オプションで上書き・削除事故を防ぐ習慣が身につく - `rm` にはゴミ箱がないため、安全に操作するポイントを理解できる ::: warning **先に知っておくこと**:この記事の `rm` は、GUI の「ゴミ箱に入れる」とは違います。`rm` で消したファイルはゴミ箱に行きません。その場ですぐ完全に消えます。だからこの記事では、練習用のディレクトリを作ってから試します。本サイトの仮想ターミナルは学習用なので、あなたのパソコンは壊れません。安心して試してください。 ::: ::: tip **言葉の整理**:`>` は「リダイレクト」といって、画面に出るはずの文字をファイルに書き出すための記号です。`echo "文字" > ファイル名` と書くと、その文字が中身になったファイルができます。 **失敗するとどうなるか**:`>` は書き出す前に、そのファイルをからっぽにします。もとから中身があるファイルに `>` を使うと、その中身は消えてしまいます。ゴミ箱にも残りません。 **安全に試す方法**:この記事では `~/practice/file-ops` の中だけで、新しい名前のファイルに書き出します。もとからあるファイルには `>` を向けません。 ::: ## はじめに {#intro} > **結論**: ファイルのコピーで上書き事故が起きた場面から安全なファイル操作の考え方を一緒に学ぶ。 :::dialogue @lina: ライニー先輩、ファイルをコピーしようとしたら、なんか上書きしちゃったみたいで... @linny: あらら、それは焦るよね。Linuxのファイル操作は便利だけど、ちょっとしたミスで大事なファイルを消しちゃうこともあるんだ。 @lina: え、怖い...どうすれば安全に操作できるんですか? @linny: 大丈夫、ポイントを押さえれば安全に使えるよ。今日は`cp`・`mv`・`rm`の3つのコマンドを、事故を防ぐ方法と一緒に覚えていこう。 ::: ## 練習環境を用意しよう {#setup} > **結論**: mkdir -pで練習用ディレクトリを作り大事なファイルを誤操作しない専用環境を用意する。 :::dialogue @linny: まずは練習用のディレクトリを作ろう。大事なファイルをまちがって消さないように、専用の場所で練習するのが基本だよ。 ::: ```bash $ mkdir -p ~/practice/file-ops $ cd ~/practice/file-ops $ pwd ``` ```output /home/user/practice/file-ops ``` :::dialogue @lina: `mkdir -p`の`-p`って何ですか? @linny: 途中のディレクトリがなくても、まとめて作ってくれるオプションだよ。`practice`ディレクトリがなくても、一気に`practice/file-ops`を作れるんだ。 ::: ## cp:ファイルをコピーする {#cp-command} > **結論**: cpはコピー元・コピー先の順で-iで上書き確認し-rでディレクトリごとコピーする。 :::dialogue @linny: `cp`は「copy」の略で、ファイルをコピーするコマンドだよ。 ::: ### 基本の使い方 {#cp-basic} ```bash $ echo "Hello Linux" > original.txt $ cp original.txt copy.txt $ ls ``` ```output copy.txt original.txt ``` :::dialogue @lina: お、`copy.txt`ができました! @linny: `cp コピー元 コピー先`の順番だね。元のファイルはそのまま残るよ。 ::: ### 上書き事故を防ぐ:-i オプション {#cp-overwrite} :::dialogue @linny: ここからが大事。`cp`は同じ名前のファイルがあると、確認なしで上書きしちゃうんだ。 @lina: え、それで私のファイルが...! @linny: そう。だから`-i`オプションをつける習慣をつけよう。「interactive(対話的)」の略で、上書きの前に確認してくれるよ。 ::: ```bash $ cp -i original.txt copy.txt cp: overwrite 'copy.txt'? ``` :::dialogue @lina: おお、聞いてくれた!`y`で上書き、`n`でキャンセルですね。 @linny: その通り。**常に`cp -i`を使う**のが安全だよ。 ::: ### ディレクトリをコピーする:-r オプション {#cp-directory} :::dialogue @linny: ディレクトリをコピーするときは`-r`オプションが必要だよ。「recursive(再帰的)」の略で、中身ごとコピーしてくれる。 @linny: あと、次の例で使う`touch`は中身が空のファイルを作るコマンド。`echo "文字"`は文字を表示するコマンドだよ。 ::: ```bash $ mkdir mydir $ touch mydir/file1.txt mydir/file2.txt $ cp -r mydir mydir-backup $ ls mydir-backup file1.txt file2.txt ``` :::tip **cpコマンドのポイント** - `cp コピー元 コピー先` - 基本形 - `cp -i` - 上書き確認(安全のため常に使う) - `cp -r` - ディレクトリをコピー ::: ## mv:ファイルを移動・リネームする {#mv-command} > **結論**: mvは移動もリネームも同じコマンドで-iで上書き確認し元の場所からなくなる点に注意。 :::dialogue @linny: `mv`は「move」の略。ファイルの移動と名前変更、両方に使えるよ。 ::: ### ファイル名を変更する {#mv-rename} ```bash $ mv copy.txt renamed.txt $ ls mydir mydir-backup original.txt renamed.txt ``` :::dialogue @lina: `copy.txt`が`renamed.txt`になりました! @linny: `mv`は移動先がファイル名なら「リネーム」、ディレクトリなら「移動」になるんだ。 ::: ### ファイルを移動する {#mv-move} ```bash $ mv renamed.txt mydir/ $ ls mydir file1.txt file2.txt renamed.txt ``` :::dialogue @lina: `renamed.txt`が`mydir`の中に移動しました。 ::: ### 上書き事故を防ぐ:-i オプション {#mv-overwrite} :::dialogue @linny: `mv`も`cp`と同じく、同名ファイルがあると上書きしちゃう。だから`-i`オプションをつけよう。 ::: ```bash $ mv -i original.txt mydir/ ``` :::warning **mvの注意点** `mv`は「移動」なので、元の場所からファイルがなくなります。「消えた」と焦る前に、移動先を確認しましょう。 ::: :::tip **mvコマンドのポイント** - `mv 元 先` - 移動またはリネーム - `mv -i` - 上書き確認(安全のため常に使う) - 移動先がディレクトリなら移動、ファイル名ならリネーム ::: ## rm:ファイルを削除する {#rm-command} > **結論**: rmにはゴミ箱がないため必ず-iオプションで削除前に必ず確認するクセをつけておく。 :::dialogue @linny: `rm`は「remove」の略で、ファイルを削除するコマンドだよ。**これが一番注意が必要**。 @lina: どうしてですか? @linny: Linuxには「ゴミ箱」がないんだ。GUIならゴミ箱から戻せるけど、`rm`で消したファイルは基本的に復元できない。 @lina: ひえっ...慎重にやります。 ::: ### 基本の使い方 {#rm-basic} :::warning 次の例は、`-i`を付けないときの動きを見るためのものです。たしかめずに消えます。じぶんで打つときは、次の節で紹介する`-i`付きを使ってください。 ::: ```bash $ touch delete-me.txt $ ls delete-me.txt mydir mydir-backup ``` ```bash $ rm delete-me.txt $ ls mydir mydir-backup ``` ### 削除前に確認:-i オプション {#rm-confirm} :::dialogue @linny: `rm`こそ`-i`オプションが大事。削除の前に確認してくれるよ。 ::: ```bash $ touch test.txt $ rm -i test.txt rm: remove regular empty file 'test.txt'? ``` :::dialogue @lina: `y`で削除、`n`でキャンセルですね。 @linny: うん。**初心者のうちは常に`rm -i`を使おう**。 ::: ### ディレクトリを削除する:-r オプション {#rm-directory} ```bash $ rm -r mydir-backup $ ls mydir ``` :::dialogue @lina: ディレクトリも消えました。 @linny: `-r`は中身ごと削除するから、使う前に`ls`で中身を確認するのがおすすめだよ。 ::: :::danger **危険:rm -rf は使わない** `rm -rf`は確認なしで強制削除するコマンドです。初心者は絶対に使わないでください。間違ったディレクトリを指定すると、大事なファイルがすべて消えてしまいます。 ::: :::tip **rmコマンドのポイント** - `rm ファイル名` - ファイルを削除 - `rm -i` - 削除前に確認(常に使う) - `rm -r` - ディレクトリを中身ごと削除 - **`rm -rf`は使わない** ::: ## ミニ課題 {#exercises} > **結論**: cp -i・mv -i・rm -iを組み合わせた実践課題でファイル操作の安全な型を体で覚える。 :::dialogue @linny: じゃあ、今日学んだことを実際に試してみよう。答えを開く前に、まず自分で考えてみてね。 ::: ### 課題1:ファイルを安全にコピーしよう {#exercise1} **やること**:`memo.txt`というファイルを作成し、`memo-backup.txt`という名前でコピーしてください。上書き確認オプションをつけること。 :::details ヒント1(方向づけ)を見る まず中身のあるファイルを1つ作ります。次にコピーしますが、そのとき「上書きする前に聞いてくれる」オプションを付けます。 ::: :::details ヒント2(コマンド名)を見る ファイル作成は `echo` と、書き出し先を指定する記号 `>` を使います。コピーは `cp` です。確認を出すオプションは `-i` です。 ::: :::details 答えを見る ```bash $ echo "大事なメモ" > memo.txt $ cp -i memo.txt memo-backup.txt $ ls memo-backup.txt memo.txt mydir ``` ::: ### 課題2:ファイルを移動してリネームしよう {#exercise2} **やること**:`memo.txt`を`mydir`ディレクトリに移動し、さらに`important.txt`という名前に変更してください。 :::details ヒント1(方向づけ)を見る 移動と名前変更は、同じ1つのコマンドでできます。2回に分けても、1回でまとめてもかまいません。 ::: :::details ヒント2(コマンド名)を見る 使うのは `mv` です。移動先に新しいファイル名まで書くと、移動とリネームを同時にできます。 ::: :::details 答えを見る ```bash $ mv -i memo.txt mydir/ $ mv -i mydir/memo.txt mydir/important.txt $ ls mydir file1.txt file2.txt important.txt original.txt renamed.txt ``` または1回で: ```bash $ mv -i memo.txt mydir/important.txt ``` ::: ### 課題3:不要なファイルを安全に削除しよう {#exercise3} **やること**:`memo-backup.txt`を削除してください。削除前に確認が出るようにすること。 :::details ヒント1(方向づけ)を見る 削除のコマンドに、「消す前に聞いてくれる」オプションを付けます。コピーや移動で使ったものと同じ文字です。 ::: :::details ヒント2(コマンド名)を見る 使うのは `rm` です。オプションは `-i` を付けます。 ::: :::details 答えを見る ```bash $ rm -i memo-backup.txt rm: remove regular file 'memo-backup.txt'? y ``` ```bash $ ls mydir ``` ::: ## 振り返り {#review} > **結論**: -iオプションをcp・mv・rmすべてにつける習慣をつけると大半の事故を防げると確認する。 :::dialogue @lina: なるほど、`-i`オプションをつければ、上書きや削除の前に確認が出るんですね! @linny: そう、それが一番大事なポイント。`cp -i`、`mv -i`、`rm -i`を習慣にすれば、ほとんどの事故は防げるよ。 @lina: あと、`rm`はゴミ箱がないから、とくに注意ですね。 @linny: うん。操作の前に`ls`で確認する習慣もつけると、さらに安全だね。 ::: ## 今日の3行まとめ {#summary} > **結論**: cp・mv・rmそれぞれの役割と-iオプションの重要性を3行で整理して定着させる。 1. `cp -i`でファイルをコピー、`-r`でディレクトリをコピー 2. `mv -i`でファイルを移動・リネーム 3. `rm -i`で削除(`rm -rf`は使わない) ## 次に読む {#next} > **結論**: 次は権限管理やファイル操作応用でchmod・head・tailなどのスキルを積み重ねる。 - [権限管理の基礎](/articles/tutorials/permissions-basics) — chmod・chownの基本操作 - [ファイル操作(応用編)](/articles/tutorials/file-operations-advanced) — head・tail・パイプ・リダイレクト - [基本コマンド10選](/articles/tutorials/basic-commands) — pwd・ls・cd・mkdir等の必須コマンド # find -exec と xargs の使い分け Source: https://penguin-gym-linux.com/articles/tutorials/find-exec-vs-xargs ## 結論:使い分けの基準 {#conclusion} **基本方針:シンプルな処理は `-exec {} +`、パイプライン組み込みや並列処理が必要なら `xargs`。** | 状況 | 推奨 | | ---------------------------------- | -------------------------------- | | シンプルな1コマンド実行 | `find -exec {} +` | | スペースを含むファイル名 | `find -print0 \| xargs -0` | | 並列実行が必要 | `xargs -P` | | パイプラインに組み込む | `xargs` | | シェル機能(変数・条件分岐)が必要 | `find -exec sh -c '...' _ {} \;` | ::: tip **スペース対策の鉄則** ファイル名にスペースが含まれる可能性がある環境では、常に `-print0 | xargs -0` の組み合わせを使う。 ::: ## find -exec の仕組み {#find-exec} `find -exec` は find の組み込み機能で、検索結果に対して直接コマンドを実行する。外部ツールへのパイプが不要なため、シンプルな処理に向いている。 ### セミコロン形式(`\;`):1件ずつ実行 ```bash find /var/log -name "*.log" -exec ls -lh {} \; ``` `{}` が見つかったファイルパスに置換される。`\;` はシェルのセミコロン解釈を防ぐエスケープ。**1ファイルごとにコマンドが起動する**ため、ファイル数が多いと低速になる。 ### プラス形式(`+`):まとめて実行 ```bash find /var/log -name "*.log" -exec ls -lh {} + ``` `+` を末尾に使うと find が引数をバッファリングし、**可能な限りまとめてコマンドを1回呼び出す**。処理速度が `\;` より大幅に向上する。 ::: tip `-exec {} +` は POSIX 準拠で外部ツール不要。ファイル名にスペースが含まれていても正しく処理できる。 ::: ## xargs の仕組み {#xargs} `xargs` は標準入力のデータをコマンドの引数に変換して実行する。パイプラインとの連携が得意。 ```bash find /var/log -name "*.log" | xargs ls -lh ``` find の出力を改行区切りで読み込み、`ls -lh file1 file2 file3 ...` のようにまとめて実行する。 主なオプション: - `-0`:NUL文字区切りで読み込む(`-print0` と組み合わせる) - `-I {}`:プレースホルダで引数位置を指定する - `-P N`:N プロセスで並列実行する - `-r`:入力が空のときコマンドを実行しない ## スペース・特殊文字を含むファイル名への対処 {#null-separator} **ファイル名にスペースや改行が含まれると、デフォルトの xargs は誤動作する。** ```bash # 危険:スペース入りファイル名で誤分割が起きる find . -name "*.txt" | xargs rm # 安全:NUL文字区切りを使う find . -name "*.txt" -print0 | xargs -0 rm ``` `-print0` は find の出力区切りをNUL文字(`\0`)にする。`xargs -0` はNUL文字で区切って読む。この組み合わせで**あらゆる特殊文字を含むファイル名を安全に処理**できる。 `find -exec {} +` を使う場合はパイプを通さないため、スペースの問題は最初から発生しない。 ::: warning 本番環境ではファイル名にスペースが含まれる可能性を常に考慮すること。 ::: ## 並列実行:xargs -P {#parallel} 大量ファイルの処理速度が問題になる場合、`xargs -P` でプロセスを並列化できる。 ```bash # 4並列で画像変換 find . -name "*.png" -print0 | xargs -0 -P 4 -I {} convert {} {}.jpg ``` `-P 4` で最大4プロセスを同時起動する。CPU コア数に近い値が目安。 `find -exec` にはネイティブの並列実行機能がない。並列処理が必要なら `xargs -P` を選ぶ。 ## シェル機能が必要なとき {#shell-features} find -exec も xargs も直接シェル構文(変数・パイプ・リダイレクト)は実行できない。その場合は `sh -c` を経由する。 ```bash # find -exec でシェル機能を使う(推奨形) find . -name "*.log" -exec sh -c 'wc -l "$1" >> /tmp/result.txt' _ {} \; # xargs でシェル機能を使う find . -name "*.log" -print0 | xargs -0 -I {} sh -c 'wc -l "{}" >> /tmp/result.txt' ``` ::: warning `sh -c` 内で `{}` を直接文字列展開するとシェルインジェクションのリスクがある。`find -exec sh -c '...' _ {} \;` の形式で `$1` を使う方が安全。 ::: ## よく使うパターン集 {#patterns} ### 古いログファイルの削除 ```bash # 30日以上前の .log を削除 find /var/log -name "*.log" -mtime +30 -exec rm -v {} + ``` ### ファイル・ディレクトリの一括パーミッション変更 ```bash # ディレクトリのみ 755 find . -type d -exec chmod 755 {} + # ファイルのみ 644 find . -type f -exec chmod 644 {} + ``` ### テキスト一括置換 ```bash # grep で対象を絞り、sed で置換 find . -name "*.conf" -print0 | xargs -0 grep -l "old_value" | xargs sed -i 's/old_value/new_value/g' ``` ### 空ディレクトリの削除 ```bash find . -type d -empty -exec rmdir {} + ``` ## まとめ {#summary} | 比較軸 | find -exec {} + | xargs | | -------------------- | ---------------- | ---------------------------- | | 外部ツール依存 | なし | xargs が必要 | | スペース対応 | デフォルトで安全 | `-print0 \| xargs -0` が必要 | | 並列実行 | 不可 | `-P` で可能 | | パイプライン組み込み | 難しい | 容易 | | 構文の簡潔さ | シンプル | パイプが必要 | スペースを含まないシンプルな処理なら `-exec {} +` が最もシンプル。パイプラインへの組み込み・並列処理・`xargs -0` が必要なケースでは `xargs` を選ぶ。 ## 次に読む {#next} - [xargs 実践活用 - 標準入力をコマンド引数に変換する](/articles/tutorials/xargs-practical) - [find・grep・awk マスターガイド](/articles/tutorials/find-grep-awk-mastery) - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) # grep・awkの応用テクニック - find/grep/awkマスターシリーズ応用編 Source: https://penguin-gym-linux.com/articles/tutorials/find-grep-awk-advanced 応用編では、grep の環境変数最適化・次世代高速ツール活用と、awk の連想配列・ユーザー定義関数・ストリーム処理を学ぶ。プロフェッショナルレベルのデータ処理技術を習得する。 ## この記事でわかること {#what-you-will-learn} - grep の高度なオプションと環境変数による検索の最適化 - ripgrep など次世代の高速検索ツールの活用 - awk の連想配列・ユーザー定義関数・ストリーム処理 - プロレベルのテキスト処理・データ加工テクニックの習得 ## grepコマンド:テキスト検索の魔法使い {#grep-command} > **結論**: grep はパターンにマッチする行を抽出する。正規表現と多彩なオプション、高速ツールの併用で実務の検索が一段と強力になる。 grep は **「Global Regular Expression Print」** の略で、ファイルや入力から特定のパターンにマッチする行を抽出するコマンド。正規表現と組み合わせることで非常に強力な検索ツールになる。 ### 基本構文 ```bash grep [オプション] パターン ファイル名 ``` ### 基本的な使い方 **文字列検索**: ```bash # "Linux"という文字列を含む行を表示 grep "Linux" document.txt # 大文字小文字を区別せずに検索 grep -i "linux" document.txt # "error"を含まない行を表示(逆検索) grep -v "error" log.txt ``` **行番号と文脈表示**: ```bash # マッチした行の行番号も表示 grep -n "function" script.js # マッチした行の前後3行も表示 grep -C 3 "ERROR" app.log # マッチした行の前1行、後2行を表示 grep -A 2 -B 1 "WARNING" app.log ``` **ファイル検索とカウント**: ```bash # "TODO"を含むファイル名のみを表示 grep -l "TODO" *.js # "error"を含む行数をカウント grep -c "error" log.txt # ディレクトリを再帰的に検索(注意: /etc/ には機密情報が含まれる場合があります) grep -r "password" /etc/ ``` ### 正規表現との組み合わせ grep の真の力は **正規表現** との組み合わせで発揮される。 ```bash # 行の始まりが"Linux"の行を検索 grep "^Linux" document.txt # 行の終わりが"finished"の行を検索 grep "finished$" log.txt # 空行を検索 grep "^$" file.txt # 1つ以上の数字を含む行を検索 grep -E "[0-9]+" numbers.txt # "color"または"colour"を検索 grep -E "colou?r" text.txt # 複数のパターンのいずれかにマッチ(OR検索) grep -E "error|warning|fatal" log.txt ``` **実用パターン例**: ```bash # IPアドレスパターンを検索 grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" access.log # メールアドレスパターンを検索 grep -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" contacts.txt # 日付パターン(YYYY-MM-DD)を検索 grep -E "20[0-9]{2}-[0-1][0-9]-[0-3][0-9]" log.txt ``` ### grepとパイプの組み合わせ 他のコマンドとパイプで繋ぐことで、強力なデータ処理パイプラインを構築できる。 ```bash # nginxプロセスのみを表示 ps aux | grep "nginx" # grep自身を除外してpythonプロセスを表示 ps aux | grep -v "grep" | grep "python" # リアルタイムでエラーを監視 tail -f /var/log/app.log | grep --line-buffered "ERROR" # 404エラーの発生回数をカウント cat access.log | grep "404" | wc -l # ポート80で待ち受けているプロセスを表示 netstat -an | grep ":80 " ``` ### 高速化テクニック **環境変数とロケール最適化** で大容量ファイル処理が高速化する。 ```bash # UTF-8処理オーバーヘッドを排除(最大10倍高速化) LC_ALL=C grep "ERROR" huge_log.txt # バイナリファイルをスキップ LC_ALL=C grep --binary-files=without-match "pattern" /var/log/* # 色付けを無効化してさらに高速化 GREP_OPTIONS="--color=never" LC_ALL=C grep -F "ERROR" *.log # 固定文字列検索(正規表現処理を無効化) grep -F "literal_string" file.txt ``` ### 次世代grep:ripgrepとagの活用 従来の grep より高速で多機能な代替ツール。 **ripgrep(rg)- Rust製高速grep**: ```bash # JavaScriptファイルのみを対象に高速検索 rg --type js "function" /var/www/ # JSON出力で構造化データとして処理 rg --json "ERROR" /var/log/ | jq '.data.lines.text' # 検索統計と件数を同時表示 rg --stats --count "TODO" ./src/ ``` **ag(The Silver Searcher)**: ```bash # マルチコア並列処理で大容量検索 ag --parallel "pattern" /large/directory/ # 前後5行表示でグループ化 ag --context=5 --group "ERROR" /var/log/ ``` **パフォーマンス比較(1GBファイル検索)**: | ツール | 実行時間 | メモリ使用量 | 特徴 | | ------------- | -------- | ------------ | ---------------- | | grep | 15.2秒 | 2MB | 標準、安定 | | LC_ALL=C grep | 8.1秒 | 2MB | 高速化済み | | ripgrep (rg) | 2.3秒 | 8MB | 最高速、多機能 | | ag | 4.1秒 | 12MB | 高速、開発者向け | ### 大容量ファイル処理 ```bash # リアルタイムでログを監視しながら検索 tail -f /var/log/huge.log | grep --line-buffered "ERROR" # gzip圧縮ファイルを解凍せずに検索 zgrep "ERROR" /var/log/app.log.gz # bzip2ファイルも直接検索可能 bzgrep "pattern" archive.log.bz2 # 大容量ファイルを分割して並列処理 split -l 1000000 huge.log chunk_ && grep "ERROR" chunk_* | sort ``` ### レポート生成 ```bash # エラーレポートをCSV形式で生成 grep -n "ERROR" *.log | awk -F: '{print $1","$2","$3}' > error_report.csv # 包括的なエラー分析レポート生成 { echo "=== ERROR分析レポート $(date) ===" echo "総エラー数: $(grep -c ERROR app.log)" echo "Top 5 エラー:" grep -o 'ERROR.*' app.log | sort | uniq -c | sort -nr | head -5 } ``` ## awkコマンド:データ処理の魔術師 {#awk-command} > **結論**: awk は列単位でデータを処理する言語。連想配列・ユーザー定義関数・ストリーム処理で集計や変換を柔軟にこなせる。 awk は **「Alfred Aho, Peter Weinberger, Brian Kernighan」** の頭文字から名付けられた、強力なテキスト処理言語。CSVファイルやログファイルの処理で真価を発揮する。 ### awkの考え方 awk は入力を **レコード**(通常は行)と **フィールド**(通常は列)に分けて処理する。 ```text 名前,年齢,職業 田中,25,エンジニア 佐藤,30,デザイナー 山田,28,マネージャー ``` - `$1`: 1番目のフィールド(名前) - `$2`: 2番目のフィールド(年齢) - `$3`: 3番目のフィールド(職業) - `$0`: レコード全体 - `NF`: フィールド数 - `NR`: レコード番号 ### 基本構文 ```bash awk 'パターン { アクション }' ファイル ``` パターンにマッチする行に対してアクションを実行する。 ### 基本的なawk操作 **列の抽出**: ```bash # 1列目(名前)のみを表示 awk '{print $1}' employees.csv # 2列目と3列目を表示 awk '{print $2, $3}' employees.csv # 行番号付きで全体を表示 awk '{print NR ": " $0}' file.txt ``` **区切り文字の指定**: ```bash # カンマ区切りファイルの1列目を表示 awk -F ',' '{print $1}' data.csv # コロン区切りのユーザー名とUIDを表示 awk -F ':' '{print $1, $3}' /etc/passwd # タブ区切りファイルの2列目を表示 awk 'BEGIN {FS="\t"} {print $2}' tab_separated.txt ``` **条件付き処理**: ```bash # 年齢が25歳より上の人の名前と年齢を表示 awk '$2 > 25 {print $1, $2}' employees.csv # 職業がエンジニアの人の名前を表示 awk '$3 == "エンジニア" {print $1}' employees.csv # フィールド数が3より多い行を行番号付きで表示 awk 'NF > 3 {print NR, $0}' data.txt ``` ### 計算・集計処理 **基本的な計算**: ```bash # 3列目の合計を計算 awk '{sum += $3} END {print "合計:", sum}' sales.csv # 2列目の平均値を計算 awk '{sum += $2; count++} END {print "平均:", sum/count}' ages.txt # 2列目の最大値を求める awk 'BEGIN {max=0} {if($2>max) max=$2} END {print "最大値:", max}' numbers.txt ``` **グループ別集計**: ```bash # 部署別の給与合計を計算 awk '{dept[$3] += $2} END {for (d in dept) print d, dept[d]}' salary.csv # IPアドレス別のアクセス回数をカウント awk '{count[$1]++} END {for (c in count) print c, count[c]}' access.log ``` **複雑な処理例**: ```bash # 地域別の売上統計(合計、件数、平均) awk -F, 'NR>1 {sales[$2]+=$4; count[$2]++} END {for(region in sales) printf "%s: 売上%d 件数%d 平均%.1f\n", region, sales[region], count[region], sales[region]/count[region]}' sales_data.csv ``` ### BEGIN・ENDパターン - `BEGIN`: ファイル処理の **開始前** に実行 - `END`: ファイル処理の **終了後** に実行 ```bash # ヘッダーを出力してからデータを処理 awk 'BEGIN {print "処理開始", "氏名", "年齢"} {print NR, $1, $2}' data.txt # 処理後に総レコード数を表示 awk '{count++} END {print "総レコード数:", count}' data.txt # レポート形式で売上集計 awk 'BEGIN {print "=== 売上レポート ==="} {total+=$3} END {print "総売上:", total, "円"}' sales.txt ``` ### 高度な活用法 ```bash # 複数ファイルをファイル名付きで処理 awk 'FNR==1{print "=== " FILENAME " ==="} {print NR, $0}' file1.txt file2.txt # 条件に応じて判定結果を追加 awk '{if($2>=60) grade="合格"; else grade="不合格"; print $1, $2, grade}' scores.txt ``` ### 連想配列の完全活用 awk の真の力は **連想配列(ハッシュテーブル)** にある。多次元データ処理で威力を発揮する。 **多次元集計(地域×月別売上)**: ```bash awk -F, ' NR>1 { sales[$2][$3] += $4; total_by_region[$2] += $4; total_by_month[$3] += $4; grand_total += $4; } END { printf "%-12s", "地域/月"; for (month in total_by_month) printf "%10s", month; printf "%12s\n", "地域計"; for (region in total_by_region) { printf "%-12s", region; for (month in total_by_month) { printf "%10d", (month in sales[region]) ? sales[region][month] : 0; } printf "%12d\n", total_by_region[region]; } }' sales_data.csv ``` ### ユーザー定義関数 複雑な処理を関数化して再利用し、保守性の高いコードを作成する。 **統計計算ライブラリ**: ```bash awk ' function average(arr, count, sum, i) { sum = 0; for (i = 1; i <= count; i++) sum += arr[i]; return sum / count; } function stddev(arr, count, avg, sum_sq, i) { avg = average(arr, count); sum_sq = 0; for (i = 1; i <= count; i++) { sum_sq += (arr[i] - avg) ^ 2; } return sqrt(sum_sq / count); } { if (NF >= 2 && $2 ~ /^[0-9]+\.?[0-9]*$/) { values[++count] = $2; sum += $2; } } END { if (count > 0) { printf "n=%d\n", count; printf "平均値: %.2f\n", average(values, count); printf "標準偏差: %.2f\n", stddev(values, count); } }' numerical_data.txt ``` ### ストリーム処理とgetline リアルタイムデータ処理や外部コマンド連携で真価を発揮する。 **リアルタイムログ監視**: ```bash tail -f /var/log/apache2/access.log | awk ' BEGIN { window_size = 300; alert_threshold = 100; } { "date +%s" | getline current_time; close("date +%s"); access_times[current_time]++; for (time in access_times) { if (current_time - time > window_size) { delete access_times[time]; } } total_access = 0; for (time in access_times) { total_access += access_times[time]; } if (total_access > alert_threshold) { printf "[ALERT] High traffic: %d requests in last 5 minutes\n", total_access; } }' ``` ### パフォーマンス最適化 :::tip **awkの高速化テクニック** - 不要な文字列結合を避ける(配列を使う) - 大きなデータは定期的に `delete` でクリア - 必要なフィールドのみを処理 - `BEGIN` ブロックで定数を初期化 ::: **メモリ効率的な大容量ファイル処理**: ```bash awk ' BEGIN { processed = 0; batch_size = 10000; } { process_record($0); processed++; if (processed % batch_size == 0) { cleanup_memory(); printf "処理中: %d レコード完了\n", processed > "/dev/stderr"; } } function process_record(record, fields) { split(record, fields, ","); if (fields[2] > threshold) { summary[fields[1]] += fields[3]; } } function cleanup_memory( key) { for (key in old_cache) delete old_cache[key]; } END { for (key in summary) printf "%s: %d\n", key, summary[key]; }' huge_data_file.csv ``` ### 高度な出力フォーマッティング **アスキーアート・チャート生成**: ```bash awk -F, ' NR > 1 { sales[$1] += $3; } END { max_sales = 0; for (person in sales) { if (sales[person] > max_sales) max_sales = sales[person]; } chart_width = 50; scale = max_sales / chart_width; print "営業成績チャート"; print "================"; for (person in sales) { bar_length = int(sales[person] / scale); printf "%-10s |", person; for (j = 1; j <= bar_length; j++) printf "█"; printf " %d万円\n", sales[person] / 10000; } }' sales_report.csv ``` ## 次のステップ {#next-steps} 応用編で学んだ grep と awk の究極テクニックを、実践編でさらに深める。 - [find・grep・awk実践編](/articles/tutorials/find-grep-awk-practical) — 組み合わせと業務での実践 - [find・grep・awkプロ編](/articles/tutorials/find-grep-awk-professional) — 演習問題とトラブルシューティング - [find・grep・awkマスターガイド](/articles/tutorials/find-grep-awk-mastery) — シリーズ全体の目次 # find・grep・awkの使い方入門 - 正規表現の基礎から Source: https://penguin-gym-linux.com/articles/tutorials/find-grep-awk-basics 基本的な Linux コマンドに慣れてきたら、次に覚えるのは **find・grep・awk** の 3 つです。この 3 つを押さえると、ファイル探し・ログ調査・集計を 1 行で片づけられるようになります。 この基礎編で扱うのは 3 点です。3 コマンドの役割と使い分け、3 コマンドすべてで効く **正規表現の基礎**、そして **find の検索機能** です。 ## この記事で身につくこと {#what-you-will-learn} - find・grep・awk のどれを使うかを、目的から選べるようになります。 - 正規表現のアンカー・量指定子・文字クラスを、読んで書けるようになります。 - find の検索条件とアクションを組み合わせて、一括処理を安全に実行できるようになります。 **想定読者**:`ls` / `cd` / `cat` などの基本コマンドを一度は触ったことがある人。 **前提**:`find` や `grep` で他人のファイルを対象にする場合は `sudo` が必要になります。パイプ(`|`)とリダイレクト(`>`)を使う例が出てきます。未習なら [パイプとリダイレクトの基礎](/articles/tutorials/pipe-redirect-basics) を先に読んでください。 ### 先に用語を整理する {#terms} 初めて出てくる言葉を、ここで一度だけ定義します。 - **正規表現**(regular expression)とは、「こういう形の文字列」をパターンで表す書き方です。「正規表現式」「regex」「regexp」とも呼びます。この記事では「正規表現」で統一します。 - **メタ文字**とは、`^` `$` `.` `*` のように、文字そのものではなく特別な意味を持つ記号です。「特殊文字」とも呼びます。 - **アンカー**とは、行頭・行末・単語境界といった「位置」を指すメタ文字です。文字そのものにはマッチしません。 - **量指定子**とは、直前のパターンが何回繰り返されるかを指定する記号です。「繰り返し指定」「quantifier」とも呼びます。 - **文字クラス**とは、`[0-9]` のように「この範囲のどれか 1 文字」を表す書き方です。 - **エスケープ**とは、メタ文字の前に `\` を付けて「記号そのもの」として扱わせることです。 - **標準エラー出力**(stderr)とは、エラーメッセージが流れ出る出口です。`2>/dev/null` は、その出口を捨てるという指定です。 ## 3つのコマンドの概要と使い分け {#overview} > **結論**: find は場所探し、grep は中身探し、awk はデータ加工が得意。目的に合わせて使い分けるのが効率化の第一歩。 まず、各コマンドの特徴と用途を理解する。**適切なコマンドを選ぶこと** が効率的な作業の第一歩。 ### find:ファイル・ディレクトリ検索 - 名前でファイルを検索 - サイズ・日付での絞り込み - 権限・所有者での検索 - 見つけたファイルに対する一括処理 得意分野は **「どこにあるかわからないファイルを探す」**。 ```bash find /home -name "*.txt" -size +1M ``` ### grep:テキスト内容検索 - ファイル内のテキスト検索 - 正規表現を使った高度な検索 - ログファイルの解析 - 設定ファイルの確認 得意分野は **「ファイルの中身から特定の文字列を探す」**。 ```bash grep -r "ERROR" /var/log/ ``` `-r` は再帰(recursive)の指定で、指定ディレクトリの下を階層ごと辿る。`/var/log/` 配下は所有者が root のファイルが多いため、読めない場合は `sudo grep -r "ERROR" /var/log/` を使う。 ### awk:テキスト処理・データ加工 - 列データの抽出・計算 - CSVファイルの処理 - ログファイルの集計 - フォーマット変換 得意分野は **「データを加工・集計・変換する」**。 ```bash awk '{sum+=$3} END {print sum}' sales.csv ``` awk は入力を 1 行ずつ読み、空白で区切った各項目を **フィールド**(列)として扱う。`$1` が 1 列目、`$3` が 3 列目、`NF` が列の総数を指す。上の例は 3 列目を合計し、全行を読み終えた時点(`END`)で結果を表示している。 ### 判断フロー | 状況 | 使うコマンド | | ---------------------------------- | ------------ | | ファイルの場所がわからない | `find` | | ファイルの中身から文字列を探したい | `grep` | | データを加工・集計したい | `awk` | ## 正規表現マスタークラス {#regex-masterclass} > **結論**: 正規表現はアンカー・量指定子・文字クラスの組み合わせ。BRE / ERE / PCRE の違いを押さえれば3コマンドで応用できる。 正規表現(Regular Expression)は、find、grep、awk の真の力を引き出すための必須スキル。基礎から実務で即座に使えるパターンまで習得する。 ### 正規表現の種類 正規表現には方言が 3 つあります。同じパターンでもツールによって解釈が変わるため、最初にこの区別を押さえます。 | 種類 | 略称 | 対応ツール | 特徴 | | ---------------- | ---- | ------------------- | ---------------------------- | | 基本正規表現 | BRE | grep, sed, vi | メタ文字をエスケープ必要 | | 拡張正規表現 | ERE | egrep, grep -E, awk | より直感的な記法 | | Perl互換正規表現 | PCRE | grep -P, perl | 最も高機能(先読み・後読み) | `grep` は指定なしだと BRE です。`+` や `?` を素直に書きたい場合は `-E` を付けて ERE に切り替えます。 ### 位置指定(アンカー) ```bash # 行頭がERRORで始まる行 grep "^ERROR" logfile.txt # .logで終わる行 grep "\.log$" filelist.txt # portという単語(report等は除外) grep -E "\bport\b" config.txt ``` ### 文字クラス ```bash # 192.168.1.x のIPアドレス grep "192\.168\.1\." access.log # 時刻形式(HH:MM) grep "[0-9][0-9]:[0-9][0-9]" log.txt # 英数字以外の文字を含む行 grep "[^a-zA-Z0-9]" data.txt ``` ### 量指定子(Quantifiers) ```bash # 0回以上の繰り返し(errorとfailedが同じ行にある) grep "error.*failed" log.txt # 1回以上の繰り返し(ERE) grep -E "[0-9]+" data.txt # 0回または1回(httpまたはhttps) grep -E "https?" urls.txt # n回以上m回以下(2〜4桁の数字) grep -E "[0-9]{2,4}" data.txt ``` ### 高度なパターンマッチング **グループ化と OR 条件**: ```bash # 複数のキーワードをOR条件で検索 grep -E "(error|warning|critical)" log.txt ``` **先読み・後読みアサーション(PCRE)**: ```bash # 「円」の前の数字だけを抽出 grep -P "\d+(?=円)" price.txt # test.txt以外のtestを含むファイル grep -P "test(?!\.txt)" filelist.txt # $記号の後の数字を抽出 grep -P "(?<=\$)\d+" invoice.txt ``` ### 実務で使える正規表現パターン集 **ログ解析**: ```bash # IPアドレス(IPv4) grep -E "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" access.log # 日時パターン(Apache形式) grep -E "\[[0-9]{2}/[A-Z][a-z]{2}/[0-9]{4}:[0-9]{2}:[0-9]{2}:[0-9]{2} [+-][0-9]{4}\]" access.log # HTTPステータスコード集計 grep -E "\" [1-5][0-9]{2} " access.log | awk '{print $9}' | sort | uniq -c # エラーレベル抽出 grep -E "\b(DEBUG|INFO|WARN|ERROR|FATAL|CRITICAL)\b" app.log ``` ステータスコードの集計で `$9` を指定しているのは、Apache の combined format ではステータスが 9 列目に来るため。User-Agent が空白で分割されて列数が行ごとに変わるので、`$(NF-1)` のような末尾からの指定は使えない。 **データ検証**: ```bash # メールアドレス(簡易版) grep -E "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b" contacts.txt # URL(http/https) grep -E "https?://[^[:space:]\"']+" webdata.txt # 電話番号(日本) grep -E "(0[0-9]{1,4}-?[0-9]{1,4}-?[0-9]{4})" contacts.txt ``` URL の例で `[^\s...]` ではなく `[^[:space:]...]` を使っているのは、**ブラケット式(`[ ]`)の内側では `\s` が空白文字クラスとして解釈されない**ため。`[^\s"']` は「バックスラッシュ・`s`・`"`・`'` 以外」の意味になり、URL に `s` が現れた時点で打ち切られる。ブラケット式の内側では POSIX 文字クラス `[:space:]` を使う。 **コード解析**: ```bash # 関数定義(JavaScript/Python) grep -E "^(function|def)\s+[a-zA-Z_][a-zA-Z0-9_]*\s*\(" *.js *.py # 変数宣言(JavaScript) grep -E "^(var|let|const)\s+[a-zA-Z_][a-zA-Z0-9_]*" *.js # TODO/FIXMEコメント grep -E "(TODO|FIXME|XXX|HACK|NOTE):" -n *.py ``` ### 正規表現のデバッグ 複雑な正規表現は **段階的に構築** すること。 ```bash # Step 1: 数字を含む行 grep "[0-9]" test.txt # Step 2: 1つ以上の数字 grep "[0-9]\+" test.txt # Step 3: 数字のみの行 grep "^[0-9]\+$" test.txt ``` `-o` オプションで部分マッチを確認できる: ```bash echo "test123abc456" | grep -o "[0-9]\+" ``` ```output 123 456 ``` :::tip **BREとEREでのエスケープの違い** 同じ「192.168.1. のあとに 1 文字以上」を書き分けると次のようになる。 - BRE: 量指定子 `+` をエスケープする → `grep "192\.168\.1\..\+" access.log` - ERE: 量指定子 `+` はそのまま書ける → `grep -E "192\.168\.1\..+" access.log` どちらも `\.` がリテラルのドット、`.` が任意の 1 文字。BRE でエスケープが必要なのは**量指定子だけ**で、`.` はエスケープしない。`grep "192\.168\.1\.\+"` と書くと `\+` が直前の `\.` に係るため、「ドットが 1 個以上続く」という別の意味になる。 ::: ### パフォーマンス最適化 :::tip **正規表現を高速化する3つのコツ** 1. **アンカーを活用**: `grep "^error" huge.log` のほうが `grep "error" huge.log` より速い 2. **不要な `.*` を除去**: `grep "error" log.txt` で十分(`.*error.*` は遅い) 3. **固定文字列は `-F` オプション**: `grep -F "exact_string" file.txt` で正規表現エンジンを使わず高速化 ::: ## findコマンド:ファイル検索の極意 {#find-command} > **結論**: find は検索場所・検索条件・アクションの3要素で構成。名前やサイズ、日付で絞り込み一括処理まで実行できる。 find は **ファイルシステムを縦横無尽に検索** できる強力なコマンド。 ### 基本構文 ```bash find [検索場所] [検索条件] [アクション] ``` 検索場所で検索条件に合うファイルを見つけ、アクションを実行する。 ### 名前による検索 ```bash # 拡張子が.txtのファイルを検索 find /home -name "*.txt" # 「config」で始まるファイル find . -name "config*" # 大文字小文字を区別せずに.logファイルを検索 find /var -iname "*.LOG" ``` ### ファイル種別による検索 `-type` は探す対象の種類を絞る。`f` が通常ファイル、`d` がディレクトリ、`l` がシンボリックリンク(別のファイルを指す「ショートカット」のこと。「シンボリックリンク」「symlink」「ソフトリンク」はすべて同じものを指す)。 ```bash # 通常ファイルのみ find /home -type f # 「log」で始まるディレクトリ find /var -type d -name "log*" # シンボリックリンク find /tmp -type l ``` ### サイズによる検索 `-size` には注意点が 1 つある。**指定した単位に切り上げてから比較する**という仕様だ。`-size -1k` は「切り上げて 1KB 未満」つまり 0 単位のファイルを指すため、**0 バイトのファイルだけ**に一致する。500 バイトのファイルは 1KB に切り上げられるため一致しない。 ```bash # 100MB より大きい find /var -size +100M # 0 バイトのファイル(切り上げの結果 0 単位になるもの) find /home -size -1k # 1024 バイト未満を厳密に指定する(c = バイト単位) find /home -size -1024c # 1GB より大きく 10GB 未満(切り上げ後の単位で比較される) find . -size +1G -size -10G ``` バイト単位で厳密に絞り込みたい場合は、`k` / `M` / `G` ではなく `c`(バイト)を使う。切り上げが起きないため、意図した境界で絞り込める。 ### 日付・時刻による検索 時刻の条件は 3 種類ある。**mtime**(modification time)は中身が変更された時刻、**atime**(access time)は読み込まれた時刻、**ctime**(change time)は権限や所有者などの属性が変わった時刻を指す。数値の単位は日で、`-7` は「7 日以内」、`+30` は「30 日より前」を意味する。 ```bash # 過去7日以内に変更されたファイル(mtime) find /home -mtime -7 # 30日以上前に変更されたファイル find /var/log -mtime +30 # 1日以上アクセスされていないファイル(atime) find /tmp -atime +1 # reference.txtより新しいファイル find /home -newer reference.txt ``` ### 権限・所有者による検索 `-perm` は権限(パーミッション)で絞り込む。**setuid ビット**とは、実行したユーザーではなくファイル所有者の権限で動く指定のことで、悪用されると権限昇格の踏み台になる。そのため棚卸しの対象になる。 ```bash # 権限が755のファイル find /home -perm 755 # setuidビットが設定されたファイル(セキュリティチェック用) find / -perm -4000 2>/dev/null # www-dataユーザーが所有するファイル find /var -user www-data # developersグループが所有するファイル find /home -group developers ``` ### 実行アクション find の真の力は、見つけたファイルに対して自動で処理を実行できること。ただし、この機能は同時に最も事故が起きやすい部分でもある。 ::: danger **先に読む:`-delete` と `-exec` は取り消せない** `-delete` はファイルをゴミ箱に入れずに消す。`-exec chmod` は該当ファイル全部の権限を書き換える。**失敗するとどうなるか**は次の 2 点。 - 検索条件が広すぎた場合、意図していないファイルまで削除・変更される。 - `rm` と同じく、削除したファイルは元に戻せない(バックアップがなければ復旧不能)。 **安全に試す方法**は、必ず 2 段階に分けること。 1. 先に `-print` だけを付けて実行し、対象一覧を目で確認する。 2. 一覧が想定どおりなら、`-print` を `-delete` や `-exec` に差し替える。 ```bash # Step 1: 対象を確認するだけ(何も変更しない) find /tmp -name "*.tmp" -print # Step 2: 一覧が想定どおりなら削除する find /tmp -name "*.tmp" -delete ``` **`-delete` は必ず条件のあとに書く**。`find` は指定順に評価するため、`find /tmp -delete -name "*.tmp"` と書くと `-name` を評価する前に削除が走り、`/tmp` 配下がすべて消える。 練習は `/tmp` 配下に自分で作った作業用ディレクトリで行う。`/`・`/etc`・`/var` を対象にした練習はしない。 ::: **ファイル削除**: ```bash # 一時ファイルを一括削除 find /tmp -name "*.tmp" -delete # 30日以上古いログファイルを削除 find /var/log -name "*.log" -mtime +30 -delete ``` `-delete` は `find` 自身が削除する仕組みで、`-exec rm {} \;` よりも速く安全に動く。ファイル名に空白や改行が含まれていても正しく扱える。ただし `-delete` は `-depth`(深い階層から先に処理する)を暗黙に有効化するため、`-prune` による枝刈りとは併用できない。 **権限変更**: ```bash # PHPファイルの権限を644に変更 find /var/www -name "*.php" -exec chmod 644 {} \; # ディレクトリの権限を755に変更 find /home -type d -exec chmod 755 {} \; ``` `{}` は見つかったファイル名に置き換わるプレースホルダー、`\;` は 1 件ずつ実行するという区切り。`\;` を `+` に変えると複数件をまとめて渡すため高速になる。 ::: warning **権限は記録しておかないと戻せない** `ls -l` の `-rw-r--r--` という表示からは `chmod` に渡す数値を機械的に復元できない。戻せる状態を作るには、実行前に数値パーミッションをファイルへ保存する。 ```bash # 実行前: 数値パーミッションをファイルに保存する find /var/www -name "*.php" -exec stat -c '%a %n' {} + > ~/perm-backup.txt # 戻すとき: 保存した値を 1 行ずつ書き戻す while read -r mode path; do chmod "$mode" "$path"; done < ~/perm-backup.txt ``` ::: **情報収集**: ```bash # txtファイルの詳細情報を表示 find /home -name "*.txt" -exec ls -lh {} \; # 100MB以上のファイルのサイズを表示 find /var -size +100M -exec du -h {} \; ``` ### findコマンドのベストプラクティス :::tip **検索範囲を限定** ルートディレクトリ(`/`)から検索すると、ディスク全体を走査するため時間がかかる。稼働中のサーバではディスク負荷の原因にもなる。できるだけ具体的なディレクトリを指定する。 - 良い例: `find /var/log -name "*.log"` - 悪い例: `find / -name "*.log"` 途中で止めたい場合は `Ctrl+C` を押す。検索は読み取りのみなので、途中で止めてもファイルは壊れない。 ::: :::tip **権限エラーを回避** アクセス権のないディレクトリのエラーメッセージを `2>/dev/null` で非表示にする。 ```bash find / -name "*.txt" 2>/dev/null ``` ::: :::tip **効率的な条件組み合わせ** 複数条件を組み合わせて精密に検索する。 ```bash # 1MB以上、7日以内のログファイル find /home -name "*.log" -size +1M -mtime -7 ``` ::: ## トラブルシューティング {#troubleshooting} > **結論**: 詰まる原因はほぼ 4 つ。権限不足・引用符忘れ・正規表現の方言違い・検索範囲の広さ。 ### 症状: `Permission denied` が大量に出る **原因**: 自分に読み取り権限のないディレクトリを走査している。 **確認**: ```bash find /var -name "*.log" ``` **対処**: エラーだけを捨てるか、`sudo` を付ける。 ```bash find /var -name "*.log" 2>/dev/null # エラーを捨てる sudo find /var -name "*.log" # 権限を借りて読む ``` ### 症状: `find . -name *.txt` が想定と違う結果になる **原因**: 引用符がないため、`*.txt` をシェルが先に展開してしまう。 **確認**: ```bash find . -name *.txt ``` **対処**: 検索パターンは必ず引用符で囲む。 ```bash find . -name "*.txt" ``` ### 症状: `grep "[0-9]+"` が何もマッチしない **原因**: `grep` は指定なしだと BRE で動くため、`+` が「文字としての +」と解釈される。 **確認**: ```bash echo "abc123" | grep "[0-9]+" ``` **対処**: `-E` で ERE に切り替えるか、`+` をエスケープする。 ```bash echo "abc123" | grep -E "[0-9]+" # ERE echo "abc123" | grep "[0-9]\+" # BRE でエスケープ ``` ### 症状: `grep` を実行するとプロンプトが返らない **原因**: 検索対象のファイル名を書き忘れたため、`grep` が標準入力からの入力を待っている。 **確認**: 何も表示されず、キー入力を受け付ける状態になっている。 **対処**: `Ctrl+C` で中断し、ファイル名を指定して再実行する。パイプで受け取る場合は入力側のコマンドが必要。 ```bash grep -E "ERROR" app.log # ファイルを指定する tail -100 app.log | grep "ERROR" # パイプで渡す ``` ### 症状: find が返ってこない **原因**: 検索範囲が広すぎる(`/` 全域など)。 **確認**: `Ctrl+C` で中断し、指定したパスを見直す。 **対処**: 対象ディレクトリを具体的に絞る。深さを制限する `-maxdepth` も有効。 ```bash find /home/user -maxdepth 3 -name "*.log" ``` ## 作業完了チェックリスト {#checklist} - [ ] 目的(場所探し / 中身探し / 加工)から使うコマンドを選べた - [ ] 正規表現を書くとき、BRE と ERE のどちらで動いているか意識できた - [ ] `-delete` や `-exec` の前に `-print` で対象を確認した - [ ] 検索範囲を `/` ではなく具体的なディレクトリに絞った ## 次のステップ {#next-steps} 基礎編では、find/grep/awk の基本的な使い分け、正規表現の基礎、そして find コマンドの強力な検索機能について学んだ。次の応用編では、grep と awk の究極テクニックを習得する。 - [find・grep・awk応用編](/articles/tutorials/find-grep-awk-advanced) — grep の最適化と awk の高度な使い方 - [find・grep・awk実践編](/articles/tutorials/find-grep-awk-practical) — 組み合わせと実務活用 - [find・grep・awkマスターガイド](/articles/tutorials/find-grep-awk-mastery) — シリーズ全体の目次 # find・grep・awkマスターガイド - 基礎から実践まで Source: https://penguin-gym-linux.com/articles/tutorials/find-grep-awk-mastery Linux の **find、grep、awk** は、ファイル操作とデータ処理の最強トリオ。このシリーズでは、基礎から実践まで段階的に分けて、これらのコマンドを完全にマスターする。 ## この記事でわかること {#what-you-will-learn} - find・grep・awk シリーズ全4編(基礎・応用・実践・プロ)の構成 - レベル別(初心者・中級者・上級者)のおすすめ学習ルート - シリーズ完了で習得できる具体的なスキルと学習目標 - 自分のレベルに合った学習の始め方 ## シリーズ構成 {#series-overview} > **結論**: 基礎・応用・実践・プロの4編構成で、初心者から上級者まで段階的に find・grep・awk を習得できる。 初心者から上級者まで、段階的にスキルアップできる構成。あなたのレベルに合わせて学習を開始する。 ### [基礎編](/articles/tutorials/find-grep-awk-basics) 正規表現と find コマンドの基礎を学習する。 - 3つのコマンドの概要と使い分け - 正規表現マスタークラス - find コマンドによるファイル検索 学習時間の目安: 約20分/基礎レベル。 ### [応用編](/articles/tutorials/find-grep-awk-advanced) grep と awk の高度なテクニックを習得する。 - grep の環境変数最適化と次世代ツール(ripgrep、ag) - awk の連想配列・ユーザー定義関数 - ストリーム処理とパフォーマンス最適化 学習時間の目安: 約25分/中級〜上級レベル。 ### [実践編](/articles/tutorials/find-grep-awk-practical) 組み合わせテクニックと実務活用を学ぶ。 - 効果的な組み合わせパターン - 業界別実践事例(Web、インフラ、データ分析) - パフォーマンス最適化技術 - 便利なワンライナー集 学習時間の目安: 約25分/上級レベル。 ### [プロ編](/articles/tutorials/find-grep-awk-professional) 演習問題とトラブルシューティングで仕上げる。 - 段階的実力確認演習問題(初級〜マスターレベル) - よくある問題と解決法 - デバッグテクニック 学習時間の目安: 約20分/エキスパートレベル。 ## 学習の進め方 {#learning-path} > **結論**: 初心者・中級者・上級者でおすすめの学習順と推奨期間が異なる。自分のレベルに合ったルートを選ぶと効率的。 ### 初心者の方 1. 基礎編で正規表現と find コマンドを学習 2. 実践編で組み合わせテクニックを習得 3. プロ編で演習問題に挑戦 推奨学習期間: 2〜3週間。 ### 中級者の方 1. 基礎編で知識の確認 2. 応用編で高度なテクニックを習得 3. 実践編で実務レベルのスキル習得 4. プロ編で上級者への飛躍 推奨学習期間: 1〜2週間。 ### 上級者の方 1. 応用編・実践編で最新テクニックを確認 2. プロ編で演習問題とケーススタディを活用 3. 日常業務での実践 推奨学習期間: 数日〜1週間。 ## このシリーズで得られるもの {#benefits} :::highlight **作業効率の劇的向上** 手動作業の大部分を自動化し、数時間の作業を数分に短縮できる。 ::: :::highlight **実践的なスキル** 業界別ケーススタディで即戦力となる実務スキルを習得できる。Web エンジニア、インフラエンジニア、データアナリスト全領域で応用可能。 ::: :::highlight **キャリア拡張** インフラ・データ・DevOps 分野への転身可能性を獲得できる。 ::: ## 今すぐ学習を開始 {#start} ### 体系的学習 基礎から順番に学んで確実にマスターする。 [基礎編から開始](/articles/tutorials/find-grep-awk-basics) ### 実践重視 即戦力となる技術を最短で習得する。 [実践編から開始](/articles/tutorials/find-grep-awk-practical) ### スキル確認 現在の実力を演習問題でチェックする。 [プロ編で実力確認](/articles/tutorials/find-grep-awk-professional) ## 学習目標 {#goals} > **結論**: シリーズ完了時に find・grep・awk 単体の操作から、組み合わせ設計と実務での即戦力活用までを習得できる。 このシリーズ完了時には、以下が習得できる。 - **find**: 任意の条件でファイル・ディレクトリを高速検索 - **grep**: 正規表現を使った高度なテキスト検索 - **awk**: データ処理・集計・レポート生成の自動化 - **組み合わせ**: 複合コマンドの設計とパイプライン構築 - **実践**: 業務での即戦力活用 ## まとめ {#summary} find・grep・awk は Linux 上級者への必須スキル。このシリーズで段階的に学習することで、確実にマスターできる。理論だけでなく、実際に手を動かして練習することが最も重要。 [基礎編を学ぶ](/articles/tutorials/find-grep-awk-basics)から始めて、Linux データ処理の最強トリオを身につけよう。 # find・grep・awkの組み合わせ方 - 実務で使える活用テクニック Source: https://penguin-gym-linux.com/articles/tutorials/find-grep-awk-practical 実践編では、find、grep、awk を組み合わせた実用的なデータ処理パターン、業務での活用事例、パフォーマンス最適化を解説する。エンジニア・データアナリスト向けの即戦力スキルを習得する。 ## この記事でわかること {#what-you-will-learn} - find・grep・awk をパイプで組み合わせる実用パターン - 職種別の実務での活用事例とワークフロー - 大量データを扱う際のパフォーマンス最適化のコツ - 現場で即戦力となるデータ処理スキルの習得 ## 組み合わせテクニック {#combination-techniques} > **結論**: find・grep・awk をパイプでつなぐと、単体では難しい処理も段階的に分解して強力な解決策に組み立てられる。 真の Linux マスターは、find、grep、awk を組み合わせて使う。単体では難しい処理も、組み合わせることで強力な解決策になる。 ### パイプでつなぐ基本パターン **find + grep の組み合わせ**: ```bash # ログファイルからERRORを含むファイルを特定 find /var/log -name "*.log" -exec grep -l "ERROR" {} \; # txtファイル群から"password"を行番号付きで検索 find /home -name "*.txt" | xargs grep -n "password" ``` **grep + awk の組み合わせ**: ```bash # エラー行から日付・時刻・最後のフィールドを抽出 grep "ERROR" /var/log/app.log | awk '{print $1, $2, $NF}' # nginxプロセスのCPU使用率を合計 ps aux | grep "nginx" | awk '{sum+=$4} END {print "CPU使用率合計:", sum "%"}' ``` **find + awk の組み合わせ**: ```bash # ログファイルの総サイズとファイル数を計算 find /var -name "*.log" -printf "%s %p\n" | awk '{size+=$1; count++} END {printf "総サイズ: %.2f MB ファイル数: %d\n", size/1024/1024, count}' ``` ### 実務レベルの複合処理 **シナリオ1: Webサーバーのアクセス解析** 過去1週間のアクセスログから、エラーが多いIPアドレス TOP10 を抽出する。 ```bash find /var/log/apache2 -name "access.log*" -mtime -7 | \ xargs grep " 5[0-9][0-9] " | \ awk '{print $1}' | \ sort | uniq -c | \ sort -rn | \ head -10 | \ awk '{printf "%-15s %d回\n", $2, $1}' ``` ステップ解説: 1. `find`: 1週間以内のアクセスログファイルを検索 2. `grep`: 5xx エラー(サーバーエラー)の行を抽出 3. `awk`: IPアドレス(1列目)のみを抽出 4. `sort | uniq -c`: IPアドレス別にカウント 5. `sort -rn`: カウント数で降順ソート 6. `head -10`: TOP10 を取得 7. `awk`: 見やすい形式で出力 **シナリオ2: 古い一時ファイルの一括削除** システム全体から30日以上古い一時ファイルを安全に削除する。 ```bash # 1. まず対象ファイルを確認 find /tmp /var/tmp /home -name "*.tmp" -o -name "temp*" -o -name "*.temp" | \ xargs ls -la # 2. 安全性を確認したら削除実行 find /tmp /var/tmp /home -name "*.tmp" -mtime +30 -size +0 | \ xargs -I {} bash -c 'echo "削除: {}"; rm "{}"' ``` :::warning 削除前に必ず対象ファイル一覧を表示して確認すること。30日以上古く、サイズが0より大きいファイルのみ対象とする。 ::: **シナリオ3: データベース接続ログの分析** MySQL のログから時間帯別の接続数を分析する。 ```bash find /var/log/mysql -name "*.log" -mtime -1 | \ xargs grep -h "Connect" | \ awk '{ match($0, /[0-9]{4}-[0-9]{2}-[0-9]{2}T([0-9]{2})/, time_parts); hour = time_parts[1]; connections[hour]++; } END { print "時間帯別MySQL接続数(過去24時間)"; for (h = 0; h < 24; h++) { printf "%02d:00-%02d:59 | ", h, h; count = (h in connections) ? connections[h] : 0; printf "%5d回 ", count; for (i = 0; i < count/10; i++) printf "▓"; printf "\n"; } }' ``` ### ワンライナーテクニック集 よく使われる便利なワンライナー集。そのまま使えて実用性抜群。 **ディスク・ファイル管理**: ```bash # 最も大きいファイルTOP20 find . -type f -exec du -h {} + | sort -rh | head -20 # 古いログファイルの総サイズを計算 find /var -name "*.log" -mtime +7 -exec ls -lh {} \; | awk '{size+=$5} END {print "削除可能サイズ:", size/1024/1024 "MB"}' ``` **ネットワーク・アクセス解析**: ```bash # 今日のアクセス数が多いIPアドレスTOP10 grep "$(date '+%d/%b/%Y')" /var/log/apache2/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10 # SSHログイン失敗が多いIPアドレス find /var/log -name "*.log" | xargs grep -h "Failed password" | awk '{print $11}' | sort | uniq -c | sort -rn ``` **システム監視**: ```bash # プロセスごとのメモリ使用量 find /proc -maxdepth 2 -name "status" 2>/dev/null | xargs grep -l "VmRSS" | xargs -I {} bash -c 'echo -n "$(basename $(dirname {})): "; grep VmRSS {}' # 今日のシステムエラー・警告の発生元統計 find /var/log -name "syslog*" | xargs grep "$(date '+%b %d')" | grep -i "error\|warn\|fail" | awk '{print $5}' | sort | uniq -c | sort -rn ``` ### パイプライン設計パターン **エラーハンドリングと復旧パターン**: 本番環境では失敗が前提。エラーを適切に処理し、処理を継続する設計が重要。 ```bash #!/bin/bash set -euo pipefail handle_error() { echo "ERROR: パイプライン処理中にエラーが発生しました(行: $1)" >&2 exit 1 } trap 'handle_error $LINENO' ERR process_logs_safely() { local input_pattern="$1" local output_file="$2" local temp_dir="/tmp/pipeline_$$" mkdir -p "$temp_dir" find /var/log -name "$input_pattern" -type f 2>/dev/null > "$temp_dir/file_list" || { echo "WARNING: 一部のファイルにアクセスできませんでした" >&2 } if [[ ! -s "$temp_dir/file_list" ]]; then echo "ERROR: 処理対象ファイルが見つかりません" >&2 rm -rf "$temp_dir" return 1 fi while IFS= read -r logfile; do if [[ -r "$logfile" ]]; then grep -h "ERROR\|WARN" "$logfile" 2>/dev/null >> "$temp_dir/errors.log" || true fi done < "$temp_dir/file_list" if [[ -s "$temp_dir/errors.log" ]]; then awk ' { if ($0 ~ /ERROR/) error_count++; if ($0 ~ /WARN/) warn_count++; } END { printf "ERROR: %d件\n", error_count; printf "WARN: %d件\n", warn_count; }' "$temp_dir/errors.log" > "$output_file" fi rm -rf "$temp_dir" } ``` **並列処理パイプライン**: CPU 集約的処理を並列化して高速化する。 ```bash parallel_log_analysis() { local log_pattern="$1" local output_dir="$2" local cpu_cores=$(nproc) local max_parallel=$((cpu_cores - 1)) find /var/log -name "$log_pattern" -type f | \ xargs -n 1 -P "$max_parallel" -I {} bash -c ' logfile="$1" output_dir="$2" result_file="$output_dir/result_$$.tmp" grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}" "$logfile" | \ awk " { ip = \$1; if (match(ip, /^192\.168\./)) region = \"local\"; else if (match(ip, /^10\./)) region = \"internal\"; else region = \"external\"; total_by_region[region]++; } END { for (region in total_by_region) { printf \"region:%s total:%d\n\", region, total_by_region[region]; } }" > "$result_file" ' -- {} "$output_dir" } ``` ## 実務での活用事例 {#real-world-examples} > **結論**: ログ解析・データ集計・運用作業など、職種ごとの具体例で3コマンドが実務でどう役立つかを把握できる。 理論より実践。実際の業務でどのように使われているかを職種別に紹介する。 ### Webエンジニアの場合 **急な本番障害対応**: 「サイトが重い」との報告。原因を素早く特定する必要がある。 ```bash # 1. エラーログの確認 find /var/log/apache2 /var/log/nginx -name "*.log" | xargs grep -E "$(date '+%d/%b/%Y')" | grep -E "5[0-9][0-9]|error|timeout" | tail -50 # 2. 重いクエリの特定 find /var/log/mysql -name "*slow.log" | xargs grep -A 5 "Query_time" | awk '/Query_time: [5-9]/ {getline; print}' # 3. 異常なアクセスパターンの検出 grep "$(date '+%d/%b/%Y')" /var/log/apache2/access.log | awk '{print $1}' | sort | uniq -c | awk '$1 > 1000 {print "異常アクセス:", $2, "回数:", $1}' ``` :::tip 手動での確認なら30〜60分かかる作業が、5分で完了する。 ::: **月次レポート作成**: 先月のアクセス統計とエラー率をまとめる。 ```bash #!/bin/bash LAST_MONTH=$(date -d "last month" '+%b/%Y') echo "=== $LAST_MONTH アクセス統計レポート ===" # 総アクセス数 TOTAL_ACCESS=$(find /var/log/apache2 -name "access.log*" | xargs grep "$LAST_MONTH" | wc -l) echo "総アクセス数: $TOTAL_ACCESS" # ユニークビジター数 UNIQUE_VISITORS=$(find /var/log/apache2 -name "access.log*" | xargs grep "$LAST_MONTH" | awk '{print $1}' | sort -u | wc -l) echo "ユニークビジター: $UNIQUE_VISITORS" # エラー率 ERROR_COUNT=$(find /var/log/apache2 -name "access.log*" | xargs grep "$LAST_MONTH" | grep -E " [45][0-9][0-9] " | wc -l) ERROR_RATE=$(echo "scale=2; $ERROR_COUNT * 100 / $TOTAL_ACCESS" | bc) echo "エラー率: $ERROR_RATE%" # 人気ページTOP10 echo "=== 人気ページ TOP10 ===" find /var/log/apache2 -name "access.log*" | xargs grep "$LAST_MONTH" | awk '{print $7}' | grep -v "\.css\|\.js\|\.png\|\.jpg" | sort | uniq -c | sort -rn | head -10 ``` ### インフラエンジニアの場合 **サーバー監視・メンテナンス**: 複数サーバーの健康状態を定期的にチェックする。 ```bash #!/bin/bash echo "=== サーバー健康状態レポート ===" date # ディスク使用率警告 echo "=== ディスク使用率 (80%以上で警告) ===" df -h | awk 'NR>1 {gsub(/%/, "", $5); if($5 > 80) printf "WARNING: %s: %s使用中 (%s%%)\n", $6, $3, $5}' # メモリ使用率 echo "=== メモリ使用状況 ===" free -m | awk 'NR==2{printf "メモリ使用率: %.1f%% (%dMB / %dMB)\n", $3*100/$2, $3, $2}' # 高CPU使用プロセス echo "=== CPU使用率TOP5 ===" ps aux --no-headers | sort -rn -k3 | head -5 | awk '{printf "%-10s %5.1f%% %s\n", $1, $3, $11}' # エラーログの急増チェック echo "=== 直近1時間のエラー件数 ===" find /var/log -name "*.log" -mmin -60 | xargs grep -h -E "$(date '+%b %d %H')|$(date -d '1 hour ago' '+%b %d %H')" | grep -ci error ``` ### データアナリストの場合 **大量データの前処理**: 数GBの CSV ファイルを Excel で開けない。事前に加工が必要。 ```bash #!/bin/bash CSV_FILE="sales_data_2024.csv" OUTPUT_DIR="processed_data" mkdir -p $OUTPUT_DIR # ファイルサイズ・行数確認 echo "ファイルサイズ: $(du -h "$CSV_FILE" | cut -f1)" echo "総行数: $(wc -l < "$CSV_FILE")" # データ品質チェック echo "空行数: $(grep -c '^$' "$CSV_FILE")" echo "不正な行数: $(awk -F',' 'NF != 5 {count++} END {print count+0}' "$CSV_FILE")" # 月別データに分割 awk -F',' 'NR==1 {header=$0; next} { month=substr($1,1,7); if(!seen[month]) { print header > "'$OUTPUT_DIR'/sales_" month ".csv"; seen[month]=1; } print $0 > "'$OUTPUT_DIR'/sales_" month ".csv" }' "$CSV_FILE" # 各月のサマリー作成 find $OUTPUT_DIR -name "sales_*.csv" | sort | while read file; do month=$(basename "$file" .csv | cut -d'_' -f2) total_sales=$(awk -F',' 'NR>1 {sum+=$4} END {print sum}' "$file") record_count=$(expr $(wc -l < "$file") - 1) printf "%s: %d件, 売上合計: %d\n" "$month" "$record_count" "$total_sales" done ``` ### 業界別ケーススタディ **ゲーム開発業界:大規模ログ解析** オンラインゲームで1日100GBのプレイヤー行動ログから、チート検出とゲームバランス分析を行う。 ```bash analyze_game_logs() { local log_date="$1" local output_dir="/analysis/$(date +%Y%m%d)" mkdir -p "$output_dir" find /game/logs -name "*${log_date}*.log" -type f | \ xargs grep -h "PLAYER_ACTION" | \ awk -F'|' ' { player_id = $3; action = $4; value = $5; # 異常に短時間での大量アクション検出 if (action == "LEVEL_UP") { player_levelups[player_id]++; if (player_levelups[player_id] > 10) { print "SUSPICIOUS_LEVELUP", player_id > "/tmp/cheat_suspects.log"; } } # 金額の異常な増加 if (action == "GOLD_CHANGE" && value > 1000000) { print "SUSPICIOUS_GOLD", player_id, value > "/tmp/gold_anomaly.log"; } player_actions[player_id]++; total_actions++; } END { avg_actions = total_actions / length(player_actions); for (player in player_actions) { if (player_actions[player] > avg_actions * 5) { printf "HIGH_ACTIVITY: %s (%d actions)\n", player, player_actions[player]; } } }' } ``` **EC・小売業界:顧客行動分析** EC サイトのアクセスログから顧客の購買パターンを分析する。 ```bash analyze_customer_journey() { local analysis_period="$1" local output_dir="/analytics/customer_journey" mkdir -p "$output_dir" find /var/log/nginx -name "access.log*" | \ xargs grep "$analysis_period" | \ awk ' BEGIN { session_timeout = 1800; } { ip = $1; url = $7; if (url ~ /\/checkout|\/purchase/) { purchase_sessions[ip]++; } if (url ~ /\/products\/([0-9]+)/) { match(url, /\/products\/([0-9]+)/, product_match); product_views[product_match[1]]++; } } END { print "=== 顧客ジャーニー分析 ==="; for (product in product_views) { printf "商品ID %s: %d回閲覧\n", product, product_views[product]; } }' } ``` ## パフォーマンス最適化 {#performance-optimization} > **結論**: 検索範囲の限定・固定文字列指定・不要な処理の削減により、大量データでも各コマンドを高速に動作させられる。 ### 基本的な高速化テクニック ```bash # 1. 検索範囲を限定する find /var/log -name "*.log" # 良い例 # find / -name "*.log" # 悪い例(遅い) # 2. ロケール最適化 LC_ALL=C grep "ERROR" huge.log # 3. 固定文字列の場合は -F grep -F "literal_string" file.txt # 4. 不要なディレクトリをスキップ find /var -path "*/node_modules" -prune -o -name "*.log" -print ``` ### 上級最適化テクニック ```bash # 並列処理で高速化 find /var/log -name "*.log" | xargs -P 4 grep "ERROR" # 圧縮ファイル直接検索 zgrep "ERROR" /var/log/app.log.gz # memmap でファイルを高速読み込み(GNU grep) grep --mmap "ERROR" huge_file.log ``` ### パフォーマンス測定と監視 ```bash # 実行時間を測定 time grep "ERROR" /var/log/huge.log # 詳細なリソース使用量を測定 /usr/bin/time -v grep "ERROR" /var/log/huge.log # 並列処理の効果測定 for cores in 1 2 4 8; do echo "並列数: $cores" time find /var/log -name "*.log" | xargs -P $cores grep -c "ERROR" done ``` ## 次のステップ {#next-steps} 実践編で学んだ組み合わせテクニックを、プロ編で演習問題とトラブルシューティングで仕上げる。 - [find・grep・awkプロ編](/articles/tutorials/find-grep-awk-professional) — 演習問題とトラブルシューティング - [find・grep・awkマスターガイド](/articles/tutorials/find-grep-awk-mastery) — シリーズ全体の目次 - [シェルスクリプト実践編](/articles/tutorials/shell-scripting-practical) — 自動化スクリプト # find・grep・awkの演習問題 - トラブルシューティングと実践力強化 Source: https://penguin-gym-linux.com/articles/tutorials/find-grep-awk-professional シリーズ最終編。実力確認のための演習問題、よくあるトラブルの解決法、スキルアップのロードマップを提供する。Linux 上級者への仕上げを行う。 ## この記事でわかること {#what-you-will-learn} - find・grep・awk で実務中に遭遇する典型的な問題と解決法 - 実力を確認するための演習問題とチャレンジ - さらなるスキルアップのための学習ロードマップ - Linux 上級者へ仕上げるための総まとめ ## よくある問題と解決法 {#troubleshooting} > **結論**: find・grep・awk で実務中に陥りやすい典型的な問題を、原因と解決策をセットで事前に把握できる。 実務で使っていると、必ず遭遇する典型的な問題と解決策を事前に知っておく。 ### findコマンドの問題 **Permission denied エラーが大量に出る** 症状: ```output find: '/root': Permission denied find: '/proc/1': Permission denied ``` 解決法: ```bash # 方法1: エラー出力を無視 find / -name "*.txt" 2>/dev/null # 方法2: 権限のある場所のみ検索 find /home /var /tmp -name "*.txt" # 方法3: sudoで実行(要注意) sudo find / -name "*.txt" ``` **ファイル名に空白があるとエラー** 症状: `"My Document.txt"` を `"My"`、`"Document.txt"` として解釈してしまう。 解決法: ```bash # -print0 と xargs -0 を使用 find /home -name "*.txt" -print0 | xargs -0 rm # -exec with + を使用 find /home -name "*.txt" -exec rm {} + ``` **検索が遅すぎる** 解決法: - `-path` で不要ディレクトリをスキップ - `-maxdepth` で検索深度を制限 - `-type f` でファイルのみに限定 ```bash find /var -maxdepth 3 -type f -path "*/node_modules" -prune -o -name "*.log" -print ``` ### grepコマンドの問題 **日本語(多バイト文字)が正しく検索できない** 解決法: ```bash # ロケール設定を確認・変更 export LANG=ja_JP.UTF-8 grep "エラー" logfile.txt # バイナリファイル扱いを回避 grep -a "エラー" logfile.txt ``` **正規表現が期待通りに動かない** よくある問題: `+`、`?`、`{}` が文字として扱われる、`()` でのグループ化ができない。 解決法: ```bash # -E で拡張正規表現を使用 grep -E "colou?r" file.txt grep -E "(http|https)://" file.txt # egrepエイリアスを使用 egrep "colou?r" file.txt ``` **Binary file matches エラー** 症状: `Binary file image.jpg matches` 解決法: ```bash # テキストファイルのみ検索 grep -I "pattern" * # ファイル種別を限定 grep -r --include="*.txt" --include="*.log" "pattern" . ``` ### awkコマンドの問題 **フィールドが期待通りに分割されない** 症状: CSV ファイルで項目内にカンマがある場合、`"田中太郎","28","東京都渋谷区","エンジニア,チームリーダー"` が正しく分割されない。 解決法: ```bash # 専用ツールを使用 csvtool col 1,2 data.csv # Pythonとの連携 python3 -c " import csv, sys reader = csv.reader(sys.stdin) for row in reader: print(row[0], row[1]) " < data.csv ``` **数値計算で精度が落ちる** 症状: 小数点の計算結果が不正確(期待 10.50、実際 10.5000000001)。 解決法: ```bash # printf で桁数指定 awk '{sum+=$1} END {printf "%.2f\n", sum}' numbers.txt # bcコマンドと連携 awk '{print $1}' numbers.txt | paste -sd+ | bc ``` ### デバッグテクニック :::tip **段階的な確認** 複雑なコマンドは部分的に実行して確認する。 ```bash # 最終的なコマンド find /var/log -name "*.log" | xargs grep -l "ERROR" | xargs wc -l # デバッグ手順 # 1. find部分のみ実行 find /var/log -name "*.log" # 2. grep部分まで実行 find /var/log -name "*.log" | xargs grep -l "ERROR" # 3. 全体を実行 find /var/log -name "*.log" | xargs grep -l "ERROR" | xargs wc -l ``` ::: :::tip **中間結果の保存** 長時間かかる処理は中間結果をファイルに保存する。 ```bash find /var -name "*.log" > all_logs.txt grep -l "ERROR" $(cat all_logs.txt) > error_logs.txt wc -l $(cat error_logs.txt) > final_result.txt ``` ::: ## さらなるスキルアップ {#next-steps} find、grep、awk をマスターしたあなたが次に学ぶべきスキルを紹介する。 ### 次のレベルのコマンド **sed(ストリームエディタ)**: テキストの置換・削除・挿入を高速実行。 ```bash sed 's/error/ERROR/g' logfile.txt ``` 学習優先度: 最高。 **xargs(引数変換)**: パイプ出力をコマンドライン引数に変換。 ```bash find . -name "*.txt" | xargs -P 4 wc -l ``` 学習優先度: 最高。 **sort/uniq(ソート・重複削除)**: データの並び替えと重複処理。 ```bash cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn ``` 学習優先度: 高。 **join/paste(ファイル結合)**: 複数ファイルのデータ結合。 ```bash join -t, file1.csv file2.csv ``` 学習優先度: 中。 ## 演習問題とチャレンジ {#exercises-challenges} > **結論**: 段階的な演習問題に実際に手を動かして取り組むことで、find・grep・awk の実力を確認し定着させられる。 理論だけでなく実際に手を動かしてスキルを定着させる。以下の演習問題に取り組んで、実力を確認する。 ### 初級チャレンジ **チャレンジ1: ファイル検索の基本** `/var/log` ディレクトリ以下から、拡張子が `.log` で、サイズが1MB以上のファイルを見つけよ。 :::details チャレンジ1 ヒントを見る find コマンドで `-name` と `-size` オプションを組み合わせる。 ::: :::details チャレンジ1 解答例を見る ```bash find /var/log -name "*.log" -size +1M ``` ::: **チャレンジ2: テキスト検索の基本** `system.log` ファイルから "ERROR" を含む行を検索し、行番号付きで表示せよ。 :::details チャレンジ2 解答例を見る ```bash grep -n "ERROR" system.log ``` ::: **チャレンジ3: データ集計の基本** `sales.csv` ファイルの3列目(売上)の合計を計算せよ。 :::details チャレンジ3 解答例を見る ```bash awk -F',' '{sum += $3} END {print "合計:", sum}' sales.csv ``` ::: ### 中級チャレンジ **チャレンジ4: ログ分析パイプライン** アクセスログから今日のユニーク IP アドレス数を数えよ。 :::details チャレンジ4 ヒントを見る grep で今日の日付を検索 → awk で IP アドレス抽出 → sort/uniq で重複除去。 ::: :::details チャレンジ4 解答例を見る ```bash grep "$(date '+%d/%b/%Y')" access.log | awk '{print $1}' | sort -u | wc -l ``` ::: **チャレンジ5: 大容量ファイル検索** ホームディレクトリから100MB以上の大きなファイル TOP5 を見つけ、サイズ順に表示せよ。 :::details チャレンジ5 解答例を見る ```bash find /home -type f -size +100M -exec ls -lh {} \; | sort -rh -k5 | head -5 ``` ::: **チャレンジ6: エラー統計レポート** 複数のログファイルからエラーの種類別件数を集計し、多い順に表示せよ。 :::details チャレンジ6 解答例を見る ```bash find /var/log -name "*.log" | xargs grep -h "ERROR" | awk '{print $4}' | sort | uniq -c | sort -rn ``` ::: ### 上級チャレンジ **チャレンジ7: Webサイト監視スクリプト** Apache アクセスログから、過去1時間で 404 エラーが10回以上発生した IP アドレスを特定し、アラートメッセージを生成せよ。 :::details チャレンジ7 ヒントを見る 時間フィルタ → 404エラー抽出 → IP別集計 → しきい値判定。 ::: :::details チャレンジ7 解答例を見る ```bash hour_ago=$(date -d '1 hour ago' '+%d/%b/%Y:%H') current_hour=$(date '+%d/%b/%Y:%H') grep -E "($hour_ago|$current_hour)" /var/log/apache2/access.log | \ grep " 404 " | \ awk '{print $1}' | \ sort | uniq -c | \ awk '$1 >= 10 {printf "ALERT: IP %s has %d 404 errors in last hour\n", $2, $1}' ``` ::: **チャレンジ8: データ品質チェック** CSV ファイルのデータ品質をチェックし、総行数・列数、空白行の数、各列のユニーク値数、数値列の最大・最小・平均値を報告するスクリプトを作成せよ。 :::details チャレンジ8 解答例を見る ```bash awk -F',' ' NR == 1 { num_columns = NF for (i = 1; i <= NF; i++) headers[i] = $i next } NF == 0 { empty_lines++; next } { total_rows++ for (i = 1; i <= num_columns && i <= NF; i++) { field_values[i][$i] = 1 if ($i ~ /^[0-9]+\.?[0-9]*$/) { numeric_count[i]++ numeric_sum[i] += $i if (numeric_min[i] == "" || $i < numeric_min[i]) numeric_min[i] = $i if (numeric_max[i] == "" || $i > numeric_max[i]) numeric_max[i] = $i } } } END { printf "総行数: %d\n", total_rows printf "列数: %d\n", num_columns printf "空白行数: %d\n", empty_lines + 0 for (i = 1; i <= num_columns; i++) { printf "列%d (%s): ユニーク値数=%d", i, headers[i], length(field_values[i]) if (numeric_count[i] > 0) { avg = numeric_sum[i] / numeric_count[i] printf ", 最小=%.2f, 最大=%.2f, 平均=%.2f", numeric_min[i], numeric_max[i], avg } print "" } }' data.csv ``` ::: **チャレンジ9: 自動バックアップスクリプト** 重要なファイルの自動バックアップスクリプトを作成せよ。前回バックアップ以降に更新されたファイルのみ対象、ファイルサイズが100MB未満のもののみ、バックアップ処理のログ生成、古いバックアップの自動削除(7日以上前)を含めよ。 :::details チャレンジ9 解答例を見る ```bash #!/bin/bash BACKUP_DIR="/backup/$(date +%Y%m%d_%H%M%S)" LAST_BACKUP_MARKER="/var/log/last_backup.timestamp" LOG_FILE="/var/log/backup.log" echo "=== Backup started at $(date) ===" >> "$LOG_FILE" mkdir -p "$BACKUP_DIR" find /home/important -type f -size -100M -newer "$LAST_BACKUP_MARKER" 2>/dev/null | \ while read file; do rel_path="${file#/home/important/}" backup_path="$BACKUP_DIR/$rel_path" backup_dir=$(dirname "$backup_path") mkdir -p "$backup_dir" if cp "$file" "$backup_path" 2>/dev/null; then echo "Backed up: $file" >> "$LOG_FILE" fi done # 古いバックアップの削除 find /backup -type d -mtime +7 -exec rm -rf {} + 2>/dev/null # タイムスタンプ更新 date > "$LAST_BACKUP_MARKER" ``` ::: ### マスターチャレンジ **チャレンジ10: 総合システム監視ダッシュボード** 以下の機能を持つシステム監視スクリプトを作成せよ。 - リアルタイムでログファイルを監視 - エラー発生時の自動アラート - システムリソース使用状況の可視化 - 日次レポートの自動生成 - Web 画面での確認(HTML レポート生成) :::details チャレンジ10 アプローチのヒント `tail -f` でリアルタイム監視、awk で統計処理、find で古いファイル管理、HTML テンプレートでレポート生成。 ::: :::tip このチャレンジを完了できれば、確実に Linux 上級者と言える。 ::: ## まとめ:Linux上級者への第一歩 {#conclusion} このシリーズでは、find、grep、awk の3つのコマンドについて、基礎から実践的な応用まで詳しく解説した。これらのコマンドをマスターすることで、真の Linux 上級者に到達できる。 ### 習得したスキル - find: 任意の条件でファイル・ディレクトリを高速検索 - grep: 正規表現を使った高度なテキスト検索 - awk: データ処理・集計・レポート生成 - 3つのコマンドの効果的な組み合わせ - パフォーマンス最適化とトラブルシューティング - 業界別実践事例と実務スキル - 演習問題と実力確認 ### シリーズ振り返り - [基礎編](/articles/tutorials/find-grep-awk-basics) — コマンドの概要と正規表現の基礎 - [応用編](/articles/tutorials/find-grep-awk-advanced) — grep と awk の高度なテクニック - [実践編](/articles/tutorials/find-grep-awk-practical) — 組み合わせと業務活用 - [プロ編](/articles/tutorials/find-grep-awk-professional) — 演習問題とトラブルシューティング(本記事) ### 期待される効果 - **作業効率**: 手動作業の大部分を自動化 - **問題解決力**: ログ解析やデータ分析が短時間で完了 - **キャリア**: インフラ・データ・DevOps 分野への展開が可能 ### 今すぐ実践しよう 学んだ知識を実際の業務で活用することが最も重要。Penguin Gym Linux で実践練習し、日常的にコマンドを使いこなすことで、真のスキルとして定着する。 # ファイアウォール設定入門 - ufwとfirewalldの基礎 Source: https://penguin-gym-linux.com/articles/tutorials/firewall-basics ## ファイアウォールとは何か? {#what-is-firewall} ファイアウォールはネットワーク通信を許可・遮断するフィルタリング機構だ。Linux カーネルの `iptables`/`nftables` がその実体で、`ufw` と `firewalld` はそれを管理する高レベルツールに相当する。 ::: tip **ディストリビューション別の標準ツール** - **Ubuntu / Debian 系**: `ufw`(Uncomplicated Firewall) - **RHEL / CentOS / Fedora 系**: `firewalld` ::: ## ufw と firewalld はどう違うのか? {#ufw-vs-firewalld} `ufw` はシンプルなコマンドラインインターフェースを重視した静的ルールセット型の設計だ。`firewalld` はゾーン(zone)という概念でルールを管理し、サービスを再起動せずに D-Bus 経由でルールを動的に変更できる。 | 項目 | ufw | firewalld | | ------------ | ------------------- | ------------------- | | 主な用途 | Ubuntu / Debian | RHEL / CentOS | | ルール管理 | 静的 | 動的(zone ベース) | | 設定の複雑さ | シンプル | 柔軟だが複雑 | | バックエンド | iptables / nftables | nftables / iptables | ## ufw の基本的な使い方 {#ufw-basics} ### インストールと有効化 Ubuntu ではデフォルトでインストール済みだ。有効化する前に SSH を許可しないとロックアウトされるため、順番に注意する。 ```bash $ sudo ufw status Status: inactive $ sudo ufw allow 22/tcp Rules updated Rules updated (v6) $ sudo ufw enable Command may disrupt existing ssh connections. Proceed with operation (y|n)? y Firewall is active and enabled on system startup ``` ::: warning `ufw enable` の前に必ず SSH(22番ポート)を許可すること。順番を誤るとリモート接続が切断される。 ::: ### ポートの許可・拒否 ```bash # ポート番号で許可 $ sudo ufw allow 80/tcp $ sudo ufw allow 443/tcp # サービス名で許可(/etc/services の定義を参照) $ sudo ufw allow ssh $ sudo ufw allow http $ sudo ufw allow https # ポートを拒否 $ sudo ufw deny 23/tcp # IP アドレスからの接続を許可 $ sudo ufw allow from 192.168.1.0/24 # 特定 IP からの特定ポートへのアクセスを許可 $ sudo ufw allow from 192.168.1.100 to any port 3306 ``` ### デフォルトポリシーの設定 ```bash # インバウンドをデフォルト拒否(推奨) $ sudo ufw default deny incoming # アウトバウンドをデフォルト許可 $ sudo ufw default allow outgoing ``` ### ルールの確認・削除 ```bash # 現在のルールを番号付きで表示 $ sudo ufw status numbered ``` ```output Status: active To Action From -- ------ ---- [ 1] 22/tcp ALLOW IN Anywhere [ 2] 80/tcp ALLOW IN Anywhere [ 3] 443/tcp ALLOW IN Anywhere ``` ```bash # ルール番号を指定して削除 $ sudo ufw delete 3 # ルール内容を指定して削除 $ sudo ufw delete allow 443/tcp ``` ### ufw の無効化とリセット ```bash # 一時的に無効化(ルールは保持) $ sudo ufw disable # ルールを完全にリセット $ sudo ufw reset ``` ::: danger `ufw reset` はすべてのルールを削除する。実行前に現在のルールをメモしておくこと。 ::: ## firewalld の基本的な使い方 {#firewalld-basics} ### インストールと起動 RHEL 系では標準でインストール済みの場合が多い。 ```bash # RHEL / CentOS $ sudo yum install firewalld # Fedora $ sudo dnf install firewalld $ sudo systemctl start firewalld $ sudo systemctl enable firewalld ``` ### ゾーン(zone)の概念 `firewalld` のルールはゾーンに紐付く。各ネットワークインターフェースはいずれか 1 つのゾーンに属し、そのゾーンのルールが適用される。 ```bash # 使用可能なゾーン一覧 $ firewall-cmd --get-zones block dmz drop external home internal public trusted work # デフォルトゾーンの確認 $ firewall-cmd --get-default-zone public # アクティブなゾーンとインターフェースの確認 $ firewall-cmd --get-active-zones ``` ### ポートの許可・削除 ```bash # 現在のルールを確認 $ firewall-cmd --list-all # ポートを一時的に許可(再起動で消える) $ sudo firewall-cmd --add-port=80/tcp # ポートを永続的に許可 $ sudo firewall-cmd --add-port=80/tcp --permanent $ sudo firewall-cmd --reload # サービス名で許可 $ sudo firewall-cmd --add-service=http --permanent $ sudo firewall-cmd --add-service=https --permanent $ sudo firewall-cmd --reload # ポートを削除 $ sudo firewall-cmd --remove-port=80/tcp --permanent $ sudo firewall-cmd --reload ``` ::: tip `--permanent` を付けると設定ファイルに書き込まれ再起動後も有効になる。`--permanent` なしの変更は現在のセッションのみ有効。`--reload` で設定ファイルの内容を即時適用できる。 ::: ### 使用可能なサービス一覧 ```bash $ firewall-cmd --get-services ``` ### ゾーンへのソース IP 追加 ```bash # 特定のサブネットを trusted ゾーンに追加 $ sudo firewall-cmd --zone=trusted --add-source=192.168.1.0/24 --permanent $ sudo firewall-cmd --reload ``` ## よくある設定パターンはどれか? {#common-patterns} ### Web サーバー(HTTP / HTTPS + SSH) **ufw の場合:** ```bash $ sudo ufw default deny incoming $ sudo ufw default allow outgoing $ sudo ufw allow ssh $ sudo ufw allow http $ sudo ufw allow https $ sudo ufw enable ``` **firewalld の場合:** ```bash $ sudo firewall-cmd --set-default-zone=public $ sudo firewall-cmd --add-service=ssh --permanent $ sudo firewall-cmd --add-service=http --permanent $ sudo firewall-cmd --add-service=https --permanent $ sudo firewall-cmd --reload ``` ### データベースサーバー(MySQL を特定 IP のみ許可) **ufw の場合:** ```bash $ sudo ufw allow from 192.168.1.10 to any port 3306 proto tcp ``` **firewalld の場合:** ```bash $ sudo firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.1.10" port port="3306" protocol="tcp" accept' --permanent $ sudo firewall-cmd --reload ``` ## 設定が反映されないときはどうするか? {#troubleshooting} 設定後にポートへの接続が通らない場合、以下の順で確認する。 ```bash # ufw: ファイアウォール自体が有効か確認 $ sudo ufw status Status: active # firewalld: サービスが起動しているか確認 $ sudo systemctl status firewalld # 実際にポートがリッスンしているか確認 $ ss -tlnp | grep :80 ``` ::: warning **クラウド環境(AWS / GCP / Azure)の注意点** クラウドの場合、VPC のセキュリティグループやネットワーク ACL が OS レベルのファイアウォールより前段で動作する。OS 側でポートを開けてもクラウド側で塞がれていれば通信できない。両方の設定を確認すること。 ::: ```bash # ログを有効化して確認(ufw) $ sudo ufw logging on $ sudo tail -f /var/log/ufw.log # ログを確認(firewalld) $ sudo journalctl -u firewalld -f ``` ## 次に読む {#next} - [SSH 鍵認証セットアップ](/articles/tutorials/ssh-key-setup) - [netstat/ss でポートと接続状態を確認する](/articles/tutorials/netstat-ss-basics) - [systemd ユニットファイルを書く](/articles/tutorials/systemd-unit-creation) # flock 入門 - 多重起動を防ぐファイルロック Source: https://penguin-gym-linux.com/articles/tutorials/flock-file-locking ## flock とは何か? {#intro} > **結論**: `flock` は util-linux 付属のコマンドで、ファイルを使った排他ロックによりスクリプトやジョブの多重起動を 1 行で防げる。 この記事で身につくこと: - `flock` で **cron ジョブの多重起動を防ぐ型** が分かる - **排他 / 共有 / ノンブロッキング / タイムアウト** の使い分けが分かる - スクリプトに **自己ロックを組み込む定型句** が手に入る ::: tip **結論(実務の型)** - 単発コマンドを守る → `flock -n /var/lock/job.lock cmd` - スクリプト全体を守る → 先頭に `FLOCKER` 定型句 - 待たせたい → `-w 秒数`、待たせず即失敗 → `-n` ::: ::: warning **前提(対象環境)** - `flock` は **util-linux** に含まれ、ほぼすべての Linux ディストリに標準搭載 - ロックは **advisory(協調的)**。`flock` を使うプロセス同士でのみ効く - NFS / CIFS では実装が限定的(後述) ::: ## なぜ多重起動を防ぐ必要があるのか? {#why} > **結論**: バックアップやバッチが前回分の実行中に再起動すると、データ破損・二重課金・負荷暴走を招く。重なりを構造的に防ぐのが flock。 cron で 5 分おきに動かすバッチが、たまたま 5 分以内に終わらなかったとする。すると次の起動が前回の実行中に重なり、同じファイルを 2 プロセスが書き込む。結果は典型的に次のいずれか。 - 出力ファイルやログの **破損・混線** - 同じ処理の **二重実行**(メール二重送信・課金二重計上) - プロセスが積み上がり **メモリ / CPU の枯渇** PID を書いたロックファイルを自前で管理する方法もあるが、プロセスが `kill -9` や電源断で死ぬと **stale lock(残骸)** が残り、次回が永久に起動しなくなる。`flock` ならカーネルがプロセス終了時に自動でロックを解放するため、この掃除が要らない。 ## flock の基本的な使い方は? {#basic} > **結論**: 基本形は `flock ロックファイル コマンド`。ロックファイルは無ければ自動作成され、コマンド終了と同時にロックが解放される。 `flock` には 3 つの構文がある。 ```bash # 形1: ファイル/ディレクトリをロックしてコマンド実行 flock /var/lock/mytask.lock command args # 形2: -c でシェル経由の単一コマンドを実行 flock /var/lock/mytask.lock -c 'command1 && command2' # 形3: 既に開いたファイルディスクリプタ番号をロック flock 200 ``` 最小の例。`echo` を排他ロック下で実行する。 ```bash flock -x /tmp/myapp.lock echo 'running under lock' ``` - ロックファイル(`/tmp/myapp.lock`)は存在しなければ作られる - `-x` は **排他ロック**(デフォルトなので省略可) - `echo` が終わると同時にロックは解放される ::: tip ロックファイルは「鍵」であって「データ」ではない。中身は空でよく、削除する必要もない。ファイルが残っていても問題は起きない(ロック状態はファイルの存在ではなく、開いている fd に紐づくため)。 ::: ## flock の主要オプションは? {#options} > **結論**: 覚えるべきは `-n`(待たない)・`-w`(時間制限)・`-s`(共有)・`-E`(失敗時の終了コード)の 4 つ。 | オプション | 意味 | | ------------------------------ | ------------------------------------------------------------ | | `-x`, `--exclusive` | 排他ロック(書き込みロック)。**デフォルト** | | `-s`, `--shared` | 共有ロック(読み取りロック)。複数が同時取得可 | | `-n`, `--nonblock` | 取得できなければ待たず即失敗(終了コード 1) | | `-w`, `--timeout 秒` | 指定秒数だけ待ち、取れなければ失敗。小数可(`-w 0.5`) | | `-u`, `--unlock` | ロックを明示的に解放 | | `-E`, `--conflict-exit-code N` | `-n` / `-w` 失敗時の終了コードを指定(既定 1) | | `-o`, `--close` | コマンド実行前に fd を閉じる(子プロセスにロックを渡さない) | | `-c`, `--command` | シェル(`sh -c`)経由で単一コマンドを実行 | ::: warning `-w 0` は `--nonblock` と同じ意味になる(ゼロ秒待ち = 待たない)。 ::: ## cron の多重起動を防ぐには? {#cron} > **結論**: cron 行を `flock -n ロックファイル` で包む。前回が実行中なら今回は黙って終了し、重なりが起きない。 これが `flock` の最頻出ユースケース。crontab を次のように書く。 ```bash # 5分おきにバックアップ。前回が走っていたら今回はスキップ */5 * * * * /usr/bin/flock -n /var/lock/backup.lock /opt/scripts/backup.sh ``` - `-n` で **重なったら即終了**(待たずにスキップ) - 前回ジョブが終わっていればロックを取得して実行 - ロックファイルは `/var/lock/` か `/run/lock/` に置くのが慣例 「重なったら少しだけ待ってほしい」場合は `-w` を使う。 ```bash # 最大30秒待ち、それでも取れなければ諦める */5 * * * * /usr/bin/flock -w 30 /var/lock/backup.lock /opt/scripts/backup.sh ``` ::: tip cron 内では `flock` を**絶対パス**(`/usr/bin/flock`)で書くと、PATH が最小限の環境でも確実に動く。 ::: ## スクリプトに自己ロックを組み込むには? {#self-lock} > **結論**: スクリプト先頭にファイルディスクリプタ方式か `FLOCKER` 定型句を置くと、呼び出し方を問わず自分自身を多重起動から守れる。 cron 行に毎回 `flock` を書く代わりに、スクリプト自身にロックを内蔵すると安全。代表的な 2 パターン。 ### パターン1: ファイルディスクリプタ方式 ```bash #!/bin/bash exec 200>/var/lock/myscript.lock flock -n 200 || { echo "already running"; exit 1; } # --- ここから下が排他実行されるクリティカルセクション --- echo "do work..." sleep 10 ``` - `exec 200>...` でロックファイルを **fd 200** に開く(200 は慣例、9〜255 で空き番号なら何でもよい) - `flock -n 200` でその fd をロック。取れなければ `||` 側で終了 - スクリプトが終わると fd が閉じ、ロックは自動解放 ### パターン2: FLOCKER 定型句(man 公式) ```bash #!/bin/bash [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || : # --- ここから下は必ずロック下で動く --- echo "do work..." ``` スクリプト自身をロックファイルとして使い、環境変数 `FLOCKER` で「既に flock 経由で再実行済みか」を判定する。先頭に貼るだけで自己ロックが完成する。 ::: warning `flock -en "$0"` のように **スクリプトファイル自体をロック対象**にする場合、そのスクリプトを `>` で書き換える処理(自己更新スクリプト等)と相性が悪い。ロック対象は書き換えない専用ファイルにするのが無難。 ::: ## ノンブロッキングとタイムアウトはどう使い分ける? {#timeout} > **結論**: 重なりを捨ててよいなら `-n`、少し待てば処理できるなら `-w 秒数`。失敗を検知したいときは終了コードを見る。 ```bash # 即失敗(重なったらスキップでよいバッチ向け) flock -n /var/lock/job.lock ./job.sh # 最大10秒待つ(短時間の競合なら吸収したい処理向け) flock -w 10 /var/lock/job.lock ./job.sh ``` ロック取得に失敗したかどうかは **終了コード**で判定する。`-n` / `-w` が失敗したときの終了コードは既定で `1`、`-E` で変更できる。 ```bash flock -n -E 99 /var/lock/job.lock ./job.sh if [ $? -eq 99 ]; then echo "別プロセスが実行中のためスキップした" fi ``` 成功してコマンドが走った場合、`flock` の終了コードは **そのコマンドの終了コード**がそのまま返る。ロック失敗(`-E` 値)と区別できるよう、`-E` には通常コマンドが返さない値を選ぶとよい。 ## flock のよくある落とし穴は? {#pitfalls} > **結論**: NFS での非対応、データファイルを `>` で再生成してロックが外れる事故、ロックファイルの置き場所が主な罠。 ::: danger **1. NFS / CIFS では効かないことがある** `flock(2)` の実装はネットワークファイルシステムで限定的。マウント条件によっては `flock` が常に失敗、あるいはロックが効かないことがある。共有ストレージ上ではなく、**ローカルの `/var/lock` / `/run/lock`** にロックファイルを置くこと。 ::: ::: warning **2. データファイルを再生成するとロックが外れる** ロックは「開いている fd」に紐づく。次のように出力先データファイルそのものをロックし、後で `>` で作り直すと **inode が変わってロックが無意味**になる。 ```bash # NG: data.txt をロックしつつ data.txt を再生成している flock data.txt sh -c '> data.txt; generate >> data.txt' ``` ロック対象は**書き換えない専用ロックファイル**にする。 ::: ::: warning **3. flock はデッドロックを検出しない** 複数のロックを交差して取り合うとデッドロックし得るが、`flock` 自身は検出しない。ロック取得は **1 スクリプト 1 ロック**を基本にし、複数ロックを跨ぐ設計は避ける。 ::: これらを避ければ、`flock` は数文字で多重起動を封じる強力で堅牢な道具になる。 ## まとめ・次に読む {#next} - [cron の基本](/articles/tutorials/cron-basics) - [systemd タイマー vs cron](/articles/tutorials/systemd-timer-vs-cron) - [trap でシグナルを安全に処理する](/articles/tutorials/trap-signal-handling) # fuser 入門 - ファイル・ディレクトリを使用中のプロセスを特定する Source: https://penguin-gym-linux.com/articles/tutorials/fuser-command ## この記事で解決できること {#intro} - `fuser` で **ファイル・ディレクトリを使用中のプロセス** を特定できる - `umount` で出る **「target is busy」「device is busy」の犯人** を即座に突き止められる - 使用中プロセスを **安全に kill する型**(`-k` / `-i` / シグナル指定)が身につく ::: tip **結論(実務の型)** - 「誰が掴んでいるか」を知りたい → `fuser -v <対象>` - マウントを外せない → `fuser -m <マウントポイント>` で犯人特定 - 止めても安全と確認できたら → `fuser -ki <対象>`(対話確認つき kill) ::: ::: warning **前提(対象環境)** - ディストリ問わず(`fuser` は `psmisc` パッケージ提供) - 例は Ubuntu / systemd 環境を想定 - マウント操作・kill には対象によって root 権限が必要 ::: ## fuser とは何か? {#what} > **結論**: `fuser` は指定したファイル・ディレクトリ・マウントポイント・ソケットを「現在使用中のプロセス」を逆引きするコマンド。PID とアクセス種別を返す。 `ps` や `top` は「プロセス → そのプロセスが何をしているか」を見る。`fuser` はその逆で、**「このファイルを今どのプロセスが掴んでいるか」** を調べる。 最小の使い方は、対象を引数に渡すだけ。 ```bash $ fuser /var/log/syslog ``` ```output /var/log/syslog: 742 ``` `742` が `/var/log/syslog` を使用中のプロセス ID。コロンの右に PID が並ぶ。何も掴んでいなければ PID は表示されず、終了コードが非ゼロになる。 ::: tip `fuser` が見つからない場合は `psmisc` を導入する(`sudo apt install psmisc` / `sudo dnf install psmisc`)。`pstree` `killall` も同パッケージ。 ::: ## 誰が使っているか詳しく見るには?(-v) {#verbose} > **結論**: `-v`(verbose)を付けると、USER・PID・ACCESS(アクセス種別)・COMMAND が表形式で並び、「誰が何の用途で掴んでいるか」が一目で分かる。 PID だけでは正体が分からない。`-v` で文脈をそろえる。 ```bash $ fuser -v /var/log/syslog ``` ```output USER PID ACCESS COMMAND /var/log/syslog: syslog 742 F.... rsyslogd ``` `ACCESS` 列の各文字が「どう掴んでいるか」を示す。 | 記号 | 意味 | | ---- | -------------------------------------------- | | `c` | カレントディレクトリとして使用 | | `e` | 実行中の実行ファイル | | `f` | オープン中のファイル(デフォルト表示で省略) | | `F` | 書き込み用にオープン中(同上) | | `r` | ルートディレクトリ | | `m` | mmap されたファイル・共有ライブラリ | 上の例の `F....` は「`rsyslogd` が書き込み用に開いている」状態。`-v` 無しの通常表示では `f` / `F` は省略され、PID と末尾記号(`c` `e` `r` `m`)だけが出る点に注意する。 ## umount できない「target is busy」を解決するには? {#busy} > **結論**: アンマウント失敗の原因は「そのファイルシステム上を使用中のプロセス」。`fuser -m <マウントポイント>` で全プロセスを洗い出せる。 最頻出のシナリオがこれ。 ```bash $ sudo umount /mnt/data ``` ```output umount: /mnt/data: target is busy. ``` `-m` を付けると、**指定したマウントポイント上のファイルを使っている全プロセス** を列挙する(引数はマウントポイントでもブロックデバイスでもよい)。 ```bash $ fuser -vm /mnt/data ``` ```output USER PID ACCESS COMMAND /mnt/data: alice 3210 ..c.. bash alice 3398 F.... vim ``` この例では `bash`(カレントディレクトリが `/mnt/data` 配下)と `vim`(ファイルを編集中)が掴んでいる。まずは正攻法で対処する。 1. `bash` → そのシェルで `cd` して `/mnt/data` から出る 2. `vim` → 保存して閉じる それでも残る常駐プロセスがあり、止めて安全と確認できた場合のみ、次の kill に進む。 ::: warning `-m` の引数を **マウントポイントではなく単なるディレクトリ** に渡すと、そのファイルシステム全体のプロセスが対象になる。`-M`(`--ismountpoint`)を併用すると、引数が実際のマウントポイントのときだけ動作し、誤爆を防げる。 ::: ## 使用中のプロセスを安全に止めるには?(-k / -i) {#kill} > **結論**: `-k` で使用中プロセスを kill できるが、デフォルトは SIGKILL で即死。実務では `-i`(対話確認)と明示シグナル指定を併用するのが安全。 `-k` は列挙したプロセスにシグナルを送る。**何も付けないとデフォルトは SIGKILL(-9)** で、プロセスは後始末なしに強制終了する。 ```bash # 危険: 確認なしで /mnt/data 上の全プロセスを SIGKILL $ fuser -km /mnt/data ``` 事故を避けるため、次の 2 つを足す。 ```bash # -i: 1 プロセスごとに kill するか対話確認 # -TERM: SIGKILL ではなく SIGTERM(正常終了を促す)を送る $ fuser -kim -TERM /mnt/data ``` ```output USER PID ACCESS COMMAND /mnt/data: alice 3398 F.... vim Kill process 3398 ? (y/N) ``` ::: danger `fuser -k` は **掴んでいるプロセスを無差別に止める**。`/`(ルート)や `/usr` のようなシステム領域に対して実行すると、システムを巻き込んで停止させる危険がある。kill 前に必ず `-v` で対象を確認すること。 ::: シグナルは `-HUP` `-TERM` `-KILL` のように名前で、または番号で指定できる。利用可能なシグナル一覧は `fuser -l` で確認する。 ## ポートを使用中のプロセスを調べるには?(-n) {#port} > **結論**: `fuser -n tcp <ポート>` で、特定の TCP/UDP ポートを LISTEN・使用中のプロセスを特定できる。「アドレスは既に使用中」エラーの調査に有効。 `-n ` で名前空間を切り替える。`tcp` / `udp` を指定するとポート番号で検索できる。 ```bash # TCP 80 番を使用中のプロセス $ sudo fuser -v -n tcp 80 ``` ```output USER PID ACCESS COMMAND 80/tcp: root 1180 F.... nginx ``` `fuser 80/tcp` のように `ポート/プロトコル` 形式でも同じ結果になる。`Address already in use` でサーバが起動しないとき、犯人の特定に使える。 ::: tip ポート・ソケットの調査は `ss` / `lsof` の方が情報量は多い。`fuser` は「掴んでいる PID をすぐ止めたい」場面(`-k` 連携)で軽快。用途で使い分ける。 ::: ## fuser と lsof はどう使い分けるか? {#vs-lsof} > **結論**: 「使用中の PID を特定して即 kill」までやるなら `fuser`、開いているファイルやポートを詳細に一覧・調査するなら `lsof`。役割が重なるが目的が違う。 | 観点 | fuser | lsof | | ------------------ | ---------------------------- | ------------------------------------- | | 主目的 | 対象を使う PID の特定 + kill | 開いているファイル/ソケットの一覧調査 | | 出力 | PID とアクセス種別(簡潔) | プロセス・FD・種別・サイズ等(詳細) | | kill 機能 | あり(`-k`) | なし(PID を別途 kill) | | マウント点まるごと | `-m` が得意 | `+D` / `+f` で代替 | | 提供パッケージ | `psmisc` | `lsof` | 「アンマウントできない犯人を特定してその場で止める」なら `fuser -m` が最短。「何が起きているか腰を据えて調べる」なら `lsof`。 ::: tip **コピペ用:busy 解決テンプレ** ```bash # 1) 犯人を確認(kill しない) fuser -vm /mnt/data # 2) 安全と確認できたら対話 + SIGTERM で停止 fuser -kim -TERM /mnt/data # 3) 改めてアンマウント sudo umount /mnt/data ``` ::: ## 次に読む {#next} - [lsof 入門 - 開いているファイルとポートを調べる](/articles/tutorials/lsof-basics) - [ps・top・killの使い方](/articles/tutorials/process-management-basics) - [netstat/ss入門 - ポートと接続状態の確認方法](/articles/tutorials/netstat-ss-basics) # fzf 入門 - 履歴・ファイルをあいまい検索で爆速に Source: https://penguin-gym-linux.com/articles/tutorials/fzf-fuzzy-finder ## fzf とは? {#intro} > **結論**: fzf は標準入力のリストを対話的にあいまい検索し、選んだ行を標準出力に返す UNIX フィルタ。履歴・ファイル・プロセスなど「一覧から 1 つ選ぶ」操作をすべて高速化する。 `fzf`(fuzzy finder)は、コマンドラインで動く汎用のあいまい検索ツールである。動作原理はシンプルで、次の 3 ステップに尽きる。 1. 標準入力(stdin)から行のリストを受け取る 2. 入力欄に打った文字で**部分一致・あいまい一致**しながら候補を絞り込む 3. 確定した行を標準出力(stdout)に書き出す この「stdin を受けて stdout に返す」という性質のおかげで、`fzf` はあらゆるコマンドと組み合わせられる。`find | fzf`、`ps -ef | fzf`、`git log | fzf` ——どんな一覧でも、その場で絞り込んで 1 行(または複数行)を選べる。 ::: tip **この記事で身につくこと** - `CTRL-R` / `CTRL-T` / `ALT-C` の 3 大キーバインドで日常操作を爆速化する - あいまい検索の拡張構文(完全一致・前方一致・否定)を使い分ける - `--preview` で中身を見ながら選ぶ実務レシピを書く ::: ::: warning **前提(対象環境)** - OS: Linux(Ubuntu / RHEL 系)/ macOS - シェル: bash または zsh - fzf バージョン: 0.48.0 以降を推奨(`fzf --bash` 等の統合フラグが使える) ::: ## fzf をインストールするには? {#install} > **結論**: ディストリのパッケージ管理(`apt` / `dnf` / `brew`)が最も手軽。最新版や全機能が欲しい場合は GitHub の公式インストールスクリプトを使う。 パッケージマネージャ経由が簡単。 ```bash # Ubuntu / Debian sudo apt install fzf # Fedora / RHEL 系 sudo dnf install fzf # macOS(Homebrew) brew install fzf ``` ディストリ同梱版が古い場合や、キーバインド・補完を確実に入れたい場合は、公式リポジトリから直接インストールする。 ```bash git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf ~/.fzf/install ``` インストールスクリプトは対話形式で、キーバインド・補完・シェル設定への追記を行うか尋ねてくる。基本はすべて `y` で問題ない。 ```bash fzf --version ``` ```output 0.55.0 (cabc3c0) ``` ::: tip バージョンが `0.48.0` 未満なら、後述のシェル統合は「旧方式(ファイルを source する)」が必要になる。まずは新しい版を入れるのが近道。 ::: ## シェル統合で 3 つのキーバインドを有効化する {#shell-integration} > **結論**: シェル統合を有効化すると `CTRL-R`(履歴)・`CTRL-T`(ファイル挿入)・`ALT-C`(ディレクトリ移動)が使えるようになる。fzf 0.48.0 以降は 1 行追記するだけ。 設定ファイルに次の 1 行を追記する。 ```bash # ~/.bashrc(bash) eval "$(fzf --bash)" ``` ```bash # ~/.zshrc(zsh) source <(fzf --zsh) ``` 追記後、`source ~/.bashrc`(または新しいターミナルを開く)で反映される。これで 3 つのキーバインドが有効になる。 | キー | 動作 | | -------- | ----------------------------------------------------------- | | `CTRL-R` | コマンド履歴をあいまい検索して挿入 | | `CTRL-T` | カレント配下のファイル/ディレクトリを選んでコマンド行に挿入 | | `ALT-C` | サブディレクトリを選んでその場で `cd` | 最も恩恵が大きいのは **`CTRL-R`** である。標準の `CTRL-R`(逐次インクリメンタル検索)と違い、うろ覚えの単語を順不同で入れても候補が一覧表示され、上下キーで選べる。 ::: warning **fzf 0.48.0 未満の場合** `fzf --bash` フラグは使えない。代わりにインストール時に生成された設定ファイルを source する(パスは環境により異なる)。 ```bash # 例: git clone 方式でインストールした場合 [ -f ~/.fzf.bash ] && source ~/.fzf.bash ``` ::: ## なぜ fzf の検索は速いのか?あいまい検索の構文 {#search-syntax} > **結論**: fzf は既定でスペース区切りの全単語 AND 一致+あいまい一致。記号を足せば完全一致・前方/後方一致・否定・OR も指定でき、絞り込みの精度を細かく制御できる。 入力欄では、文字を順不同・飛び飛びで打っても一致する(例: `ako` が `awk-oneliners` にヒット)。さらに記号で一致モードを切り替えられる。 | 入力例 | 意味 | | ---------- | --------------------------------------------- | --------------------------------- | | `git push` | `git` と `push` の両方を含む(AND・あいまい) | | `'wild` | `wild` を**完全一致**で含む(あいまい無効) | | `^core` | `core` で**始まる** | | `.md$` | `.md` で**終わる** | | `!test` | `test` を**含まない**(否定) | | `^src | ^app` | `src` または `app` で始まる(OR) | ```bash # 拡張子 .log で終わり、archive を含まないファイルだけ絞り込む find . -type f | fzf --query "'.log$ !archive" ``` `--query`(短縮 `-q`)で初期クエリを与えられる。スクリプトから「ある程度絞った状態」で起動したいときに便利。 ## どう実務で使うのか?(実践レシピ) {#recipes} > **結論**: `$(... | fzf)` をコマンド置換に埋め込むのが基本形。vim で開く・プロセスを kill する・git ブランチを切り替える、といった「選んで実行」が 1 行で書ける。 ### ファイルを選んで vim で開く ```bash vim "$(fzf)" ``` ### プロセスを選んで kill する ```bash kill -9 "$(ps -ef | fzf | awk '{print $2}')" ``` ### git ブランチを選んでチェックアウト ```bash git checkout "$(git branch --all | fzf | tr -d ' *')" ``` ### ディレクトリを選んで移動(ALT-C を使わない場合) ```bash cd "$(find . -type d | fzf)" ``` ::: tip 複数選択したいときは `-m`(`--multi`)を付け、`TAB` でマークする。選んだ全行が改行区切りで出力される。 ```bash # 複数ファイルをまとめて削除候補に rm -i $(fzf -m) ``` ::: ## プレビューウィンドウで中身を確認しながら選ぶ {#preview} > **結論**: `--preview` に「選択中の行を引数に取るコマンド」を渡すと、右側に中身が表示される。プレースホルダ `{}` が現在の候補に置き換わる。 ファイル選択時に中身を覗けると、選び間違いが激減する。 ```bash # 選択中ファイルの中身を表示 fzf --preview 'cat {}' # bat があれば色付きでプレビュー(行番号・シンタックスハイライト) fzf --preview 'bat --color=always {}' ``` `{}` が「いま選択している行」に展開される。`git log` と組み合わせると、コミットを選びながら差分を確認できる。 ```bash git log --oneline | fzf --preview 'git show --color=always {1}' ``` `{1}` は選択行の 1 番目のフィールド(=コミットハッシュ)。プレビュー内でフィールド指定ができる。 ## よく使うオプションと環境変数 {#options} > **結論**: 見た目は `--height` / `--layout=reverse` / `--border` で整える。既定の動作は `FZF_DEFAULT_OPTS` と `FZF_DEFAULT_COMMAND` に一度書いておくと全コマンドへ反映される。 ### よく使うオプション - `--height 40%`: 画面全体を占有せず、下から 40% にウィンドウを表示 - `--layout=reverse`: 入力欄を上、候補を下に表示(上から読める) - `--border`: 枠線を付ける - `--multi` / `-m`: `TAB` で複数選択 - `--preview 'cmd {}'`: プレビューウィンドウ ### 環境変数で既定値を固定する ```bash # ~/.bashrc などに追記 export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border' # 候補生成コマンドを ripgrep / fd に差し替え(.gitignore 尊重・隠しファイル除外) export FZF_DEFAULT_COMMAND='fd --type f --hidden --exclude .git' ``` `FZF_DEFAULT_COMMAND` は、引数なしで `fzf` を起動したとき・`CTRL-T` のときの候補生成に使われる。`fd` や `rg --files` を指定すると `.gitignore` を尊重した高速な一覧になる。 ::: warning `FZF_DEFAULT_COMMAND` に指定したコマンド(`fd` / `rg` 等)が未インストールだと、`fzf` 起動時に候補が空になる。エイリアスではなく実体のあるコマンド名を指定すること。 ::: ::: tip **コピペ用: 最初に入れる設定** ```bash # ~/.bashrc eval "$(fzf --bash)" export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border' ``` これだけで `CTRL-R` / `CTRL-T` / `ALT-C` と、見やすい既定レイアウトが揃う。 ::: ## まとめと次に読む {#next} `fzf` は「一覧から 1 つ選ぶ」というコマンドライン作業を丸ごと高速化する。まず `CTRL-R` の履歴検索から使い始め、慣れたら `--preview` やコマンド置換で自分のワークフローに組み込むとよい。 - [bash ヒストリ活用術 - 過去のコマンドを使い倒す](/articles/tutorials/bash-history-tips) - [find・grep・awkの使い方入門 - 正規表現の基礎から](/articles/tutorials/find-grep-awk-basics) - [alias コマンド入門 - エイリアスでコマンドを短縮する方法](/articles/tutorials/alias-basics) # getopts 入門 - シェルスクリプトでオプション引数を処理する Source: https://penguin-gym-linux.com/articles/tutorials/getopts-argument-parsing ## この記事で解決できること {#intro} - シェルスクリプトで `-a` `-b value` のような **オプション引数を安全に処理** できる - `getopts` の `optstring` / `OPTARG` / `OPTIND` の **役割と書き方** が分かる - 不正オプション・引数不足を **自前でハンドリング** できる(silent モード) - `getopts`(シェル組み込み)と `getopt`(外部コマンド)の **使い分け** が分かる ::: tip **結論(実務の型)** - オプション解析は **自前パースより `getopts`**(POSIX 準拠・移植性が高い) - ループは `while getopts ":a:bc" opt; do ... done` の形が基本 - 引数を取るオプションは optstring で `文字:` と書く(`a:`) - 解析後は `shift $((OPTIND - 1))` で **残りの位置引数** を取り出す ::: ::: warning **前提(対象環境)** - bash / POSIX sh(`getopts` は組み込みコマンド) - 扱えるのは **短いオプション**(`-a` `-v`)のみ。`--verbose` のようなロングオプションは非対応 ::: ## getopts とは何か? {#what} > **結論**: `getopts` はシェル組み込みのオプション解析コマンド。`while` ループと組み合わせて `-a` `-b value` 形式の引数を 1 つずつ取り出す。 `getopts` は **コマンドラインのオプション(`-` で始まる短い引数)を 1 つずつ解析する** ためのシェル組み込みコマンドである。`$1` `$2` を `case` 文で泥臭く分解する代わりに、標準化された方法でオプションを処理できる。 基本構文は次のとおり。 ```bash getopts optstring name [args] ``` - `optstring`: 受け付けるオプション文字の一覧(例: `"abc"` なら `-a` `-b` `-c`) - `name`: 検出したオプション文字を格納する変数名 - 呼び出すたびに **次のオプションを 1 つ** 処理し、オプションが残っていれば終了ステータス 0(真)を返す この「残っている間は真を返す」性質を使い、`while` でループするのが定石である。 ## なぜ自前パースより getopts を使うのか? {#why} > **結論**: `getopts` は結合オプション(`-abc`)や `--` の扱いを標準仕様どおり処理する。`case` 文の自前実装はこれらを取りこぼしやすい。 自前で `while [ $# -gt 0 ]` と `case "$1" in` を書くと、次のような細かい仕様を **すべて自分で実装** する羽目になる。 - 結合オプション: `-a -b -c` を `-abc` とまとめて書けるようにする - 引数付きオプション: `-b value` と `-bvalue` の両方を受け付ける - 終端記号 `--`: それ以降をオプションとして扱わない - 不正オプション・引数不足のエラー検出 `getopts` はこれらを **POSIX 標準の挙動** で処理する。移植性が高く、bash 専用の構文に依存しないため、`#!/bin/sh` のスクリプトでもそのまま動く。 ::: tip ロングオプション(`--output file`)が必須でなければ、まず `getopts` を検討する。コードが短くなり、挙動も標準化される。 ::: ## getopts の基本的な使い方は? {#basic} > **結論**: `optstring` で受け付ける文字を宣言し、引数を取る文字には `:` を付ける。`OPTARG` に引数値、`OPTIND` に次の位置が入る。 ### optstring の書き方 `optstring` は受け付けるオプション文字を並べた文字列。**引数を取るオプションは文字の後ろに `:` を付ける**。 | optstring | 意味 | | --------- | ------------------------------------- | | `"abc"` | `-a` `-b` `-c`(いずれも引数なし) | | `"a:bc"` | `-a` は引数あり、`-b` `-c` は引数なし | | `":a:bc"` | 先頭 `:` で silent モード(後述) | ### 基本テンプレート ```bash #!/bin/bash verbose=0 output="" while getopts "vo:" opt; do case "$opt" in v) verbose=1 ;; o) output="$OPTARG" ;; \?) echo "不明なオプション: -$OPTARG" >&2; exit 1 ;; esac done shift $((OPTIND - 1)) echo "verbose=$verbose output=$output" echo "残りの引数: $*" ``` 実行例: ```bash $ ./script.sh -v -o result.txt input1 input2 ``` ```output verbose=1 output=result.txt 残りの引数: input1 input2 ``` ### OPTARG と OPTIND `getopts` は解析の過程で 2 つの変数を自動更新する。 - **`OPTARG`**: 引数を取るオプション(`o:` 等)の **引数値** が入る。上の例では `-o result.txt` の `result.txt` - **`OPTIND`**: **次に処理する引数の位置**(番号)。最初は `1`。解析が終わると「最初の非オプション引数」を指す ループ後に `shift $((OPTIND - 1))` を実行すると、処理済みのオプションがすべて取り除かれ、`$1` 以降に **オプション以外の引数(ファイル名等)だけ** が残る。 ::: warning `OPTIND` はシェル起動時に `1` で初期化される。同じシェル内で `getopts` ループを **2 回** 回す関数等では、ループの前に手動で `OPTIND=1` にリセットしないと 2 回目が正しく動かない。 ::: ## エラー処理はどうするのか? {#error} > **結論**: optstring の先頭に `:` を置くと silent モードになり、不正オプションは `?`、引数不足は `:` として `name` に入り、`OPTARG` に該当オプション文字が入る。 `getopts` のエラー報告には 2 つのモードがある。 ### 通常モード(optstring が `:` で始まらない) 不正オプションや引数不足を検出すると、`getopts` が **標準エラーへ自動でメッセージを出力** し、`name` に `?` を格納する。手軽だが、メッセージの文面を制御できない。 ### silent モード(optstring の先頭に `:`) optstring を `":vo:"` のように **先頭 `:`** で始めると、`getopts` は自動メッセージを出さず、すべてのエラーをスクリプト側で処理できる。挙動は次のとおり。 | 状況 | `name` の値 | `OPTARG` の値 | | ---------------- | ----------- | ------------------------------ | | 不正なオプション | `?` | 入力された不正な文字 | | 引数が不足 | `:` | 引数を必要としたオプション文字 | silent モードでのエラーハンドリング例: ```bash while getopts ":vo:" opt; do case "$opt" in v) verbose=1 ;; o) output="$OPTARG" ;; \?) echo "不明なオプション: -$OPTARG" >&2; exit 1 ;; :) echo "オプション -$OPTARG には引数が必要です" >&2; exit 1 ;; esac done ``` ::: tip **実務では silent モードを推奨**。エラーメッセージを日本語化したり、`usage()` を呼んで終了したりと、ユーザー向けの出力を完全にコントロールできる。 ::: ## getopts と getopt の違いは? {#vs-getopt} > **結論**: `getopts` はシェル組み込みで短いオプション専用。ロングオプション(`--verbose`)が必要なら外部コマンド `getopt`(util-linux)を使う。 名前が 1 文字違うだけの紛らわしい 2 つだが、別物である。 | 項目 | `getopts`(組み込み) | `getopt`(外部コマンド) | | ---------------- | ------------------------ | -------------------------------------- | | 実体 | シェル組み込みコマンド | `/usr/bin/getopt`(util-linux 等) | | ロングオプション | 非対応(`-a` のみ) | 対応(`--all`) | | 移植性 | POSIX 標準で高い | 実装差あり(GNU 拡張版が必要なことも) | | 使い方 | `while` ループで都度取得 | 一括で並べ替えてから解析 | 短いオプションだけで足りるなら `getopts`、`--output` のようなロングオプションが要件なら `getopt`(GNU 拡張版)を検討する。 ::: warning ロングオプションを `getopts` だけで扱う裏技(`-` を引数ありオプションとして処理し `OPTARG` を再解析する手法)も存在するが、可読性が下がる。要件が固いなら素直に `getopt` を使うほうが保守しやすい。 ::: ## よくあるハマりどころは? {#pitfalls} > **結論**: `OPTIND` のリセット忘れ、`shift` の付け忘れ、ロングオプション非対応の 3 つが定番。 ### 1. 関数内で OPTIND をリセットしない 関数内で `getopts` を使うと、`OPTIND` が前回の値を引きずる。**ループ前に `local OPTIND` または `OPTIND=1`** を入れる。 ```bash parse_args() { local OPTIND # 関数ローカルにして毎回リセット while getopts ":vo:" opt; do # ... done } ``` ### 2. shift を忘れて位置引数がずれる `shift $((OPTIND - 1))` を忘れると、`$1` がオプション文字列のままになり、後続のファイル処理が壊れる。**ループ直後に必ず実行** する。 ### 3. ロングオプションを渡してしまう `--verbose` を `getopts` に渡すと、`-` の後の各文字(`-`, `v`, `e`...)を個別オプションとして解釈し、意図しない動作になる。ロングオプションが必要なら設計段階で `getopt` を選ぶ。 ## まとめ {#summary} `getopts` はシェルスクリプトのオプション解析を **標準化された移植性の高い方法** で実装できる組み込みコマンド。`while getopts ":a:bc" opt; do case ... done` の型を覚え、silent モード(先頭 `:`)でエラーを自前ハンドリングし、最後に `shift $((OPTIND - 1))` で位置引数を取り出す——この 3 点を押さえれば実務で十分戦える。 ## 次に読む {#next} - [シェルスクリプトの書き方入門](/articles/tutorials/shell-scripting-basics) - [test コマンド入門 - 条件判定](/articles/tutorials/test-conditionals) - [終了ステータス入門 - $? と && ||](/articles/tutorials/exit-codes-and-status) # Git + Linux の基本操作 - バージョン管理を始めよう Source: https://penguin-gym-linux.com/articles/tutorials/git-linux-basics ## この記事で解決できること {#intro} - Git の **最小限のセットアップ** ができる - `git init` / `add` / `commit` / `push` の **基本フロー** が身につく - Linux 特有の落とし穴(**権限・改行コード・実行ビット**)を回避できる - SSH 鍵認証で **GitHub / GitLab に push** できるところまで到達する ::: tip **結論(最短ルート)** 1. インストール:`sudo apt install git` 2. 初期設定:`git config --global user.name / user.email` 3. リポジトリ:`git init` または `git clone ` 4. 反復:`git status` → `git add` → `git commit -m "..."` 5. 共有:`git remote add` → `git push -u origin main` ::: ::: warning **前提(対象環境)** - Ubuntu / Debian 系 Linux(22.04 以降を想定) - bash または zsh - GitHub / GitLab / 自前 Git サーバを SSH 経由で利用 ::: ## 1. インストールと初期設定 {#install} ### 1-1. インストール ```bash $ sudo apt update $ sudo apt install -y git $ git --version ``` ```output git version 2.43.0 ``` ::: tip RHEL / Fedora 系は `sudo dnf install git`、macOS は `brew install git`。 ::: ### 1-2. グローバル設定(最初に 1 度だけ) ```bash $ git config --global user.name "Your Name" $ git config --global user.email "you@example.com" $ git config --global init.defaultBranch main ``` `init.defaultBranch main` を設定すると、以後 `git init` で自動的に `main` ブランチが作られる(GitHub が 2020 年以降の標準として採用)。 ### 1-3. 設定の確認 ```bash $ git config --list --global ``` ::: tip `--global` は `~/.gitconfig` に書き込まれる。リポジトリごとに上書きしたい場合は、当該リポジトリ内で `--global` を外して実行する(`.git/config` に保存)。 ::: ## 2. リポジトリを用意する:init / clone {#init} ### 2-1. 既存ディレクトリで始める ```bash $ cd ~/projects/myapp $ git init $ ls -la .git ``` `.git/` ディレクトリが作成された時点で「Git 管理下」になる。中身を直接編集する必要は通常ない。 ### 2-2. リモートを clone する ```bash # HTTPS(簡単だが PAT 必要) $ git clone https://github.com/user/repo.git # SSH(鍵を登録しておけば後はノータッチ・推奨) $ git clone git@github.com:user/repo.git $ cd repo ``` ::: warning GitHub の HTTPS 認証は 2021 年以降パスワード認証が廃止され、Personal Access Token (PAT) が必須。日常使いでは SSH 経由の方が摩擦が少ない(§6 で鍵セットアップを解説)。 ::: ## 3. 基本フロー:status → add → commit {#basic-flow} Git の日常作業は **「変更を見る → 含める → スナップショットする」** の繰り返し。 ### 3-1. 状態を確認する ```bash $ git status ``` ```output On branch main Untracked files: (use "git add ..." to include in what will be committed) README.md ``` ### 3-2. 変更をステージへ ```bash $ git add README.md # 単一ファイル $ git add src/ # ディレクトリ単位 $ git add -p # 変更ハンク単位で対話的に選ぶ ``` ::: warning `git add .` は便利だが、`.env` などの **秘密ファイルを誤って含めやすい**。必ず `git status` を確認してから add する習慣を。 ::: ### 3-3. コミット(スナップショット保存) ```bash $ git commit -m "Add README" ``` ```output [main (root-commit) abc1234] Add README 1 file changed, 5 insertions(+) create mode 100644 README.md ``` ::: tip **3 コマンドのメンタルモデル** - `status` = 「今どうなってる?」 - `add` = 「次のコミットに含める」 - `commit` = 「スナップショット確定」 ::: ## 4. 履歴を見る:log / diff {#log-diff} ### 4-1. コミット履歴 ```bash $ git log --oneline --graph --decorate -10 ``` ```output * abc1234 (HEAD -> main) Add README * def5678 Initial commit ``` 実用には次のエイリアスがおすすめ。 ```bash $ git config --global alias.lg "log --oneline --graph --decorate --all -20" $ git lg ``` ### 4-2. 差分を見る ```bash $ git diff # ワーキングツリー vs ステージ $ git diff --staged # ステージ vs HEAD $ git diff HEAD~1 HEAD # 直前コミットと現在 $ git diff main..feature # ブランチ間 ``` ### 4-3. ファイル単位の履歴 ```bash $ git log --follow -p src/index.js ``` `--follow` でリネームを跨いで追跡できる。 ## 5. リモートと連携する:push / pull {#remote} ### 5-1. リモートを追加 ```bash $ git remote add origin git@github.com:user/repo.git $ git remote -v ``` ```output origin git@github.com:user/repo.git (fetch) origin git@github.com:user/repo.git (push) ``` ### 5-2. push(初回) ```bash $ git push -u origin main ``` `-u`(`--set-upstream`)を付けると、以後の `git push` / `git pull` で remote / branch 指定を省略できる。 ### 5-3. pull / fetch ```bash $ git pull # fetch + merge を自動実行 $ git fetch # 取得だけ $ git log HEAD..origin/main # 自分より進んでいる差分を確認 ``` ::: warning **チーム作業での pull 事故** - `git pull` は merge を **自動実行** するため、ローカル変更との衝突が起きやすい。 - 不安なときは `git fetch` → `git log HEAD..origin/main` → `git merge` の 3 段階に分けると安全。 - リベース運用なら `git config --global pull.rebase true` を検討。 ::: ## 6. SSH 鍵認証のセットアップ {#ssh} ### 6-1. 鍵ペアの生成 ```bash $ ssh-keygen -t ed25519 -C "you@example.com" $ cat ~/.ssh/id_ed25519.pub ``` `ed25519` は軽量・高速で、現在の標準アルゴリズム。古い環境向けに RSA を使う場合は `-t rsa -b 4096`。 ::: warning パスフレーズは **必ず設定する**(漏洩時の被害を限定するため)。`ssh-agent` にロードしておけば毎回入力する手間は省ける。 ::: ### 6-2. GitHub / GitLab に公開鍵を登録 `~/.ssh/id_ed25519.pub`(**末尾 `.pub` の方**)の内容をコピーし、GitHub の Settings → SSH and GPG keys から追加する。秘密鍵 `id_ed25519` は絶対に公開しない。 ### 6-3. 接続テスト ```bash $ ssh -T git@github.com ``` ```output Hi user! You've successfully authenticated, but GitHub does not provide shell access. ``` このメッセージが出れば成功(exit code は 1 だが正常)。 ## 7. .gitignore:Linux 特有の除外設定 {#gitignore} ```gitignore # OS / エディタ .DS_Store *~ .*.swp .idea/ .vscode/ # 言語ランタイム node_modules/ __pycache__/ *.pyc target/ build/ dist/ # 機密情報(最重要) .env .env.* *.key *.pem # ログ *.log logs/ ``` ::: tip 迷ったら GitHub 公式の [github/gitignore](https://github.com/github/gitignore) リポジトリに言語別テンプレートが揃っている。 ::: ::: danger **追跡済みファイルは .gitignore では除外されない** すでにコミット済みのファイルは、後から `.gitignore` に書いても無視されない(add 時点で追跡開始されているため)。 ```bash $ git rm --cached .env $ echo ".env" >> .gitignore $ git commit -m "Untrack .env" ``` ただし **過去のコミット履歴には残る**。秘密情報が漏れた場合は `git filter-repo` 等で履歴改変+鍵ローテーションが必要。 ::: ## 8. Linux 特有のトラブル {#troubles} ### 8-1. Permission denied(push 時) ```output ERROR: Permission to user/repo.git denied to other-user. fatal: Could not read from remote repository. ``` 原因候補: - SSH 鍵が GitHub に登録されていない - 複数アカウントの鍵が混在し、別アカウントの鍵が先にロードされている - リポジトリへの書き込み権限が無い(コラボレーターでない) 切り分け: ```bash $ ssh -T git@github.com # どのユーザーで認証されているか確認 $ ssh-add -l # ssh-agent にロード済みの鍵を表示 ``` 複数鍵を使い分ける場合は `~/.ssh/config` でホスト別に指定する。 ### 8-2. 改行コードの混入(CRLF / LF) Windows 由来のファイルが混ざると、**プロジェクト全体の diff が壊れる**(全行変更扱いになる)。 ```bash # Linux / macOS:改行を変換せず保存(推奨) $ git config --global core.autocrlf input ``` リポジトリ単位で固定する場合は `.gitattributes` を作成: ``` * text=auto eol=lf *.sh text eol=lf *.bat text eol=crlf ``` ### 8-3. 実行ビット(chmod +x)が記録されない ```bash $ chmod +x scripts/deploy.sh $ git status # 変更が出ない場合がある ``` 明示的に mode 変更を記録する: ```bash $ git update-index --chmod=+x scripts/deploy.sh $ git diff ``` ```output old mode 100644 new mode 100755 ``` ::: tip `core.fileMode = false` がリポジトリに設定されていると mode 変更が無視される。`git config core.fileMode` で確認可。 ::: ### 8-4. 大文字小文字違いのファイル名 Linux のファイルシステム(ext4 等)は **case-sensitive** だが、macOS / Windows は **case-insensitive** がデフォルト。`Readme.md` と `README.md` を取り違えると、Linux ベースの CI で破綻する。 ```bash # リポジトリ全体で case 違いを検出 $ git config --global core.ignorecase false ``` 命名規約を `CONTRIBUTING.md` に明記し、レビューで止めるのが現実的。 ## 9. 安全テンプレート集 {#templates} ::: tip **初回セットアップ** ```bash git init echo "node_modules/" > .gitignore git add README.md .gitignore git commit -m "Initial commit" git remote add origin git@github.com:user/repo.git git push -u origin main ``` ::: ::: tip **日常作業ループ** ```bash git status git diff git add -p # 変更ハンク単位で確認しながら add git commit -m "feat: 機能の説明" git push ``` ::: ::: tip **取り消し操作(破壊度が低い順)** ```bash git restore # ワーキングツリーの変更を破棄 git restore --staged # add を取り消し(ファイル本体は残る) git commit --amend # 直前コミットを修正(push 前のみ) git revert # 取り消しコミットを新規作成(共有後でも安全) ``` ::: ::: warning **やってはいけないこと** - 共有済みコミットへの `git push --force`(チーム全員の履歴が壊れる) - 確認なしの `git reset --hard`(コミット前の作業が消える) - `.env` / 秘密鍵 / 認証トークンのコミット(履歴に残ると除去が困難) - `--no-verify` での hook スキップ(lint / テスト失敗を握りつぶす) ::: ## 次に読む {#next} - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) - [SSH で接続できないときのチェックリスト](/articles/troubleshooting/ssh-troubleshooting) - [Vim 入門 - 基本操作と実践テクニック](/articles/tutorials/vim-basics) # グロブ(ワイルドカード)入門 - * ? [] と extglob の使い分け Source: https://penguin-gym-linux.com/articles/tutorials/globbing-wildcards ## この記事で学べること {#intro} - **グロブ(ワイルドカード)** が「シェルの仕事」だと理解できます - `*` `?` `[]` の **3 つの基本記号** を使い分けられます - `{}`(ブレース展開)との違いがわかります - `extglob`・隠しファイル・無マッチ時の挙動まで **落とし穴を避けられます** ::: tip **言葉の整理(先に取りちがえを防ぎます)** - **グロブ**:ファイル名をまとめて指すための書き方です。「ワイルドカード」「パターン」も、ほぼ同じものを指す呼び方です。この記事では「グロブ」にそろえます。 - **正規表現**:文字の中身をさがすための書き方です。`grep` などで使います。**グロブとは別のしくみです。** 記号が似ているので取りちがえやすい点に気をつけてください。 - **展開**:あなたが書いた記号を、シェルがほんもののファイル名に置きかえることです。 - **隠しファイル**:名前が `.` で始まるファイルです。`.bashrc` が代表例です。 `*` はグロブでは「何文字でも」という意味です。正規表現では「すぐ前の文字のくりかえし」という意味になります。同じ記号でも意味がちがいます。 ::: ::: tip **結論(先に覚えるべき型)** - どんな文字が何個でも → `*`(例: `*.txt`) - ちょうど1文字 → `?`(例: `file?.log`) - 限られた候補から1文字 → `[]`(例: `img[0-9].png`) - 展開するのは **コマンドではなくシェル** ::: ## 1. グロブとは何か? {#what} > **結論**: グロブはシェルがコマンド実行前にワイルドカードをファイル名へ展開する仕組み。コマンドは展開後の結果を受け取る。 ::: dialogue @lina: 先輩、`ls *.txt` と書くと `.txt` のファイルだけ出ますよね。あの `*` はどういう意味なのでしょうか。 @linny: いい質問だね。`*` は「どんな文字でも、何個でもよい」という意味の記号だよ。 @linny: そして大事なのは、**この `*` を処理しているのが `ls` ではなくシェル** だということ。 @lina: えっ、`ls` ではないのですか。 @linny: そう。Enter を押した瞬間、シェルが `*.txt` を見る。そして **実際に存在するファイル名に書きかえてから** `ls` に渡すんだ。これをグロブ展開と呼ぶよ。 ::: ::: highlight **展開のイメージ** カレントディレクトリに `a.txt` `b.txt` `memo.md` がある場合: ``` あなたが入力: ls *.txt シェルが展開: ls a.txt b.txt ← memo.md は外れる ls が受け取る: a.txt b.txt ``` `ls` は `*` を一切知らない。展開済みのファイル名を受け取るだけ。 ::: ::: tip グロブはファイル名のためのしくみです。文字の中身をさがす正規表現とは **別のもの** です。`grep` で使う `.*` などが正規表現にあたります。取りちがえないでください。 ::: ::: warning **先に、事故をふせぐ話** グロブは `rm` と組み合わせると強力です。同時にあぶなくもあります。`rm` で消したファイルは、GUI とちがってゴミ箱に行きません。その場ですぐ完全に消えます。 そこで、`rm` の前に同じパターンを `ls` で確かめるくせをつけてください。 ```bash $ ls *.tmp # まず何が対象になるかを目で見て確かめる $ rm *.tmp # 納得してから消す ``` 本サイトの仮想ターミナルは学習用です。何を打っても、あなたのパソコンのファイルは消えません。安心して試してください。 ::: ## 2. `*` アスタリスク:0文字以上の任意の文字列 {#asterisk} > **結論**: `*` は0文字以上の任意の文字列にマッチする。ただし先頭のドットとスラッシュにはマッチしない。 ```bash $ ls ``` ```output a.txt b.txt data.csv report.txt notes.md ``` ```bash # 拡張子が .txt のものだけ $ ls *.txt ``` ```output a.txt b.txt report.txt ``` ::: dialogue @lina: `*.txt` は「何か + .txt」ということですね。 @linny: そのとおり。`*` は **0 文字でもマッチする** のもポイントだよ。だから `*` だけ書くと「全部」になる。 @lina: では `re*` はどうなりますか。 @linny: 「re で始まるもの」だね。`report.txt` がマッチする。`*` は前でも後ろでも真ん中でも置けるよ。 ::: ```bash # re で始まる $ ls re* report.txt # data を含む(前後に何かあってもOK) $ ls *data* ``` ```output data.csv ``` ::: warning `*` は **先頭のドットにマッチしません。** つまり隠しファイルは対象外です。 `ls *` を実行しても `.bashrc` のような隠しファイルは出てきません。これは `rm *` で設定ファイルまで巻きこんで消す事故をふせぐための決まりです。 ::: ## 3. `?` クエスチョンマーク:任意の1文字 {#question} > **結論**: `?` はちょうど1文字にマッチする。文字数が決まっているファイルを狙うときに使う。 ```bash $ ls ``` ```output log1.txt log10.txt log2.txt log3.txt ``` ```bash # log + 1文字 + .txt(log10.txt は2文字なので外れる) $ ls log?.txt ``` ```output log1.txt log2.txt log3.txt ``` ::: dialogue @lina: `?` は 1 文字だけなんですね。`log10.txt` が外れたのはなぜでしょうか。 @linny: `log?.txt` は「log のあとに **きっかり 1 文字**、そして .txt」という意味だからだよ。 @linny: `log10.txt` は `1` と `0` で 2 文字ある。だからマッチしないんだ。 @lina: 2 文字ぶん指定したいときはどうしますか。 @linny: `??` と 2 個並べればいい。`log??.txt` なら `log10.txt` がマッチするよ。 ::: ```bash # 2文字ぶん $ ls log??.txt ``` ```output log10.txt ``` ## 4. `[]` 角カッコ:候補の中から1文字 {#bracket} > **結論**: `[]` は角カッコ内に並べた文字のどれか1文字にマッチする。範囲指定や否定もできる。 ```bash $ ls ``` ```output img0.png img1.png img2.png img9.png imgA.png ``` ### 4-1. 候補を列挙する ```bash # 0 か 1 か 2 のどれか $ ls img[012].png ``` ```output img0.png img1.png img2.png ``` ### 4-2. 範囲で指定する ```bash # 0〜9 の数字1文字 $ ls img[0-9].png ``` ```output img0.png img1.png img2.png img9.png ``` ::: dialogue @lina: `[0-9]` で「0 から 9 まで」なんですね。アルファベットも指定できますか。 @linny: できるよ。`[a-z]` で小文字、`[A-Z]` で大文字だね。`[a-zA-Z0-9]` のように **組み合わせる** こともできる。 @linny: ただし「マッチするのは 1 文字だけ」という点は忘れないでね。 ::: ### 4-3. 否定する(`!` または `^`) ```bash # 数字「以外」の1文字 $ ls img[!0-9].png ``` ```output imgA.png ``` ::: highlight **`[]` の書き方まとめ** | 書き方 | 意味 | | ---------- | ------------------------- | | `[abc]` | a / b / c のどれか1文字 | | `[a-z]` | a〜z の1文字(範囲) | | `[0-9]` | 0〜9 の1文字(範囲) | | `[!0-9]` | 数字以外の1文字(否定) | | `[a-zA-Z]` | 英字1文字(範囲の組合せ) | ::: ## 5. `{}` ブレース展開:グロブと混同しやすい別物 {#brace} > **結論**: ブレース展開はファイルの有無に関係なく文字列を生成する。実在ファイルに展開するグロブとは別の仕組み。 ```bash # ブレース展開:3つの文字列を生成する $ echo file{1,2,3}.txt ``` ```output file1.txt file2.txt file3.txt ``` ::: dialogue @lina: あれ、これはグロブと同じではないのですか。 @linny: 見た目は似ているけれど **まったく別物** なんだ。`{}` は文字列を作るだけで、**ファイルが実在するかどうかは見ていない**。 @linny: だから存在しないファイル名でも生成される。 @lina: グロブのほうは、実在するファイルにしか展開されないんですね。 @linny: そう。`{}` は「これからまとめて作る」用途。`*` や `[]` は「すでにあるものを選ぶ」用途だと覚えるといいよ。 ::: ```bash # 連番ディレクトリをまとめて作る(実在しなくてOK) $ mkdir log{2024,2025,2026} # 連番の範囲も書ける $ echo {1..5} ``` ```output 1 2 3 4 5 ``` ::: warning `*` や `[]` は、**あてはまるファイルが 1 つも無いとき** はパターンがそのまま文字として残ります。これが bash のはじめの設定での動きです。 いっぽう `{}` はいつでも展開されます。動きがちがうので気をつけてください。 ::: ## 6. extglob:拡張グロブでもっと柔軟に {#extglob} > **結論**: extglob を有効にすると「いずれか」「除外」などの高度なパターンが書ける。既定では無効なので shopt で有効化する。 ```bash # 拡張グロブを有効化(bash) $ shopt -s extglob ``` ::: dialogue @lina: 基本記号はわかりました。「.jpg と .png だけ」のような複数条件は書けますか。 @linny: 標準のグロブだと少し苦しいね。でも **extglob(拡張グロブ)** を有効にすると一気に楽になるよ。 @linny: `@(...)` で「いずれか」、`!(...)` で「除外」が書けるんだ。 ::: ::: highlight **extglob の記法** | 書き方 | 意味 | | ------------ | ----------------------- | | `?(pattern)` | 0回か1回 | | `*(pattern)` | 0回以上 | | `+(pattern)` | 1回以上 | | `@(pattern)` | ちょうど1回(いずれか) | | `!(pattern)` | pattern 以外 | `pattern` の部分は `|` で区切って複数指定できます。 ::: ```bash # .jpg または .png にマッチ $ ls *.@(jpg|png) # .txt 以外のすべて $ ls !(*.txt) ``` ::: tip `shopt -s extglob` は、そのシェルだけの設定です。ターミナルを閉じるともとに戻ります。 今すぐもとに戻したいときは `shopt -u extglob` を実行します。 いつでも使いたいときは `~/.bashrc` に書き足します。設定ファイルを触るので、先にコピーを取ってください。 ```bash $ cp ~/.bashrc ~/.bashrc.bak $ echo 'shopt -s extglob' >> ~/.bashrc $ source ~/.bashrc ``` おかしくなったら `cp ~/.bashrc.bak ~/.bashrc` で戻せます。`>>` は末尾に書き足す記号です。`>` は中身を全部消すので使いません。 ::: ## 7. よくある初心者のつまずき {#pitfalls} > **結論**: 無マッチ時の挙動・隠しファイル・クォートの3点が定番の落とし穴。仕様を知れば事故は防げる。 ### 7-1. リナの失敗:マッチしないとパターンがそのまま残る ```bash # .xml ファイルが1つも無い場合 $ ls *.xml ``` ```output ls: '*.xml' にアクセスできません: そのようなファイルやディレクトリはありません ``` ::: dialogue @lina: エラーメッセージに `*.xml` がそのまま出ています。`*` が効いていないのでしょうか。 @linny: 効いていないのではなく、**展開しなかった** んだ。 @lina: えっ、展開しないという選択肢があるんですか。 @linny: そう。bash のはじめの設定では、あてはまるものが 0 件のときはパターンをそのまま文字として渡す。だから `ls` は「`*.xml` という名前のファイルが無い」と答えたんだ。 @lina: エラーの読み方が変わりました。0 件だったという合図なんですね。よくわかりました。 @linny: そのとおり。`shopt -s nullglob` を使えば、0 件のときは何も渡さない動きに変えられるよ。 ::: ### 7-2. 隠しファイルは `*` で拾えない ```bash # 隠しファイルも対象にしたいとき $ shopt -s dotglob $ ls * ``` `dotglob` を有効にすると、`*` が隠しファイルにもあてはまります。ふだんは無効のままにしておくほうが安全です。 ### 7-3. クォートするとグロブは無効になる ```bash # クォートすると * は展開されない $ ls "*.txt" ``` ```output ls: '*.txt' にアクセスできません: そのようなファイルやディレクトリはありません ``` ::: warning `"*.txt"` や `'*.txt'` のように **クォートで囲むと、グロブ展開は起きません。** `*` を文字そのものとして使いたいときには便利です。ただし展開したいのに囲ってしまうと、思わぬところで無効になります。 ::: ## 8. ミニ課題:実際にやってみよう {#exercise} > **結論**: 拡張子の抽出・文字数指定・範囲指定の3問で、グロブの基本記号を手を動かして確かめる。 ::: dialogue @lina: 知識は入りました。手を動かして試したいです。 @linny: いいね、3 問用意したよ。まず練習用ファイルを作ってから挑戦してみて。 ::: 練習用のからっぽのディレクトリを作り、その中で試します。こうすれば、もとからあるファイルにはふれません。 ```bash # 練習用ディレクトリとファイルを準備 $ mkdir -p ~/glob-practice && cd ~/glob-practice $ touch a.txt b.txt c.log data1.csv data2.csv data10.csv ``` **課題 1**: `.csv` で終わるファイルだけを一覧表示しよう。 :::details ヒント 1(方向づけ)を見る 「名前のはじめは何でもよい。ただし終わりは `.csv`」と伝えます。はじめの部分にあたる記号を 1 つ使います。 ::: :::details ヒント 2(コマンド名)を見る 一覧を出すのは `ls` です。「何文字でもよい」を表す記号は `*` です。 ::: :::details 答えを見る ```bash $ ls *.csv ``` ```output data1.csv data10.csv data2.csv ``` `.csv` で終わる 3 つが出ます。`a.txt` や `c.log` は外れます。 なお `ls` は名前を文字として並べます。そのため `data10.csv` が `data2.csv` より先に出ます。 ::: **課題 2**: `data` + 1 文字 + `.csv` のファイルだけを表示しよう。`data10.csv` は除きます。 :::details ヒント 1(方向づけ)を見る 今度は文字の数を決めます。「何文字でもよい」ではなく「ちょうど 1 文字」を表す記号を使います。 ::: :::details ヒント 2(コマンド名)を見る 一覧を出すのは `ls` です。ちょうど 1 文字を表す記号は `?` です。 ::: :::details 答えを見る ```bash $ ls data?.csv ``` ```output data1.csv data2.csv ``` `data10.csv` は `1` と `0` で 2 文字あるので外れます。 ::: **課題 3**: `data1.csv` と `data2.csv` だけを、角カッコを使って表示しよう。 :::details ヒント 1(方向づけ)を見る えらびたい文字を自分で並べます。「この中のどれか 1 文字」を表す書き方を使います。 ::: :::details ヒント 2(コマンド名)を見る 一覧を出すのは `ls` です。文字を並べるのは `[]` です。中に `1` と `2` を書きます。 ::: :::details 答えを見る ```bash $ ls data[12].csv ``` ```output data1.csv data2.csv ``` `[12]` は「1 か 2 のどちらか 1 文字」という意味です。`data10.csv` は外れます。 練習が終わったら、作ったディレクトリごと片づけられます。 `-r` は「ディレクトリの中身ごと消す」というオプションです。`-i` を足すと、1 つずつ確認しながら消せます。まず `ls` で中身を目で見てから消してください。 ```bash $ cd ~ $ ls ~/glob-practice $ rm -ri ~/glob-practice ``` ::: ## 9. 振り返り {#review} ::: dialogue @lina: 整理します。グロブを展開しているのはコマンドではなくシェルなんですね。 @linny: そのとおり。だからコマンドは、展開されたあとのファイル名を受け取るだけなんだ。 @lina: `*` は何文字でも、`?` はちょうど 1 文字、`[]` は並べた中から 1 文字。ここまで覚えました。 @linny: 完璧だね。`rm` と組み合わせるときは、先に同じパターンを `ls` で確かめること。それだけは忘れないで。 ::: ## 今日の 3 行まとめ {#three-lines} 1. グロブを展開しているのはコマンドではなくシェル。コマンドは展開されたあとの名前を受け取る 2. `*` は 0 文字以上、`?` はちょうど 1 文字、`[]` は並べた中から 1 文字にあてはまる 3. `rm` と組み合わせる前に、同じパターンを `ls` で確かめる ## 次に読む {#next} - [pwd・cd・lsの使い方](/articles/tutorials/basic-commands) - [cp・mv・rmの使い方](/articles/tutorials/file-operations-basics) - [find・grep・awkの使い方入門](/articles/tutorials/find-grep-awk-basics) - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [仮想ターミナルでコマンドを練習する](/terminal) # GNU parallel 入門 - コマンドを並列実行して高速化する Source: https://penguin-gym-linux.com/articles/tutorials/gnu-parallel ## この記事で解決できること {#intro} - 複数コマンドを **CPU コアぶん同時に走らせて** 処理時間を短縮できる - `xargs -P` で困る **出力の混線・順序崩れ** を回避できる - `--joblog` と `--resume` で **中断した残りのジョブを続きから実行** できる ::: tip **結論(使いどころ)** - 1 件あたり重い処理を大量に回す(画像変換・ダウンロード・テスト)→ `parallel` - 出力を混ぜたくない / 入力順を保ちたい → `-k` - 中断した処理を続きから再開したい → `--joblog` + `--resume` ::: ::: warning **前提(対象環境)** - OS: Ubuntu / Debian 系を想定(他ディストリも考え方は同じ) - GNU parallel(Ole Tange 作)が対象。`moreutils` 同梱の別物 `parallel` とは互換性がない ::: ## GNU parallel とは何か? {#what} > **結論**: 標準入力やコマンド引数のリストを受け取り、各要素に対してコマンドを並列に実行するツール。既定では CPU コア数ぶんのジョブを同時に走らせる。 GNU parallel は、`xargs` の「リストを受け取ってコマンドを組み立てる」発想を、**並列実行と出力制御に特化させた**コマンドである。基本形は次の 2 通り。 ```bash # 1) 引数を ::: の後ろに並べる parallel echo ::: a b c # 2) 標準入力から渡す seq 1 3 | parallel echo ``` ```output a b c ``` `a` `b` `c` のそれぞれに対して `echo` が**別プロセスとして同時に**起動する。既定の同時実行数は CPU コア数なので、コア数より多い入力は順にスロットへ流し込まれる。 ::: tip 入力 1 件ごとにコマンドが 1 回起動する(`xargs` の既定とは逆)。1 回の起動で複数引数をまとめたい場合は後述の `-N` を使う。 ::: ## インストールと最初の一歩はどうするのか? {#install} > **結論**: `apt install parallel` で導入。初回は学術引用の通知が出るので、`parallel --citation` を一度実行して了承を記録しておく。 ```bash sudo apt update sudo apt install parallel parallel --version ``` GNU parallel は学術論文での引用を求めるツールで、初回実行時に引用のお願いが表示される。CI やスクリプトで邪魔になる場合は、一度だけ次を実行して了承を記録する(`~/.parallel/will-cite` が作られ、以降は通知が消える)。 ```bash parallel --citation ``` ::: warning ディストリによっては `moreutils` パッケージが別物の `parallel` を提供する。`parallel --version` で先頭行に `GNU parallel` と表示されるかを必ず確認する。表示されなければ GNU 版ではない。 ::: ## なぜ xargs ではなく parallel なのか? {#vs-xargs} > **結論**: `xargs -P` も並列実行できるが、出力が行単位で混ざる。parallel は出力をジョブ単位でまとめ、`-k` で入力順も保てる。 `xargs -P 4` は手軽だが、複数ジョブの標準出力が**1 行ずつ交互に混線**しやすい。parallel は各ジョブの出力を内部でバッファし、**ジョブが終わった単位でまとめて**出力するため混ざらない。 | 観点 | `xargs -P` | `parallel` | | ------------------ | ---------- | --------------------- | | 出力の混線 | 起きやすい | ジョブ単位で防ぐ | | 入力順の出力 | 保証なし | `-k` で保証 | | 置換文字列の柔軟さ | `{}` のみ | `{}` `{.}` `{/}` 等 | | 実行ログ / 再開 | なし | `--joblog` `--resume` | | 進捗表示 | なし | `--bar` `--eta` | ::: tip 速度だけなら `xargs -P` で十分なことも多い。**出力を壊したくない・再実行したい**ときに parallel の価値が出る。 ::: ## 置換文字列(プレースホルダ)はどう使うのか? {#placeholders} > **結論**: `{}` が入力そのもの。`{.}` は拡張子除去、`{/}` はファイル名、`{//}` はディレクトリ、`{#}` はジョブ番号。出力名の組み立てに多用する。 入力をコマンドのどこに差し込むかは置換文字列で指定する。省略すると末尾に `{}` が補われる。 ```bash # *.wav を同名 .mp3 に変換({.} で拡張子を除去) parallel ffmpeg -i {} {.}.mp3 ::: *.wav ``` 主要な置換文字列は次のとおり。 | 記法 | 意味 | 例(入力 `dir/file.txt`) | | ------ | ------------------------ | ------------------------- | | `{}` | 入力そのもの | `dir/file.txt` | | `{.}` | 拡張子を除去 | `dir/file` | | `{/}` | ディレクトリを除いた名前 | `file.txt` | | `{//}` | ディレクトリ部分 | `dir` | | `{/.}` | 名前から拡張子も除去 | `file` | | `{#}` | ジョブ通し番号 | `1`, `2`, ... | | `{%}` | ジョブスロット番号 | `1`〜(同時実行数の範囲) | ```bash # 入力ごとにジョブ番号を添えて出力先を作る parallel 'echo job {#}: {}' ::: alpha beta gamma ``` ```output job 1: alpha job 2: beta job 3: gamma ``` ## ジョブ数と出力順はどう制御するのか? {#control} > **結論**: 同時実行数は `-j` で指定。`-j0` は可能な限り多く、`-j 200%` はコア数の 2 倍。出力を入力順に揃えるなら `-k` を付ける。 ```bash # 同時 4 ジョブ parallel -j 4 ./convert.sh ::: *.dat # CPU コア数の 2 倍(I/O 待ちが多い処理向け) parallel -j 200% curl -O ::: "${urls[@]}" # 同時実行数の上限なし(注意して使う) parallel -j0 echo ::: {1..100} ``` 並列実行すると終わった順に出力されるため、入力と出力の対応が崩れる。**入力順で出力したい**ときは `-k`(`--keep-order`)を付ける。 ```bash seq 1 5 | parallel -k 'sleep $((RANDOM % 3)); echo {}' ``` ```output 1 2 3 4 5 ``` ::: tip 本番投入前は `--dry-run` を付けると、実際に走らせるコマンド列だけを表示できる。置換文字列の展開ミスをここで潰す。 ::: ## 複数の入力を組み合わせるには? {#multiple-inputs} > **結論**: `:::` を複数並べると全組み合わせ(直積)。`--link` で同じ位置どうしを対にする。1 行に複数列があるファイルは `--colsep` で `{1}` `{2}` として使う。 ```bash # 直積: a-1 a-2 b-1 b-2 c-1 c-2 の 6 ジョブ parallel echo ::: a b c ::: 1 2 ``` ```bash # --link: a-1 b-2 c-3 のように位置で対応づけ parallel --link echo ::: a b c ::: 1 2 3 ``` ファイルを入力にするなら `::::`(または `-a`)。CSV のように列を分けたい場合は `--colsep`。 ```bash # hosts.txt の各行を引数にする parallel ping -c1 {} :::: hosts.txt # "user,host" 形式を列分割して使う parallel --colsep ',' ssh {2} -l {1} uptime :::: targets.csv ``` ## 進捗・ログ・失敗時の再実行はどうするのか? {#joblog} > **結論**: `--bar` で進捗バー、`--joblog` で各ジョブの結果を記録。`--resume` を併用すると、未完了ジョブだけを次回に引き継げる。 ```bash # 進捗バーを表示 parallel --bar ./task.sh ::: {1..50} # 実行ログを記録(終了コード・所要時間が残る) parallel --joblog run.log ./task.sh ::: *.dat ``` `--joblog` で残したログがあれば、`--resume` で**すでに成功したジョブを飛ばして**続きから実行できる。失敗したジョブだけやり直すなら `--resume-failed`。 ```bash # 中断・失敗後、同じコマンドに --resume を足して再実行 parallel --joblog run.log --resume ./task.sh ::: *.dat ``` エラーで早めに止めたいときは `--halt`。 ```bash # 1 つでも失敗したら走行中のジョブを終えて停止 parallel --halt now,fail=1 ./task.sh ::: *.dat ``` ::: warning `--resume` は **同じ `--joblog` ファイルと同じコマンド** が前提。コマンドや入力を変えると正しく再開できない。 ::: ## 標準入力を分割する --pipe とは? {#pipe} > **結論**: `--pipe` は引数ではなく標準入力そのものをブロックに分割し、各ブロックを並列のコマンドへ流す。巨大ログの集計などに向く。 これまでは「引数リストを並列化」していたが、`--pipe` は**1 本の入力ストリームを分割**して並列処理する。 ```bash # 巨大ファイルを 10MB ずつに分け、各ブロックを並列で grep cat huge.log | parallel --pipe --block 10M grep ERROR ``` `--block` で 1 ブロックのサイズを指定する。行の途中で切れないよう、parallel は改行境界で分割する。 ## 実務でよく使うレシピ集 {#recipes} > **結論**: 画像変換・一括ダウンロード・複数ホストへのコマンド実行が定番。`--dry-run` で確認してから本番投入するのが安全。 ::: highlight **コピペ用テンプレ** ```bash # 画像を一括リサイズ(出力名は {.}_small.jpg) parallel convert {} -resize 50% {.}_small.jpg ::: *.jpg # URL 一覧を 8 並列でダウンロード parallel -j8 wget -q ::: $(cat urls.txt) # 複数ホストへ同じコマンド(出力はホスト単位でまとまる) parallel -k --tag ssh {} 'uptime' :::: hosts.txt # まず確認 → 問題なければ --dry-run を外す parallel --dry-run ./batch.sh {} ::: input/* ``` ::: `--tag` を付けると各出力行の先頭に入力(ホスト名など)が付き、どのジョブの結果かを追いやすくなる。 ## 次に読む {#next} - [xargs の実践的な使い方と parallel との使い分け](/articles/tutorials/xargs-practical) - [taskset でプロセスを CPU コアに固定する](/articles/tutorials/taskset-cpu-affinity) - [pv でパイプの進捗を可視化する](/articles/tutorials/pv-progress-viewer) # GPG入門 - ファイルの暗号化・復号と署名の基本 Source: https://penguin-gym-linux.com/articles/tutorials/gpg-basics ## この記事で解決できること {#intro} - `gpg` でファイルを **暗号化・復号** できるようになる - **公開鍵方式** と **パスワード方式**(共通鍵)の **使い分け** が分かる - **署名** でファイルの改ざん・なりすましを **検証** できる ::: tip **結論(使い分けの型)** - **自分だけ / 相手に渡すパスワードで** 守る → `gpg -c`(パスワード方式) - **相手の公開鍵で** 渡す → `gpg -e -r <相手のID>`(公開鍵方式) - **改ざんされていないことを示す** → `gpg -b`(分離署名) ::: ::: warning **前提(対象環境)** - OS:Ubuntu / Debian 系(`apt install gnupg`)。多くのディストリで標準同梱 - コマンド名は `gpg`(GnuPG 2.x 系を想定) ::: ## GPG とは何か? {#what} > **結論**: GPG は GnuPG の実装で、公開鍵暗号と署名を扱う標準ツール。暗号化で「読めなくし」、署名で「改ざんを検出」する。 GPG(GnuPG, GNU Privacy Guard)は OpenPGP 標準(RFC 4880)を実装した暗号化ツール。やれることは大きく 2 つ。 - **暗号化 / 復号**:第三者に中身を読ませない - **署名 / 検証**:誰が作り、改ざんされていないかを保証する 暗号化には 2 方式がある。**公開鍵方式**(相手の公開鍵で暗号化し、相手の秘密鍵でしか復号できない)と、**パスワード方式**(共通鍵 / 対称暗号。同じパスワードで暗号化・復号する)。鍵の準備が要らないパスワード方式から見ていく。 ## パスワードだけで暗号化するには? {#symmetric} > **結論**: 鍵ペア不要で最も手軽なのが `gpg -c`。入力したパスフレーズで暗号化し、同じパスフレーズで復号する対称暗号。 鍵の作成すら不要。`-c`(`--symmetric`)でパスフレーズを使った暗号化ができる。 ```bash $ gpg -c secret.txt ``` 実行するとパスフレーズを聞かれ、`secret.txt.gpg`(バイナリ)が生成される。元ファイルは残るので、不要なら別途削除する。 復号は `-d`(`--decrypt`)。 ```bash $ gpg -d secret.txt.gpg ``` 標準出力に中身が表示される。ファイルへ書き出すなら `-o` で出力先を指定する。 ```bash $ gpg -o secret.txt -d secret.txt.gpg ``` ::: tip メールやチャットに貼れるテキスト形式が欲しいときは `-a`(`--armor`)を付ける。`secret.txt.asc` という ASCII 形式(Base64)になる。 ```bash $ gpg -c -a secret.txt ``` ::: ::: warning パスワード方式は **パスフレーズを安全に相手へ渡せること** が前提。チャットに暗号文とパスワードを並べて送るのは無意味。鍵交換が難しいなら次の公開鍵方式を使う。 ::: ## 公開鍵方式を使うには? {#keypair} > **結論**: まず鍵ペアを作る。`gpg --full-generate-key` で秘密鍵(自分用)と公開鍵(配布用)が生成される。 公開鍵方式では「暗号化に使う公開鍵」と「復号に使う秘密鍵」のペアを最初に作る。 ```bash $ gpg --full-generate-key ``` 対話形式で鍵の種類・長さ・有効期限・名前・メールアドレスを聞かれる。迷ったら既定値(RSA、3072 bit 以上)で問題ない。最後にパスフレーズを設定する。これは **秘密鍵を保護するためのもの** で、暗号化のパスワードとは役割が異なる。 作成済みの鍵は次で確認する。 ```bash $ gpg --list-keys # 公開鍵の一覧 $ gpg --list-secret-keys # 秘密鍵の一覧 ``` 出力に表示される長い 16 進の文字列が **鍵 ID / フィンガープリント**。以降、相手を指定するときに使う。 ## 公開鍵を相手に渡すには? {#exchange} > **結論**: `gpg --export -a` で公開鍵をテキスト出力し相手へ渡す。受け取った側は `gpg --import` で取り込む。 暗号化してもらうには、自分の **公開鍵** を相手に渡す必要がある。`--export` に `-a` を付けてテキスト化する。 ```bash $ gpg --export -a "you@example.com" > my-pubkey.asc ``` 相手はこのファイルを取り込む。 ```bash $ gpg --import my-pubkey.asc ``` ::: danger **秘密鍵(`--export-secret-keys`)は絶対に共有しない。** 渡すのは常に公開鍵(`--export`)。秘密鍵が漏れると、暗号文をすべて復号され、なりすまし署名も可能になる。 ::: ## 公開鍵でファイルを暗号化・復号するには? {#encrypt} > **結論**: `gpg -e -r <相手のID>` で相手の公開鍵を使って暗号化する。復号できるのはその相手だけ。 相手の公開鍵を取り込んだら、`-e`(`--encrypt`)と `-r`(`--recipient`)で宛先を指定して暗号化する。 ```bash $ gpg -e -r "friend@example.com" report.pdf ``` `report.pdf.gpg` が生成される。これは **friend@example.com の秘密鍵を持つ人だけ** が復号できる。受け取った相手は次で復号する。 ```bash $ gpg -d report.pdf.gpg > report.pdf ``` 自分でも後から中身を確認したい場合は、宛先に自分も追加しておく(`-r` は複数指定できる)。 ```bash $ gpg -e -r "friend@example.com" -r "you@example.com" report.pdf ``` ::: tip テキストで渡したいときは `-a` を併用する。 ```bash $ gpg -e -a -r "friend@example.com" message.txt # message.txt.asc ``` ::: ## 署名で改ざんを検証するには? {#sign} > **結論**: 中身を暗号化せず「本物であること」だけ示すなら署名。`gpg -b` で本体と別の署名ファイルを作り、`--verify` で検証する。 署名は暗号化とは別物。**中身は読める** ままで、「確かに本人が作った / 改ざんされていない」ことを保証する。配布ファイルの真正性確認によく使われる。 最も実務的なのが **分離署名**(detached signature)。`-b`(`--detach-sign`)で本体とは別の署名ファイルを作る。 ```bash $ gpg -b -a release.tar.gz ``` `release.tar.gz.asc`(署名)が生成される。本体とこの署名を一緒に配布する。受け取った側は `--verify` で検証する。 ```bash $ gpg --verify release.tar.gz.asc release.tar.gz ``` `Good signature` と出れば、署名者の公開鍵で検証が成立し、ファイルは改ざんされていない。 ::: tip 署名と暗号化は併用できる。`-s -e -r <相手>` で「署名付き暗号化」になる。本文を読めるテキストに署名を埋め込む `--clearsign` も、メール本文などで使われる。 ::: ::: warning `--verify` が `Good signature` でも、その **公開鍵が本当に相手のものか** は別問題。鍵の入手元(公式サイトのフィンガープリント等)を確認すること。検証はあくまで「その鍵で署名された」ことの保証。 ::: ## まとめ:コマンド早見表 {#summary} > **結論**: パスワード方式は `-c`、公開鍵方式は `-e -r`、署名は `-b`。テキスト出力は `-a`、復号は `-d` を覚えれば日常操作はカバーできる。 | 目的 | コマンド | | ------------------ | ---------------------------- | | パスワードで暗号化 | `gpg -c file` | | 公開鍵で暗号化 | `gpg -e -r file` | | 復号 | `gpg -d file.gpg` | | 鍵ペアを作成 | `gpg --full-generate-key` | | 公開鍵を書き出す | `gpg --export -a ` | | 公開鍵を取り込む | `gpg --import key.asc` | | 分離署名を作成 | `gpg -b -a file` | | 署名を検証 | `gpg --verify file.asc file` | - [openssl で証明書・ハッシュ・暗号化を扱う](/articles/tutorials/openssl-basics) - [scp / rsync で安全にファイルを転送する](/articles/tutorials/scp-rsync-basics) - [base64 でバイナリをテキスト化する](/articles/tutorials/base64-encoding) # グループ管理入門 - groupadd/groupmodとアクセス制御 Source: https://penguin-gym-linux.com/articles/tutorials/group-management-basics ## この記事でわかること {#intro} - `groupadd` / `groupmod` / `groupdel` の**基本構文と実用例**が分かる - グループへのメンバー追加・削除(`usermod -aG` / `gpasswd`)の正しい手順が分かる - `/etc/group` の構造とアクセス制御設計の考え方が身につく ::: tip **結論(実務の型)** - **グループ作成** → `groupadd groupname` - **メンバー追加** → `usermod -aG groupname username`(`-a` 必須。忘れると既存グループから追い出される) - **メンバー削除** → `gpasswd -d username groupname` - グループ変更は**再ログインまたは `newgrp`** で反映 ::: ## groupadd とは? {#groupadd} `groupadd` は新しいグループを作成するコマンドだ。ユーザー管理の `useradd` に相当するグループ版と捉えれば分かりやすい。 ```bash # 基本形 sudo groupadd developers # GID を指定して作成(番号が決まっている場合) sudo groupadd -g 1500 developers # システムグループとして作成(GID 999 以下の範囲) sudo groupadd -r sysgroup ``` 作成後は `/etc/group` に追記される。確認は次のコマンドで行う。 ```bash grep developers /etc/group # → developers:x:1500: ``` ::: tip GID を明示しない場合、システムが `/etc/login.defs` の `GID_MIN`〜`GID_MAX`(通常 1000〜60000)の範囲で自動採番する。 ::: ## groupmod でグループを変更するには? {#groupmod} `groupmod` は既存グループの属性(グループ名・GID)を変更するコマンドだ。 ```bash # グループ名を変更 sudo groupmod -n newdevelopers developers # GID を変更 sudo groupmod -g 1600 developers ``` ::: warning GID を変更すると、そのグループを所有者とするファイルが「孤立した GID」を持ったままになる。変更後は次のコマンドで整合性を修正する。 ```bash find / -gid 旧GID -exec chgrp 新GID {} \; ``` ::: ## groupdel でグループを削除するには? {#groupdel} `groupdel` はグループを `/etc/group` から削除する。 ```bash sudo groupdel developers ``` そのグループを**プライマリグループ**とするユーザーが存在する場合は削除できない。先にそのユーザーのプライマリグループを変更してから実行する。 ```bash # プライマリグループが developers のユーザーを確認 awk -F: '$4 == "1500" {print $1}' /etc/passwd # プライマリグループを変更してから削除 sudo usermod -g othergroup username sudo groupdel developers ``` ## グループメンバーを追加・削除するには? {#membership} ### メンバー追加 ```bash # usermod -aG(推奨・複数グループ対応) sudo usermod -aG developers alice # gpasswd -a(グループ単位で管理する場合) sudo gpasswd -a alice developers ``` `-a`(append)は**必須フラグ**だ。これを省略すると alice の所属グループが `developers` だけにリセットされる。 ```bash # 危険な例:既存グループをすべて消して developers だけにする sudo usermod -G developers alice # -a なし ``` ### メンバー削除 ```bash sudo gpasswd -d alice developers ``` ### 複数グループを一度に設定 ```bash sudo usermod -aG developers,ops alice ``` ## グループメンバーシップを確認するには? {#check} グループへの変更はすぐには反映されず、**再ログインが必要**なことが多い。 ```bash # 現在のユーザーの所属グループ groups # 特定ユーザーの所属グループ id alice groups alice # /etc/group を直接確認 grep developers /etc/group # → developers:x:1500:alice,bob ``` 再ログインせずにグループを反映するには `newgrp` を使う。 ```bash # 現在のシェルを developers グループで起動 newgrp developers ``` ::: tip `newgrp` はサブシェルを起動するため、`exit` で元のシェルに戻る。スクリプト内でのグループ切り替えには使いにくい点を覚えておくこと。 ::: ## /etc/group の構造を読む {#etc-group} `/etc/group` の 1 行は次の形式だ。 ``` グループ名:パスワード:GID:メンバーリスト ``` ``` developers:x:1500:alice,bob,carol ``` - **グループ名**: `developers` - **パスワード**: `x`(`/etc/gshadow` に移管されているため実質的に使われない) - **GID**: `1500` - **メンバーリスト**: カンマ区切り。プライマリグループのユーザーはここに表示されない ```bash # 全グループ一覧 cat /etc/group # GID でソート sort -t: -k3 -n /etc/group ``` ## グループを使ったアクセス制御の設計 {#access-control} グループは「誰がどのリソースにアクセスできるか」を一元管理する仕組みとして機能する。典型的なパターンを 2 つ示す。 ### パターン 1: ディレクトリの共有 ```bash # /srv/project を developers グループで共有する sudo groupadd developers sudo mkdir -p /srv/project sudo chown root:developers /srv/project sudo chmod 2775 /srv/project # setgid ビット付き # alice を developers に追加 sudo usermod -aG developers alice ``` `chmod 2775` の `2`(setgid)により、このディレクトリ内に作成したファイルは自動的に `developers` グループを継承する。 ### パターン 2: sudo 権限のグループ管理 Ubuntu では `sudo` グループへの追加が sudo 権限付与の標準的な方法だ。 ```bash # alice に sudo 権限を付与(Ubuntu) sudo usermod -aG sudo alice # CentOS / RHEL の場合は wheel グループを使う sudo usermod -aG wheel alice ``` ::: warning sudo グループへの追加は**再ログイン後**に有効になる。緊急の場合は `su - alice` でユーザーを切り替えて `sudo -l` で確認すること。 :::
## まとめ | 操作 | コマンド | 注意点 | | -------------- | ----------------------------- | ----------------------------- | | グループ作成 | `groupadd groupname` | GID 自動採番 | | グループ名変更 | `groupmod -n newname oldname` | GID は変わらない | | GID 変更 | `groupmod -g GID groupname` | 既存ファイルの GID を手動修正 | | グループ削除 | `groupdel groupname` | プライマリグループは削除不可 | | メンバー追加 | `usermod -aG group user` | `-a` 必須 | | メンバー削除 | `gpasswd -d user group` | 再ログイン後に反映 | | メンバー確認 | `groups user` / `id user` | — | - `usermod -aG` の `-a` を忘れると既存グループが消える — 最も多いミス - グループ変更は再ログインまたは `newgrp` で反映 - setgid ビット(`chmod g+s`)を使うとディレクトリ内のファイルがグループを継承する
# heredoc(ヒアドキュメント)入門 - 複数行の文字列をスクリプトに埋め込む Source: https://penguin-gym-linux.com/articles/tutorials/heredoc-basics ## ヒアドキュメントとは何か? {#what-is-heredoc} 複数行の文字列をスクリプトに直接埋め込む構文。`< /tmp/config.conf host=localhost port=5432 dbname=mydb EOF ``` 追記の場合は `>>`: ```bash cat <> /tmp/config.conf user=admin password=secret EOF ``` root 権限が必要なファイルへの書き込みは `tee` を使う(`sudo cat < /root/file` は `cat` に sudo が適用されてもリダイレクト先への書き込みは一般ユーザー権限になるため)。 ```bash cat < **結論**: 3 つともファイルのバイトを 16 進数で表示するツール。実務ではまず `hexdump -C` か `xxd` を使い、編集が必要なら `xxd -r` を使う。`od` は移植性が必要な場面の保険。 - ファイルの中身を **1 バイト単位の 16 進数** で確認できる - 目に見えない **制御文字・改行コード(CRLF)・BOM** を発見できる - `xxd -r` で **16 進ダンプを編集してバイナリに戻す** ことができる ::: tip **3 ツールの一言まとめ** - `hexdump -C` … 16 進 + ASCII の定番ビュー(Linux で最も使われる) - `xxd` … 見やすく、`-r` で逆変換・`-i` で C 配列化までできる多機能 - `od` … POSIX 標準で最古。移植性が最優先のときの保険 ::: ::: warning **前提(対象環境)** - OS: Ubuntu / 一般的な Linux - `hexdump` / `od` は coreutils・bsdmainutils 系で標準提供 - `xxd` は `vim-common` パッケージに含まれる(Ubuntu では `vim` 導入時に同梱) ::: ## なぜバイナリを16進数で見る必要があるのか? {#why} > **結論**: テキストエディタでは見えない「目に見えないバイト」を確認するため。改行コードの違い、先頭の BOM、末尾の余分な空白、壊れたエンコーディングは 16 進ダンプで初めて姿を現す。 「スクリプトが動かない」「diff が一致しない」「ファイル判定がおかしい」——こうした問題の裏には、**画面に表示されないバイト** が潜んでいることが多い。 - Windows 由来の改行 `\r\n`(CRLF)が混入している - UTF-8 ファイルの先頭に BOM(`EF BB BF`)が付いている - 全角スペースや制御文字が紛れ込んでいる これらはテキストエディタでは判別しづらいが、16 進ダンプなら 1 バイトずつ正確に観測できる。 ## hexdump の基本 - まず -C を使う {#hexdump} > **結論**: `hexdump` のデフォルト出力は 16 ビット語をバイトスワップして表示するため読みにくい。実務では必ず `-C`(canonical)を付け、左に 16 進・右に ASCII を並べて読む。 サンプルファイルを用意する。 ```bash $ printf 'Hello, World!\n' > demo.txt ``` `-C` を付けると、オフセット・16 進バイト列・ASCII が 1 行に並ぶ。 ```bash $ hexdump -C demo.txt ``` ```output 00000000 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 0a |Hello, World!.| 0000000e ``` 左の `00000000` はファイル先頭からのオフセット(16 進)、中央が各バイトの 16 進値、右の `|...|` が印字可能文字の ASCII 表現(印字不能は `.`)。 ::: warning **`-C` を付けないと読みにくい** ```bash $ hexdump demo.txt ``` ```output 0000000 6548 6c6c 2c6f 5720 726f 646c 0a21 000000e ``` これはリトルエンディアン環境で 2 バイトを 1 語としてバイトスワップした表示。`48 65` が `6548` と入れ替わって見える。混乱の元なので、人間が読むときは `-C` を使う。 ::: よく使うオプション: - `-n `: 先頭 N バイトだけ表示 - `-s `: 先頭から N バイトスキップして表示 - `-v`: 同一行の繰り返しを `*` で省略せず全表示 ```bash $ hexdump -C -n 16 -s 0 demo.txt ``` ## xxd の基本と便利な使い方 {#xxd} > **結論**: `xxd` は素の実行で見やすいダンプを出す。真価は `-r`(16 進→バイナリ復元)・`-p`(プレーン 16 進)・`-i`(C 配列出力)にあり、hexdump にはできない逆変換まで担える。 素の `xxd` は 2 バイトごとにグループ化された 16 進と右側の ASCII を表示する。 ```bash $ xxd demo.txt ``` ```output 00000000: 4865 6c6c 6f2c 2057 6f72 6c64 210a Hello, World!. ``` ### プレーン16進で取り出す(-p) `-p`(plain)は区切りなしの連続した 16 進文字列を出す。スクリプトで扱いやすい。 ```bash $ xxd -p demo.txt ``` ```output 48656c6c6f2c20576f726c64210a ``` ### 16進からバイナリに戻す(-r) `xxd` 最大の武器が逆変換 `-r`。ダンプを編集してから元のバイナリに戻せる。 ```bash $ xxd demo.txt | xxd -r Hello, World! ``` プレーン 16 進から戻すときは `-p` も合わせる。 ```bash $ echo '48656c6c6f0a' | xxd -r -p Hello ``` ### C言語の配列として出力(-i) `-i`(include)はファイルを C のバイト配列として出力する。ファームウェアやテスト用データの埋め込みに便利。 ```bash $ xxd -i demo.txt ``` ```output unsigned char demo_txt[] = { 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a }; unsigned int demo_txt_len = 14; ``` よく使うオプション: - `-l `: 先頭 N バイトだけ表示 - `-s `: 開始位置を指定 - `-c `: 1 行あたりのバイト数(既定 16) - `-g `: グループ化するバイト数(既定 2) ## od はどんなときに使うのか? {#od} > **結論**: `od`(octal dump)は POSIX 標準で最も移植性が高い。組み込み Linux や最小構成で `hexdump` や `xxd` が無い環境でも使える。`od -A x -t x1z` で `hexdump -C` 相当の出力になる。 `od` は名前の通り元は 8 進ダンプだが、`-t` で表示形式を、`-A` でオフセットの基数を指定できる。 ```bash $ od -A x -t x1z demo.txt ``` ```output 000000 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 0a >Hello, World!.< 00000e ``` - `-A x`: オフセットを 16 進で表示(`d` で 10 進、`o` で 8 進) - `-t x1`: 1 バイト単位の 16 進で表示 - `z`: 行末に ASCII 表現を併記 その他のよく使う形式: - `od -c`: 文字とエスケープ表記(`\n` `\t` 等)で表示 - `od -An -t x1`: オフセットを省略して 16 進バイトのみ ::: tip `od` の出力は POSIX で仕様が定められているため、スクリプトの移植性を重視するなら `od -A n -t x1` のように `od` を選ぶ価値がある。 ::: ## 3ツールをどう使い分けるか? {#compare} > **結論**: 日常の確認は `hexdump -C` か `xxd`、バイナリを編集するなら `xxd -r`、移植性が要るスクリプトなら `od`。迷ったら `hexdump -C` で十分。 | 用途 | 推奨ツール | | ----------------------------- | ---------------- | | とにかく中身を見たい | `hexdump -C` | | 見やすさ重視・グループ表示 | `xxd` | | 16 進を編集してバイナリに戻す | `xxd -r` | | C 配列に埋め込む | `xxd -i` | | 移植性・最小環境 | `od -A x -t x1z` | ## 実務での使いどころ {#practical} > **結論**: 改行コードの混入・BOM の有無・バイナリの直接編集が代表的な実務シーン。いずれも 16 進ダンプでなければ確実には判断できない。 ### CRLF(改行コード)の混入を見つける `\r\n`(CRLF)が混ざると `0d 0a` のように `0d` が現れる。Unix の改行 `\n` は `0a` のみ。 ```bash $ printf 'line1\r\nline2\n' | xxd ``` ```output 00000000: 6c69 6e65 310d 0a6c 696e 6532 0a line1..line2. ``` `310d 0a6c` の `0d` が CRLF の証拠。右側 ASCII では `.` としか見えないバイトを正確に特定できる。 ### UTF-8 の BOM を確認する ファイル先頭に `EF BB BF` があれば UTF-8 BOM 付き。 ```bash $ hexdump -C bom.txt ``` ```output 00000000 ef bb bf 68 69 0a |...hi.| 00000006 ``` 先頭 3 バイト `ef bb bf` が BOM。シェルスクリプトの先頭にこれが付くと `#!/bin/bash` が正しく解釈されず、実行エラーの原因になる。 ### バイナリを直接編集する(ラウンドトリップ) `xxd` でダンプ → エディタで 16 進を書き換え → `xxd -r` で戻す、という流れで 1 バイト単位の編集ができる。 ```bash $ xxd demo.txt > demo.hex # demo.hex を編集(例: 48 を 4a に変更) $ xxd -r demo.hex > demo_edited.txt ``` ::: warning `xxd -r` で戻すときはオフセット列とバイト列の整合に注意。バイト数を変えるとオフセットがずれて意図しない結果になることがある。1 バイト置換のような最小限の編集にとどめるのが安全。 ::: ## トラブルシューティング {#troubleshooting} > **結論**: `xxd: command not found` は `vim-common` 未導入が原因。出力が読みにくいときは `hexdump` に `-C` を付け忘れていないか確認する。 ### xxd: command not found `xxd` は `vim-common`(または `xxd`)パッケージに含まれる。最小構成の環境では未導入のことがある。 ```bash $ sudo apt install xxd ``` `xxd` が使えない環境では `od -A x -t x1z` または `hexdump -C` で代替する。 ### hexdump の出力が想定と違う `-C` 無しのデフォルトは 16 ビット語のバイトスワップ表示。ASCII が並ばず読みにくいときは `-C` の付け忘れを疑う。 ### 大きなファイルで同じ行が `*` に省略される `hexdump` / `od` は同一内容の行を `*` で省略する。全行を確認したいときは `-v` を付ける。 ```bash $ hexdump -C -v largefile.bin | head ``` ## 次に読む {#next} - [file コマンドでファイル種別を判定する](/articles/tutorials/file-command-type) - [stat でファイルの詳細情報を読む](/articles/tutorials/stat-file-inspection) - [base64 エンコード入門](/articles/tutorials/base64-encoding) # history expansion(履歴展開)入門 - !! や !$ でコマンド再利用を高速化 Source: https://penguin-gym-linux.com/articles/tutorials/history-expansion ## この記事でわかること {#intro} - history expansion(履歴展開)が「過去コマンドを呼び出す記法」だと分かる - `!!` / `!$` でコマンドや引数を一瞬で再利用できる - `!string` / `!:n` / `:h` `:t` `:r` で履歴を細かく取り出せる - `^old^new^` のクイック置換と、`histverify` で安全に使う方法が分かる ::: dialogue @lina: ライニー先輩、`sudo !!` とか `cd !$` ってよく見るんですけど、あの `!` マークって何なんですか? @linny: それは「history expansion(履歴展開)」っていう bash の機能だよ。`!` から始まる記法を打つと、過去のコマンドや引数を呼び出して再利用できるんだ。タイプ量がぐっと減るよ。 ::: ## 1. history expansion とは何か? {#what} > **結論**: history expansion は `!` で始まる記法を、過去のコマンドや引数に展開する bash の機能。タイプ量を減らし再入力ミスを防ぐ。 ::: dialogue @lina: 「展開」ってどういうことですか? @linny: コマンドを実行する前に、bash が `!!` のような記法を「実際のコマンド文字列」に置き換えることだよ。例えば `!!` は直前のコマンド全体に置き換わるんだ。 ::: history expansion は大きく 3 つの部品でできています。 | 部品 | 役割 | 例 | | ----------------------- | ---------------------------------- | ------------ | | イベント指定子(event) | どのコマンドを呼ぶか | `!!` / `!42` | | ワード指定子(word) | そのコマンドのどの単語を取り出すか | `!$` / `!^` | | 修飾子(modifier) | 取り出した単語をどう加工するか | `:h` / `:t` | ::: tip history expansion は主に **bash の対話シェル** で有効です。スクリプト内ではデフォルト無効なので、「便利だけどスクリプトには書かない」と覚えておきましょう。 ::: ## 2. イベント指定子:どのコマンドを呼ぶか {#event} > **結論**: `!!` は直前、`!n` は履歴番号 n、`!string` は string で始まる直近、`!?string?` は string を含む直近のコマンドを指す。 ::: dialogue @lina: まず「どのコマンドを呼ぶか」を決めるんですね。 @linny: そう。一番よく使うのが `!!` =直前のコマンド。`sudo` を付け忘れたときの定番テクだよ。 ::: ### !! — 直前のコマンド全体 ```bash $ apt update E: Could not open lock file /var/lib/dpkg/lock-frontend ... $ sudo !! sudo apt update ``` ### !n / !-n — 履歴番号で指定 ```bash $ history | grep tar 765 tar -czf backup.tar.gz /etc $ !765 tar -czf backup.tar.gz /etc ``` `!n` は履歴番号 n のコマンド、`!-n` は「n 個前」のコマンドを指します(`!-1` は `!!` と同じ)。 ### !string — string で始まる直近のコマンド ```bash $ !ssh ssh user@server ``` `!string` は「string で始まる、もっとも最近のコマンド」を再実行します。 ### !?string? — string を含む直近のコマンド ```bash $ !?server? ssh user@server ``` `!?string?` は「string をどこかに含む直近のコマンド」を探します。先頭以外にもマッチする点が `!string` との違いです。 ::: warning `!string` は確認なしで即実行されます。意図しない古いコマンドが呼ばれることもあるので、不安なときは次章の `:p`(表示のみ)で中身を確かめましょう。 ::: ## 3. ワード指定子:引数だけを取り出す {#word} > **結論**: `!$` は最後の引数、`!^` は最初の引数、`!*` は全引数、`!:n` は n 番目の単語を取り出す。長いパスの再入力を防げる。 ::: dialogue @lina: コマンド全体じゃなくて、引数だけ使い回したいときもありますよね。 @linny: そこでワード指定子。`!$` は「直前コマンドの最後の引数」。長いパスをもう一度打たずに済むんだ。 ::: ### !$ — 最後の引数 ```bash $ mkdir -p /var/log/myapp $ cd !$ cd /var/log/myapp ``` ### !^ と !\* — 最初の引数 / 全引数 ```bash $ cp config.yml config.yml.bak $ vim !^ vim config.yml ``` `!^` は最初の引数、`!*` はコマンド名を除いたすべての引数を表します。 ### !:n — n 番目の単語 単語には 0 から番号が振られ、`0` がコマンド名そのものです。 ```bash $ cp src.txt dst.txt $ echo !:1 echo src.txt $ echo !:2 echo dst.txt ``` | ワード指定子 | 意味 | | ------------ | -------------------------- | | `!^` | 最初の引数(`!:1` と同じ) | | `!$` | 最後の引数 | | `!*` | すべての引数(`!:1-$`) | | `!:0` | コマンド名そのもの | | `!:n` | n 番目の単語 | | `!:n-m` | n から m 番目までの単語 | ::: tip `!$` などは「直前コマンド」が暗黙の対象です。別のコマンドの引数がほしいときは `!ssh:$` のようにイベント指定子と組み合わせます。 ::: ## 4. 修飾子:取り出した単語を加工する {#modifier} > **結論**: `:h` はディレクトリ部分、`:t` はファイル名、`:r` は拡張子を除いた部分、`:e` は拡張子を取り出す。`:p` は実行せず表示のみ。 ::: dialogue @lina: パスから「ディレクトリだけ」「ファイル名だけ」を取りたいことがあるんですが。 @linny: それは修飾子の出番。ワード指定子のうしろに `:h` や `:t` を付けると、パスを部品に分解できるよ。 ::: ```bash $ ls /var/log/syslog $ echo !$:h echo /var/log $ echo !$:t echo syslog ``` | 修飾子 | 動作 | 例(`/var/log/app.log`) | | ------ | --------------------------------------- | ------------------------ | | `:h` | head:末尾の要素を除く(ディレクトリ) | `/var/log` | | `:t` | tail:末尾の要素のみ(ファイル名) | `app.log` | | `:r` | root:拡張子を除く | `/var/log/app` | | `:e` | extension:拡張子のみ | `log` | | `:p` | print:展開結果を表示するだけ(非実行) | — | ### :p で「展開結果だけ」を確認する ```bash $ !ssh:p ssh user@server ``` `:p` を付けると、history expansion の結果を**実行せずに表示**します。展開結果は履歴に追加されるので、確認してから `!!` で実行できます。 ::: warning `rm` や `sudo` を含むコマンドを `!string` で呼ぶときは、まず `:p` で中身を確認する習慣をつけると事故を防げます。 ::: ## 5. クイック置換:^old^new^ {#substitution} > **結論**: `^old^new^` は直前コマンドの最初の old を new に置換して再実行する短縮記法。タイプミスの修正に最適。 ::: dialogue @lina: 直前のコマンドの一部だけ打ち間違えたとき、全部打ち直すのは面倒です。 @linny: `^old^new^` を使えば、直前コマンドの `old` を `new` に置き換えて再実行できるよ。タイプミス修正の鉄板テクだね。 ::: ```bash $ cat /etc/hosst cat: /etc/hosst: No such file or directory $ ^hosst^hosts^ cat /etc/hosts ``` `^old^new^` は `!!:s/old/new/` の短縮形で、**最初に一致した 1 箇所だけ**を置換します。すべて置換したいときは `!!:gs/old/new/` を使います。 ::: tip 末尾の `^` は省略できます(`^hosst^hosts` でも動作)。ただし置換後にさらに文字を足したい場合は末尾 `^` まで明示しましょう。 ::: ## 6. histverify で安全に使う {#histverify} > **結論**: `shopt -s histverify` を設定すると、history expansion が即実行されず編集行に展開される。確認してから Enter できる。 ::: dialogue @lina: `!!` って確認なしで実行されるのが、ちょっと怖いです。 @linny: その不安、`histverify` で解消できるよ。設定しておくと、`!!` を展開した結果がいったん入力行に表示されて、Enter を押すまで実行されないんだ。 ::: `~/.bashrc` に次の 1 行を追記します。 ```bash shopt -s histverify ``` 設定後は `source ~/.bashrc` で反映します。これ以降、`!!` や `!string` を打つと、展開後のコマンドが入力行に現れ、内容を確認・編集してから Enter で実行できます。 ::: tip `histverify` は破壊的コマンドの誤実行を防ぐ安全装置です。history expansion を日常的に使うなら設定しておくと安心です。 ::: ## 7. まとめ {#summary} > **結論**: イベント指定子・ワード指定子・修飾子の 3 部品を組み合わせれば、過去コマンドを自在に再利用できる。 history expansion の基本記法を一覧で振り返ります。 | 記法 | 意味 | | ------------ | ------------------------------- | | `!!` | 直前のコマンド全体 | | `!n` / `!-n` | 履歴番号 n / n 個前のコマンド | | `!string` | string で始まる直近のコマンド | | `!$` / `!^` | 最後の引数 / 最初の引数 | | `!*` | すべての引数 | | `!$:h` `:t` | パスのディレクトリ / ファイル名 | | `^old^new^` | 直前コマンドを置換して再実行 | | `!!:p` | 展開結果を表示のみ(非実行) | ::: dialogue @lina: `!` ひとつでこんなに呼び出し方があるんですね。まずは `!!` と `!$` から使ってみます! @linny: それで十分。慣れてきたら `!string` や `^old^new^` を足していけば、コマンド入力がどんどん速くなるよ。 ::: ## 次に読む {#next} - [bash ヒストリ活用術 - 過去のコマンドを使い倒す](/articles/tutorials/bash-history-tips) - [.bashrc と .profile の読み込み順](/articles/tutorials/bashrc-profile-order) - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) # inotifywait 入門 - ファイル変更を監視して自動処理する Source: https://penguin-gym-linux.com/articles/tutorials/inotifywait-file-monitoring ## inotifywait とは何か? {#intro} > **結論**: `inotifywait` は Linux の inotify カーネル機能を使い、ファイル作成・変更・削除などのイベントを**ポーリングなしで検知**する CLI。変更をトリガーに自動処理を組める。 - `inotifywait` で **ファイルシステムのイベント** をリアルタイム検知できる - `while read` ループと組み合わせて **変更検知 → 自動処理** の型が作れる - `close_write` と `max_user_watches` の **2 つの落とし穴** を避けられる ::: tip **結論(実務の型)** - 1 回だけ待つ → `inotifywait path` - 常時監視して処理を回す → `inotifywait -m -e close_write path` - 「保存完了」を拾いたいなら `modify` ではなく **`close_write`** ::: ::: warning **前提(対象環境)** - OS:Ubuntu / Debian / RHEL 系などの Linux(Linux カーネル 2.6.13 以降の inotify) - パッケージ `inotify-tools` が必要(後述) - ローカルファイルシステム前提(NFS など一部のネットワーク FS では検知できない) ::: ## なぜ inotifywait を使うのか?(ポーリングとの違い) {#why} > **結論**: `while sleep 1; do ...; done` のようなポーリングは CPU を無駄に使い検知も遅い。inotifywait はカーネルがイベントを push するため、低負荷かつ即時に反応する。 ファイル変更を検知する素朴な方法は「定期的に `ls` や `stat` を見に行く」ポーリングだ。だがこれには欠点がある。 - 間隔を短くすると CPU・I/O を無駄に消費する - 間隔を長くすると検知が遅れる - 監視対象が増えるほどスキャンコストが膨らむ `inotifywait` はカーネルの inotify サブシステムにイベントを登録し、変更が起きた瞬間だけ通知を受け取る。アイドル時の負荷はほぼゼロで、反応は即時だ。`watch` コマンドが「定期的に画面を更新する」ポーリング型なのに対し、inotifywait は「イベント駆動」と理解するとよい。 ## inotifywait はどうやってインストールする? {#install} > **結論**: `inotifywait` は coreutils ではなく `inotify-tools` パッケージに含まれる。Debian 系は `apt`、RHEL 系は `dnf` で導入する。 ```bash # Ubuntu / Debian $ sudo apt install inotify-tools # RHEL / Rocky / AlmaLinux(EPEL が必要な場合あり) $ sudo dnf install inotify-tools # Fedora $ sudo dnf install inotify-tools ``` インストールできたか確認する。 ```bash $ inotifywait --help | head -n 1 ``` ```output inotifywait 3.22.6.0 ``` ::: tip 同梱の `inotifywatch` はイベントの**統計**を集計するツール。「どのファイルがどれだけアクセスされているか」を後から知りたいときに使う。本記事ではイベントを逐次処理する `inotifywait` を扱う。 ::: ## 基本の使い方:ファイル変更を監視する {#basic} > **結論**: 引数にパスを渡すと、イベントが 1 回起きるまでブロックし、内容を表示して終了する。継続監視には `-m`(monitor)を付ける。 ### 1 回だけ待つ(ワンショット) ```bash $ inotifywait /tmp/watchdir ``` 別のターミナルで `/tmp/watchdir` 内のファイルを触ると、次のように出力して終了する。 ```output Setting up watches. Watches established. /tmp/watchdir/ MODIFY test.txt ``` 出力は **`監視パス イベント名 ファイル名`** の順。デフォルトではすべての種類のイベントを対象に、最初の 1 件で終了する。 ### 常時監視する(-m) 毎回終了されては自動処理に使えない。`-m`(`--monitor`)を付けると終了せず、イベントを延々と出し続ける。 ```bash $ inotifywait -m /tmp/watchdir ``` ```output Setting up watches. Watches established. /tmp/watchdir/ OPEN test.txt /tmp/watchdir/ MODIFY test.txt /tmp/watchdir/ CLOSE_WRITE,CLOSE test.txt ``` `Setting up watches.` などの起動ログは標準エラーに出る。スクリプトで処理したいときは `-q`(quiet)で抑制するとよい。 ## どのイベントを監視すべき?(-e の選び方) {#events} > **結論**: `-e` で対象イベントを絞る。「ファイル保存の完了」を拾うなら `modify` ではなく **`close_write`** が正解。modify は書き込みのたびに何度も発火する。 `-e`(`--event`)を付けないと全イベントが対象になり、ノイズが多い。代表的なイベントは次のとおり。 | イベント | 発火タイミング | | ------------- | ------------------------------------------------- | | `create` | ファイル / ディレクトリが作成された | | `modify` | 内容が書き込まれた(**書き込みごとに複数回**) | | `close_write` | 書き込み用に開かれたファイルが**閉じられた** | | `delete` | ファイル / ディレクトリが削除された | | `moved_to` | このディレクトリへ移動 / リネームされて入ってきた | | `moved_from` | このディレクトリから移動 / リネームで出ていった | | `attrib` | 権限・所有者・タイムスタンプなどが変わった | 複数指定は `-e` を並べるか、カンマ区切りで書く。 ```bash # 作成・書き込み完了・削除だけを監視 $ inotifywait -m -e create -e close_write -e delete /tmp/watchdir ``` ::: warning **`modify` ではなく `close_write` を使う理由** エディタやプログラムは 1 回の保存でも内部的に複数回書き込むことがあり、`modify` はそのたびに発火する。「保存が**終わった**」瞬間を 1 回だけ拾いたいなら、ファイルが閉じられたことを表す `close_write` が適切。これを知らないと「同じファイルで処理が何度も走る」事故になる。 ::: ## 出力を整形する:--format と --timefmt {#format} > **結論**: `--format` で出力を任意の書式に変えられる。`%w` 監視パス・`%f` ファイル名・`%e` イベント名・`%T` タイムスタンプが基本。`%T` は `--timefmt` と併用する。 デフォルト出力はスペース区切りで扱いにくい。`--format` でスクリプトが処理しやすい形に整える。 ```bash $ inotifywait -m --timefmt '%F %T' --format '%T | %e | %w%f' \ -e close_write /tmp/watchdir ``` ```output 2026-06-05 21:30:11 | CLOSE_WRITE,CLOSE | /tmp/watchdir/report.csv ``` 主なフォーマット指定子: - `%w` … 監視しているパス(watch 対象のディレクトリ) - `%f` … イベント対象のファイル名(ディレクトリを監視している場合) - `%e` … 発生したイベント名(カンマ区切り) - `%T` … タイムスタンプ(`--timefmt` の strftime 書式で整形) `%w%f` をつなげるとフルパスになる。区切り文字を変えたい場合は `%Xe`(`X` が区切り文字)も使える。 ## ディレクトリを再帰的に監視する(-r と watch 上限) {#recursive} > **結論**: `-r` でサブディレクトリまで再帰監視できる。ただし**ディレクトリ 1 個につき watch を 1 個消費**し、`fs.inotify.max_user_watches` の上限に当たると失敗する。 ```bash $ inotifywait -m -r -e close_write /var/www ``` 巨大なツリーを `-r` で監視すると、次のエラーが出ることがある。 ```output Failed to watch /var/www; upper limit on inotify watches reached! Please increase the amount of inotify watches allowed per user via `/proc/sys/fs/inotify/max_user_watches'. ``` 現在の上限を確認する。 ```bash $ cat /proc/sys/fs/inotify/max_user_watches ``` ```output 8192 ``` 一時的に引き上げる(再起動でリセット)。 ```bash $ sudo sysctl fs.inotify.max_user_watches=524288 ``` 恒久化するには設定ファイルに書く。 ```bash $ echo 'fs.inotify.max_user_watches=524288' | sudo tee /etc/sysctl.d/90-inotify.conf $ sudo sysctl --system ``` ::: warning **再帰監視のもう 1 つの注意点** inotify はディレクトリ単位で watch を張る。監視開始**後**に新しく作られた深い階層のサブディレクトリは、watch が張られるまでの一瞬の隙にイベントを取りこぼす可能性がある(レース)。頻繁にサブディレクトリが増えるツリーでは、この取りこぼしを前提に設計する。 ::: ## 実践:ファイル変更を検知して自動処理する {#automation} > **結論**: `inotifywait -m` の出力を `while read` で受け、ファイルごとに処理を回すのが定番の型。`--format` で必要な値だけ渡すとパースが安定する。 監視ディレクトリに置かれた CSV を、書き込み完了のたびに処理する例。 ```bash #!/usr/bin/env bash set -euo pipefail WATCH_DIR=/var/spool/incoming inotifywait -m -q \ --format '%w%f' \ -e close_write \ "$WATCH_DIR" | while read -r filepath; do case "$filepath" in *.csv) echo "処理開始: $filepath" # ここで実際の処理(取り込み・変換・通知など)を行う ;; *) echo "対象外、スキップ: $filepath" ;; esac done ``` ポイント: - `-q` で起動ログを抑制し、`--format '%w%f'` でフルパスだけを渡す - `close_write` だけに絞り、「保存完了」のみを処理トリガーにする - `while read -r` の `-r` でバックスラッシュをそのまま受ける ::: tip **多重起動を防ぐ** この監視スクリプトを cron や systemd で起動する場合、二重に立ち上がると同じファイルを 2 回処理しかねない。[flock でファイルロック](/articles/tutorials/flock-file-locking)を併用し、インスタンスを 1 つに限定すると安全。 ::: ::: warning **エディタの「アトミック保存」に注意** vim などは「一時ファイルに書く → 元の名前へリネーム」という保存方式を取ることがある。この場合 `close_write` ではなく `moved_to` や `create` として観測される。テキストエディタの保存を確実に拾いたいなら、ディレクトリを監視して `-e close_write -e moved_to` の両方を対象にするとよい。 ::: ## inotifywait のよくある質問 {#faq} > **結論**: 終了コードはイベント受信で 0、エラーで 1、`-t` のタイムアウトで 2。NFS など一部のネットワーク FS では他ホストの変更を検知できない。 **Q. 一定時間イベントが無ければ抜けたい** `-t`(`--timeout`)で秒数を指定する。タイムアウトで終了した場合の終了コードは `2`。 ```bash $ inotifywait -t 30 -e close_write /tmp/watchdir ``` **Q. 監視から特定のパスを除外したい** `--exclude`(大文字小文字を区別)/ `--excludei`(区別しない)に拡張正規表現を渡す。 ```bash $ inotifywait -m -r --exclude '\.git/' -e close_write /repo ``` **Q. NFS 上のファイルを監視できる?** inotify はローカルカーネルが観測した変更だけを通知する。NFS など別ホストが書き換えるネットワーク FS では、その変更を検知できないことがある。共有ストレージの監視はポーリングなど別手段を検討する。 ## 次に読む {#next} - [cron の基本](/articles/tutorials/cron-basics) - [flock でファイルロック](/articles/tutorials/flock-file-locking) - [watch コマンド入門](/articles/tutorials/watch-command-basics) - [Too many open files の対処](/articles/troubleshooting/too-many-open-files) # ionice 入門 - ディスクI/O優先度を制御する Source: https://penguin-gym-linux.com/articles/tutorials/ionice-io-priority ## ionice とは何か? {#what} > **結論**: `ionice` は、プロセスごとにディスク I/O の優先度を設定・確認するコマンド。`nice` が CPU 時間を制御するのに対し、`ionice` は「誰が先にディスクへアクセスできるか」を制御する。 `ionice` は `util-linux` パッケージに含まれる標準コマンドで、ほぼすべてのディストリビューションに最初から入っている。バックアップや大量コピーのような I/O 負荷の高い処理を「他のプロセスの邪魔をしないように」走らせたいときに使う。 基本の構文は 2 通り。 ```bash # これから起動するコマンドに優先度を付ける ionice [オプション] コマンド [引数...] # 既に動いているプロセス(PID 指定)の優先度を変更・確認する ionice [オプション] -p PID... ``` ## なぜ ionice が必要なのか? {#why} > **結論**: バックアップや `rsync` が走るとサーバ全体が重くなる典型原因はディスク I/O の奪い合い。`ionice` で重い処理を低優先度に下げれば、本番サービスの応答を守れる。 CPU に余裕があっても、ディスクが 1 台しかなければ I/O は順番待ちになる。`tar` や `dd`、`rsync`、`du` のような処理がディスクを占有すると、同じディスクを使う Web サーバや DB のレスポンスが一気に悪化する。 `nice` で CPU 優先度だけ下げても、I/O 待ちが原因の遅延は解消しない。ここで効くのが `ionice`。重いバッチ処理を idle クラスに落とせば、「他に誰もディスクを使っていないときだけ動く」状態にできる。 ::: tip **`top` で `wa`(iowait)が高い**ときは CPU ではなく I/O がボトルネック。この状況こそ `ionice` の出番。 ::: ## スケジューリングクラスと優先度をどう指定するのか? {#class} > **結論**: クラスは `-c` で指定する。`1`=realtime(最優先・要 root)、`2`=best-effort(既定)、`3`=idle(暇なときだけ)。`-n` で 0(最高)〜7(最低)の優先度を付ける。 I/O スケジューリングクラスは 4 種類ある。 | 番号 | クラス | 意味 | `-n` 優先度 | | ---- | ----------- | ------------------------------------------------ | ----------- | | 0 | none | 明示クラスなし。CPU の nice 値から自動決定 | 無視 | | 1 | realtime | 常に最優先でディスクへアクセス。他を飢餓させ得る | 0〜7 | | 2 | best-effort | 既定クラス。通常の処理 | 0〜7 | | 3 | idle | 他がディスクを使っていないときだけ動く | 無視 | 優先度 `-n` は **0 が最高、7 が最低**。realtime と best-effort クラスでのみ意味を持ち、idle クラスでは無視される(idle は常に最低だから)。 ```bash # バックアップを idle クラスで実行(他の処理を一切邪魔しない) ionice -c 3 tar czf /backup/data.tar.gz /var/data # 大量コピーを best-effort の最低優先度で実行 ionice -c 2 -n 7 cp -a /src/huge.img /mnt/ # realtime クラス(最優先)。root 権限が必要 sudo ionice -c 1 -n 0 dd if=/dev/sdb of=/dev/sdc bs=1M ``` ::: warning **realtime(クラス 1)は root 権限が必須**で、使い方を誤ると他のプロセスを完全に I/O 飢餓に追い込む。本番では基本的に `idle` か `best-effort` を使い、realtime は明確な理由があるときだけにする。 ::: ## 実行中のプロセスに後から適用するには? {#pid} > **結論**: `-p PID` で動作中のプロセスにも適用できる。`-p` だけなら現在の優先度を確認、`-c`/`-n` と併用すれば変更できる。 起動時に `ionice` を付け忘れた重い処理にも、後から優先度を変えられる。 ```bash # PID 1234 の現在の I/O 優先度を確認 ionice -p 1234 ``` ```output best-effort: prio 4 ``` ```bash # 動作中の PID 1234 を idle クラスに変更 ionice -c 3 -p 1234 # ユーザー単位・プロセスグループ単位でも指定できる ionice -c 3 -u 1000 # UID 1000 の全プロセス ionice -c 3 -P 5678 # プロセスグループ 5678 ``` ::: tip すでに暴走している `du` や `rsync` を見つけたら、`pgrep rsync` で PID を拾って `ionice -c 3 -p $(pgrep rsync)` で idle に落とすと、kill せずにサーバを軽くできる。 ::: ## ionice が効かない時は何を確認するのか? {#scheduler} > **結論**: I/O 優先度を尊重するのは **BFQ スケジューラ**(と旧 CFQ)だけ。多くの現代システム既定の `mq-deadline` / `none` では `ionice` のクラス指定が効かない。 これが `ionice` 最大の落とし穴。優先度は「I/O スケジューラ」が解釈して初めて効く。優先度をきちんと扱うのは歴史的には CFQ、現在は **BFQ** のみ。CFQ は Linux 5.0 で削除済みで、NVMe などで既定の `none` や `mq-deadline` は I/O 優先度を区別しない。 まず対象ディスクの現在のスケジューラを確認する。 ```bash # sda のスケジューラを確認([] で囲まれたものが現在の設定) cat /sys/block/sda/queue/scheduler ``` ```output none mq-deadline kyber [bfq] ``` `[bfq]` のように bfq が選択されていれば `ionice` が効く。`[mq-deadline]` などの場合は切り替える。 ```bash # bfq モジュールを読み込み(必要な場合) sudo modprobe bfq # sda のスケジューラを bfq に切り替え(再起動で元に戻る一時設定) echo bfq | sudo tee /sys/block/sda/queue/scheduler ``` ::: warning スケジューラの変更はディスクの全体特性に影響する。BFQ は公平性に強い一方、超高 IOPS の NVMe ではスループットが落ちる場合がある。本番では影響を検証してから恒久化(`udev` ルール等)すること。`-c idle` を付けても重い処理が他を圧迫し続けるなら、まずスケジューラを疑う。 ::: ## nice と ionice はどう使い分けるのか? {#nice} > **結論**: CPU バウンドな処理は `nice`、I/O バウンドな処理は `ionice`。両方重い処理には両方を併用するのが定石。 | ボトルネック | 使うコマンド | 例 | | ----------------- | ------------ | -------------------------- | | CPU(計算・圧縮) | `nice` | `gzip`, `ffmpeg`, ビルド | | ディスク I/O | `ionice` | `tar`, `rsync`, `dd`, `du` | | 両方 | 併用 | バックアップ全般 | バックアップは CPU(圧縮)と I/O(読み書き)の両方を食うため、両方を下げるのが王道。 ```bash # CPU も I/O も最低優先度でバックアップ nice -n 19 ionice -c 3 tar czf /backup/data.tar.gz /var/data ``` ## 実務でよく使うパターンは? {#patterns} > **結論**: 「夜間バックアップ」「巨大ファイルのコピー・削除」「暴走プロセスの応急処置」の 3 つが定番。idle クラスを軸にコピペで使える型を持っておく。 ```bash # 1) cron の夜間バックアップを idle + nice で(本番に影響させない) nice -n 19 ionice -c 3 rsync -a /var/www/ /backup/www/ # 2) 巨大ファイルの削除でディスクを占有させない ionice -c 3 rm -rf /var/log/old-huge-dir/ # 3) 動作中の重いプロセスを応急で idle に落とす ionice -c 3 -p "$(pgrep -d, -f backup-script)" ``` ::: tip **`-t`(--ignore)オプション**を付けると、優先度設定に失敗してもコマンド自体は続行する。スケジューラが対応していない環境でもスクリプトを止めたくない場合に有効。 ::: ## 次に読む {#next} - [nice / renice で CPU 優先度を制御する](/articles/tutorials/nice-renice-basics) - [ディスク I/O が遅いときの調査](/articles/troubleshooting/disk-io-troubleshooting) - [cron で定期バックアップを組む](/articles/tutorials/cron-basics) # iptables/nftables 入門 - パケットフィルタの基礎 Source: https://penguin-gym-linux.com/articles/tutorials/iptables-nftables ## iptables と nftables の違いとは? {#difference} iptables は長年 Linux のデファクト標準ファイアウォールだったが、現在は nftables への移行が進んでいる。Ubuntu 22.04 以降はデフォルトで nftables(iptables-nft ラッパー経由)を使用している。既存サーバの保守なら iptables、新規構築なら nftables を選ぶのが基本方針だ。 | 比較項目 | iptables | nftables | | ------------ | ------------------------- | ---------------------- | | 導入時期 | 1998年〜 | 2014年〜(Linux 3.13) | | 構文 | 複雑・冗長 | 統一・簡潔 | | IPv4/IPv6 | 別コマンド(ip6tables) | 統合(inet ファミリ) | | Ubuntu 22.04 | iptables-nft ラッパー経由 | ネイティブ | ::: tip Ubuntu 22.04 以降では `iptables` コマンドを実行しても、内部的には nftables が処理している。`update-alternatives --list iptables` で現在の実装を確認できる。 ::: ## iptables の基本構造 {#iptables-structure} iptables のルールは「テーブル → チェーン → ルール」の 3 層構造で管理される。通常のファイアウォール設定は `filter` テーブルの 3 チェーンで完結する。 **filter テーブルの 3 チェーン:** - **INPUT**: サーバ宛のパケット(外部 → 自機) - **OUTPUT**: サーバ発のパケット(自機 → 外部) - **FORWARD**: 転送パケット(ルーター構成時のみ使用) **ルールの判定(ターゲット):** - **ACCEPT**: パケットを通過させる - **DROP**: 黙って破棄する(送信元に通知なし) - **REJECT**: エラーを返して破棄する ## iptables コマンドの基本操作 {#iptables-commands} 既存ルールの確認から始めるのが基本。`-n` で DNS 逆引きをスキップし、`-v` でパケットカウントも表示する。 ### ルールの表示 ```bash # 全チェーンのルール一覧(詳細) iptables -L -n -v # ルール番号付きで表示 iptables -L INPUT -n -v --line-numbers ``` ### ルールの追加 ```bash # ループバックを許可(必須) iptables -A INPUT -i lo -j ACCEPT # 確立済み接続を許可(必須) iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # SSH(ポート 22)を許可 iptables -A INPUT -p tcp --dport 22 -j ACCEPT # HTTP/HTTPS を許可 iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j ACCEPT # それ以外を DROP iptables -A INPUT -j DROP ``` ### ルールの削除・リセット ```bash # 番号指定で削除(--line-numbers で確認後) iptables -D INPUT 3 # ルール内容を指定して削除(-A を -D に置き換える) iptables -D INPUT -p tcp --dport 80 -j ACCEPT # 全チェーンのルールをクリア iptables -F # デフォルトポリシーを ACCEPT に戻す iptables -P INPUT ACCEPT iptables -P OUTPUT ACCEPT ``` ::: warning `iptables -F` でルールをクリアした後にデフォルトポリシーが DROP のままだと SSH 接続が切断される。リモートサーバで作業する場合は必ずデフォルトポリシーを確認してから実行すること。 ::: ## nftables の基本構造 {#nftables-structure} nftables は「ファミリ → テーブル → チェーン → ルール」の構造を持つ。iptables と異なり、テーブルとチェーンを自分で作成する必要がある。`inet` ファミリを使えば IPv4/IPv6 を 1 つのルールセットで管理できる。 ```bash # 現在の全ルールを表示 nft list ruleset # テーブル一覧 nft list tables # 特定テーブルの詳細表示 nft list table inet filter ``` ## nftables コマンドの基本操作 {#nftables-commands} nftables はテーブルとチェーンを先に作成してからルールを追加する。`type filter hook input priority 0` が最も一般的なフィルタチェーンの設定だ。 ### テーブルとチェーンの作成 ```bash # テーブル作成 nft add table inet filter # INPUT チェーン作成(デフォルトポリシー: drop) nft add chain inet filter input '{ type filter hook input priority 0 ; policy drop ; }' # OUTPUT チェーン作成(デフォルトポリシー: accept) nft add chain inet filter output '{ type filter hook output priority 0 ; policy accept ; }' ``` ### ルールの追加 ```bash # ループバックを許可 nft add rule inet filter input iif lo accept # 確立済み接続を許可 nft add rule inet filter input ct state established,related accept # SSH を許可 nft add rule inet filter input tcp dport 22 accept # HTTP/HTTPS を許可(複数ポートをまとめて指定可能) nft add rule inet filter input tcp dport '{ 80, 443 }' accept ``` ### ルールの削除 ```bash # ハンドル番号で削除(-a オプションで番号を確認) nft list ruleset -a nft delete rule inet filter input handle 5 ``` ### 全ルールのリセット ```bash # 全ルールをクリア nft flush ruleset # テーブルごと削除 nft delete table inet filter ``` ::: danger `nft flush ruleset` はすべてのルールを即座に削除する。デフォルトポリシーが DROP のチェーンがあるとパケットがすべて遮断されるため、リモートサーバでの実行は接続状態を確認してから行うこと。 ::: ## 設定の永続化 {#persistent} iptables / nftables のルールはデフォルトで再起動時にリセットされる。サーバ再起動後も設定を維持するには永続化が必要だ。 ### iptables の永続化(Ubuntu) ```bash # iptables-persistent をインストール apt install iptables-persistent # 現在のルールを保存 netfilter-persistent save # 手動で保存する場合 iptables-save > /etc/iptables/rules.v4 ip6tables-save > /etc/iptables/rules.v6 ``` ### nftables の永続化(Ubuntu) ```bash # nftables.service を有効化 systemctl enable nftables # 現在のルールセットをファイルに保存 nft list ruleset > /etc/nftables.conf # サービスを再起動して動作確認 systemctl restart nftables systemctl status nftables ``` `/etc/nftables.conf` の基本テンプレート: ```bash #!/usr/sbin/nft -f flush ruleset table inet filter { chain input { type filter hook input priority 0; policy drop; iif lo accept ct state established,related accept tcp dport 22 accept tcp dport { 80, 443 } accept } chain output { type filter hook output priority 0; policy accept; } } ``` ::: tip Ubuntu 22.04 では `nftables.service` がデフォルトで搭載されている。`/etc/nftables.conf` を編集して `systemctl restart nftables` で即反映できる。 ::: ## firewalld / ufw との関係 {#frontend} RHEL / CentOS / Fedora では nftables のフロントエンドとして `firewalld` が使われている。Ubuntu では `ufw`(Uncomplicated Firewall)が nftables / iptables のフロントエンドとして提供されており、日常的な管理は ufw が最もシンプルだ。 ```bash # ufw の基本操作(Ubuntu) ufw status verbose ufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw enable ufw status numbered ``` ::: tip Ubuntu での日常的なファイアウォール管理は `ufw` が最もシンプル。iptables / nftables を直接操作するのは細かい制御が必要な場面や学習目的に絞るとよい。 ::: ## 次に読む {#next} - [ネットワークコマンド入門 - ip/ifconfig で接続状態を確認する](/articles/tutorials/network-commands-basics) - [netstat/ss 入門 - ポートと接続状態の確認方法](/articles/tutorials/netstat-ss-basics) - [nc (netcat) 入門 - ポート疎通とデバッグの万能ツール](/articles/tutorials/netcat-basics) # ジョブ制御入門 - jobs/fg/bg/Ctrl+Z Source: https://penguin-gym-linux.com/articles/tutorials/job-control-basics ## この記事でできるようになること {#intro-list} - `Ctrl+Z` で実行中のコマンドを **一時停止** して、端末を取り戻せる - `bg` / `fg` で **バックグラウンドとフォアグラウンド** を自由に行き来できる - `jobs` で **ジョブの一覧** を確認し、`%番号` で 1 つずつ指定できる - `&` を末尾に付けて **最初からバックグラウンドで実行** できる - 「端末が固まった」「終了したのにジョブが消えない」といった **詰まりどころ** を抜けられる **対象読者**: `vim` や長いコマンドの実行中に「ちょっとだけ別のコマンドを打ちたい」と思って詰まった方。`Ctrl+C` で全部止めてしまって悔しい思いをした方。 ## 導入:リナが Ctrl+C で泣いた日 {#intro} ::: dialogue @lina: 先輩、聞いてください!`vim` で長い設定ファイルを編集していました。途中で `ls` も打ちたくなったんです。でも新しいターミナルの開き方が分からなくて、つい `Ctrl+C` を押しました。 @lina: そうしたら vim ごと終了しちゃって...未保存の変更が全部消えました...。 @linny: あー、それは痛いね。でも安心して。それを防ぐキー操作があるよ。`Ctrl+Z`(コントロール ゼット)だ。 @lina: シー(C)じゃなくてゼット(Z)、ですか? @linny: そう。`Ctrl+C` は「実行中のコマンドを **止めて終わらせる**」。`Ctrl+Z` は「実行中のコマンドを **一時停止して脇に置く**」だけなんだ。 @linny: 一時停止なら端末が手元に戻る。だから別のコマンドを打てる。用が済んだら `fg` で元の作業に戻れるよ。 @lina: えっ、そんな魔法みたいなのが…!じゃあ、あのとき vim を殺さずに済んだんですね。 @linny: そういうこと。この機能を「ジョブ制御」と呼ぶ。シェル(bash や zsh)が持っている基本機能の 1 つだよ。今日は 4 つだけ覚えよう。**`Ctrl+Z`(一時停止)/ `bg`(裏で再開)/ `fg`(前に戻す)/ `jobs`(一覧)** だね。 ::: ::: tip **結論(実務の型)** - 端末を取り戻したいだけ → **`Ctrl+Z` → `bg`**(裏で動かしたまま端末を解放) - 一時停止してそのまま放置 → **`Ctrl+Z`** だけ(停止状態) - 元の作業に戻る → **`fg`** - 何が動いているか分からなくなったら → **`jobs`** ::: ::: warning **先に覚える「固まったように見えるとき」の抜け方** ジョブ制御では、画面が止まったように見える場面がある。次の表の順で試せば必ず抜けられる。 | 見えている状態 | 抜け方 | | -------------------------------------- | --------------------------------------------------------- | | コマンドが動いたままプロンプトが出ない | `Ctrl+Z` で一時停止する(プロンプトが戻る) | | `Ctrl+Z` の直後で何をすべきか不明 | `fg` で元に戻すか、`bg` で裏に回す。どちらも取り消せる | | 何が止まっているか分からない | `jobs` で一覧を見る。`fg %1` のように番号で選ぶ | | どうしても終わらせたい | `jobs` で番号を確認してから `kill %1`(`%` を必ず付ける) | `Ctrl+Z` は終了ではない。押しても作業内容は消えないので、安心して試してほしい。 ::: ::: highlight **端末(ターミナル)とは** 端末は「文字を打ち込んで、結果を文字で受け取る窓口」のこと。「ターミナル」「コンソール」も、ほぼ同じものを指す別の呼び方だよ。 この記事でいう「端末が戻る」は「`$` の入力待ちに戻り、次のコマンドを打てる状態になる」という意味。その `$` の記号をプロンプトと呼ぶ。 ::: ::: warning **前提(対象環境)** - bash / zsh(ジョブ制御は POSIX シェルの標準機能。`dash` にも `jobs` / `fg` / `bg` はある) - 非対話シェル(`sh -c '...'` やシェルスクリプトの中)ではデフォルトで無効 - 本記事のキー操作は端末上の **対話的セッション** が前提 ::: ## 1. 「ジョブ」と「プロセス」は何が違う? {#job-vs-process} > **結論**: プロセスは OS 目線で動くプログラム単位。ジョブはシェル目線で打ったコマンド 1 単位。指定は `%番号`。 ::: dialogue @lina: そもそも「ジョブ」って何ですか?「プロセス」とは別ものですか? @linny: 似ているけれど、見る立場が違うんだ。**プロセス**(process)は OS から見た「動いているプログラム 1 つ 1 つ」のこと。 @linny: 一方の **ジョブ**(job)は、シェルから見た「あなたが打ったコマンド 1 つの単位」だよ。 @lina: ジョブはシェル目線、プロセスは OS 目線、ということですね。 @linny: そう。例えば `ls | grep .txt | sort` を打つとする。プロセスは 3 つ作られる。でもシェルから見れば **ジョブは 1 つ** なんだ。`Ctrl+Z` を押すと、シェルはジョブ単位でまとめて一時停止してくれるよ。 ::: ::: highlight **フォアグラウンドとバックグラウンド** - **フォアグラウンド**(前面、foreground): あなたのキー入力を受け取る側。実行中は端末が返ってこない - **バックグラウンド**(裏、background): 画面の裏で動く側。端末はあなたが自由に使える レジに並ぶ列に例えると分かりやすい。フォアグラウンドは「今レジの前にいる人」。バックグラウンドは「番号札を持って別の場所で待っている人」だよ。 ::: ### ジョブ番号と PID の違い {#job-number-vs-pid} | 種類 | 例 | 付与する人 | 用途 | | ---------- | ------- | ---------- | ---------------------------- | | ジョブ番号 | `%1` | シェル | `fg %1` / `kill %1` 等で指定 | | PID | `12345` | カーネル | `kill 12345` / `ps` の出力 | ::: tip **覚え方**: ジョブ番号には先頭に `%` を付ける。`fg 1` ではなく `fg %1`。`kill 1` ではなく `kill %1`。 `kill 1` は「ジョブ 1 番」ではなく「PID 1 番のプロセス」への命令になる。PID 1 は systemd(システム全体の親玉)。一般ユーザーなら「Operation not permitted」で弾かれる。いずれにせよ、あなたが止めたかったジョブは止まらない。 ::: ## 2. Ctrl+Z:今すぐ一時停止する {#ctrl-z} > **結論**: `Ctrl+Z` は実行中のジョブを終わらせず、一時停止する。端末がすぐ手元に戻り、別のコマンドを打てる。 ::: dialogue @linny: 一番大事なキー操作だよ。`vim` でも `tail -f` でも何でもいい。1 つコマンドを起動して、動いている状態で `Ctrl+Z` を押してみよう。 @lina: えっ、止まっちゃうのは怖いです…元に戻せますか? @linny: 戻せる。「一時停止」は「終了」とは違う。冷凍庫に入れている状態だと思ってほしい。`fg` で解凍すれば、何事もなく動き出すよ。 ::: ### 試してみる {#try-ctrl-z} ```bash $ sleep 100 ``` `sleep 100` は 100 秒間なにもせずに待つコマンド。この 100 秒間、端末はふさがったままになる。 ここで `Ctrl+Z` を押す。 ```output ^Z [1]+ Stopped sleep 100 $ ``` `[1]+ Stopped` が出れば成功。`$` プロンプトが戻っていることに注目しよう。**端末が手元に戻った**。 ::: tip **`[1]+` の意味** - `[1]` = ジョブ番号 1 番 - `+` = 直近に操作したジョブ(`fg` / `bg` を番号なしで打つと、これが対象になる) - `Stopped` = 停止中(CPU を使っていない) ::: ### この状態で別コマンドを打てる {#do-other-things} ```bash $ ls $ pwd $ echo "別のことができる" ``` `sleep` は冷凍庫の中で止まったまま。その間に自由に他のコマンドを実行できる。これが `Ctrl+Z` の威力。 ## 3. fg:呼び戻す / bg:裏で再開する {#fg-bg} > **結論**: 停止したジョブは `fg` で前面に呼び戻す。`bg` なら停止したまま裏で再開する。vim は `fg` の往復が基本。 ::: dialogue @lina: 停止したジョブは、どうやって動かせばいいですか? @linny: 選択肢は 2 つ。**`fg`**(foreground の略)は「前面に呼び戻して再開」。**`bg`**(background の略)は「裏で再開」だよ。 @lina: 使い分けの基準は何ですか? @linny: あなたが画面に向き合って続けるなら `fg`。あなたが別の作業を続けるなら `bg` だね。 ::: ### fg:フォアグラウンドへ戻す {#fg} さっきの `sleep 100` を戻してみる。 ```bash $ fg ``` ```output sleep 100 ``` `sleep` の続きが始まる。端末は再びふさがる。残り時間を待ってもいい。もう一度 `Ctrl+Z` で止めてもいい。 ### bg:バックグラウンドで再開 {#bg} 「裏で動かしながら端末は使い続けたい」なら `bg` を使う。 ```bash $ sleep 100 ^Z [1]+ Stopped sleep 100 $ bg [1]+ sleep 100 & $ ``` 表示が `Stopped` から `&`(バックグラウンドで実行中)に変わった。`sleep` は裏で時計を進める。あなたは他の作業ができる。 ::: warning **`bg` できないコマンドもある**: `vim` のような画面全体を使う対話アプリは、裏でキー入力を待てない。そのため `bg` すると停止状態に戻ってしまう(`Stopped (tty input)`)。`vim` は **`Ctrl+Z` で停止 → 用が済んだら `fg`** の往復で使うこと。 ::: ## 4. & を末尾に付ける:最初からバックグラウンド {#ampersand} > **結論**: コマンドの末尾に `&` を付ければ最初から裏で実行できる。出力は `> file 2>&1` で逃がすのが定石。 ::: dialogue @linny: 最初から「これは裏で動かす」と決まっているなら、コマンドの末尾に **`&` を付けて起動** すると早いよ。 @lina: `Ctrl+Z` → `bg` の 2 ステップを、1 ステップで済ませるってことですか? @linny: そう、結果は同じ。`Ctrl+Z → bg` は「実行してから気が変わって裏に回す」。`&` は「最初から裏で動かす」という違いだけだね。 ::: ### & の使い方 {#use-ampersand} ```bash $ sleep 100 & [1] 12345 $ ``` `[1]` がジョブ番号。`12345` が PID。すぐにプロンプトが戻る。 ```bash $ jobs [1]+ Running sleep 100 & ``` `Running` 状態。止まらずに動いている。 ### よく使う実例:ログ収集を裏で {#example-log} ```bash $ tail -f /var/log/syslog > mylog.txt & [1] 23456 $ # この間に別の作業 $ vim config.txt ``` `tail -f` を裏で動かしながら、`vim` で設定を編集できる。こうした並行作業がやりやすくなる。 ::: warning **`&` だけだと出力が混ざる**: 標準出力(コマンドが出す普通の結果)は、リダイレクトしなければ **そのまま端末に流れる**。標準エラー出力(コマンドが出すエラーの文言)も同じ。あなたが別のコマンドを打っている最中に、突然その出力が割り込んでくる。長時間動かすなら `> file 2>&1` でファイルへ逃がすこと。 ::: ## 5. jobs:今動いてるものを全部見る {#jobs} > **結論**: `jobs` で現在のシェルが管理する全ジョブを一覧表示できる。個別指定は `%番号` で、`%` を必ず付ける。 ::: dialogue @lina: 複数のジョブを動かすと、どれがどれだか分からなくなりそうです。 @linny: そんなときは **`jobs`** 一発。今のシェルが管理しているジョブを、全部一覧で出してくれるよ。 ::: ### 基本:jobs {#jobs-basic} ```bash $ sleep 100 & [1] 12345 $ sleep 200 & [2] 12346 $ vim notes.txt # Ctrl+Z で停止 $ jobs [1] Running sleep 100 & [2]- Running sleep 200 & [3]+ Stopped vim notes.txt ``` 3 つのジョブが見える。 - `+` = 直近に操作したジョブ(番号なしの `fg` / `bg` の対象 = `vim`) - `-` = その 1 つ前のジョブ(= `sleep 200`) ### ジョブを個別に指定:%番号 {#job-spec} ```bash $ fg %1 # sleep 100 を前面に $ bg %3 # vim を裏に(停止状態のまま) $ kill %2 # sleep 200 を終了 ``` ::: warning **`kill` の前に確かめること** `kill` は GUI の「ウィンドウを閉じる」と違い、保存するか聞いてくれない。編集中のファイルがあると、保存していない内容はそのまま消える。 - 先に `jobs` で「本当にそのジョブでよいか」を確かめる - エディタなど作業中のジョブは、`kill` ではなく `fg` で戻して自分で保存してから終わらせる - 練習は `sleep` を相手にする。`sleep` には消えて困る中身が無いので、安心して試せる ::: ::: tip **% プレフィックスを忘れない** `kill 1` は **PID 1(systemd)へのコマンド** になる。一般ユーザーなら権限不足で弾かれる。root で実行しても systemd が設定を読み直す動きに入るだけで、狙ったジョブは止まらない。「ジョブを指定するときは必ず `%`」を体に覚えさせよう。 ::: ### jobs の便利オプション {#jobs-options} | オプション | 効果 | | ---------- | ------------------------------ | | `jobs -l` | PID も併せて表示 | | `jobs -p` | PID だけを表示(スクリプト用) | | `jobs -r` | 実行中ジョブだけ | | `jobs -s` | 停止中ジョブだけ | ## 6. ターミナルを閉じたらジョブはどうなる? {#hangup} > **結論**: 既定では SIGHUP でジョブは終了する。生き残らせるには `nohup` / `disown`。より確実なのは `tmux`。 ::: dialogue @lina: バックグラウンドで動かしたまま、ターミナルを閉じたらどうなるんですか? @linny: 普通は **終了する**。ターミナルの窓を閉じたり SSH が切れたりすると、シェルに **SIGHUP(ハングアップ信号)** が届く。シェルはそれを配下のジョブにも伝えるんだ。 @lina: シグナルって何ですか? @linny: シグナルは「プロセスに送る短い合図」のこと。「終わってください」「今すぐ止まれ」といった内容を、名前と番号で送るしくみだよ。 @lina: えっ、長い処理を裏で動かして油断していたら、全部消えちゃうんですか? @linny: そう。それを防ぐのが **`nohup`** や **`disown`** だよ。もっと根本的に解決したいなら **`tmux`** だね。 ::: ### nohup:起動時に SIGHUP を無視させる {#nohup} ```bash $ nohup ./long_script.sh > output.log 2>&1 & [1] 34567 ``` `nohup` を付けて起動すると、シェル終了時の SIGHUP を無視する。ログアウトしてもジョブは生き残る。 ### disown:起動後に「シェルの管理下から外す」 {#disown} ```bash $ ./long_script.sh & [1] 45678 $ disown %1 $ exit # ターミナルの窓を閉じても %1 は生き続ける ``` 「`&` で動かし始めたあとに、長時間処理へ切り替えたくなった」場合は `disown` で対応できる。 ::: tip **より確実なのは tmux** `nohup` / `disown` は「ターミナル切断時にジョブが終了しないようにする」だけ。一方 **`tmux`** は「画面ごとサーバ側に保存しておく」。だから後から `tmux attach` でその画面に戻り、続きを見られる。長い処理を流して後でログを見たいなら tmux が向いている。→ [tmux 入門](/articles/tutorials/tmux-basics) ::: ## 7. つまずきポイント集 {#pitfalls} > **結論**: Ctrl+C と Ctrl+Z の混同、`kill 1` 事故、出力の乱入、vim の bg 失敗が代表的な落とし穴。 ::: dialogue @lina: 先輩、よくある落とし穴ってありますか? @linny: 4 つ覚えておけば、だいたい網羅できるよ。 ::: ### ピンチ 1: Ctrl+C と Ctrl+Z を取り違える {#pitfall-1} ::: warning **症状**: 一時停止のつもりだったのに、ジョブが消えた。 **原因**: `Ctrl+C`(SIGINT で終了)と `Ctrl+Z`(SIGTSTP で一時停止)を混同した。 **対処**: 「**C = Cancel(中止)、Z = Z 字に寝かせる(停止)**」と覚える。`Ctrl+Z` は終了ではない。vim やエディタで押しても作業内容は保たれる。 ::: ### ピンチ 2: kill %1 を kill 1 と打って怖い思いをする {#pitfall-2} ::: warning **症状**: `kill 1` を実行して「Operation not permitted」と言われた。止めたかったジョブは動いたままだった。 **原因**: ジョブ番号と PID を取り違えて、`%` を忘れた。`kill 1` は PID 1(systemd)へシグナルを送る命令になる。 **対処**: **ジョブ指定には必ず `%` を付ける**。不安なら先に `jobs -l` で PID を確認する。`kill %%`(最後にアクセスしたジョブ)を使う手もある。 ::: ### ピンチ 3: バックグラウンドジョブの出力が画面に乱入 {#pitfall-3} ::: warning **症状**: `&` で動かしたコマンドの出力が、他のコマンドを入力している最中に割り込んでくる。 **原因**: バックグラウンドでも、標準出力と標準エラー出力は既定で端末に流れる。 **対処**: `> file 2>&1 &` でファイルへ逃がす。捨ててよいなら `> /dev/null 2>&1 &` にする。 ::: ### ピンチ 4: vim を bg したら止まったまま動かない {#pitfall-4} ::: warning **症状**: `vim` を `Ctrl+Z` → `bg` した。しかし `jobs` で見ると `Stopped (tty input)` のまま動かない。 **原因**: vim は端末の入力を待つアプリ。バックグラウンドは端末から切り離された状態なので、キー入力を受け取れない。そのためシェルが自動的に停止状態へ戻す。 **対処**: vim や top のような対話的アプリは **`Ctrl+Z` で停止 → 用が済んだら `fg`** の往復だけで使う。`bg` には回さない。 ::: ## 8. 実用テンプレ:ジョブ制御の事故らない型 {#template} > **結論**: vim の往復は `Ctrl+Z → 別作業 → fg`。長い処理は `& + > file 2>&1`。ログアウトを跨ぐなら nohup / disown / tmux。 ::: tip **コピペ用:vim 編集中に別コマンドを打つ型** ```bash # 1. vim で編集中 $ vim config.txt # Ctrl+Z で一時停止 # 2. 端末が戻ってきたので別作業 $ ls /etc/ $ grep "error" /var/log/syslog # 3. vim に戻る $ fg ``` vim を閉じずに往復するのが要点。「`Ctrl+Z` → 別作業 → `fg`」のリズムを覚えよう。 ::: ::: tip **コピペ用:長い処理を裏で動かす型** ```bash # 出力をファイルへ逃がしながら裏で実行 $ ./long_script.sh > output.log 2>&1 & [1] 12345 # 経過を確認 $ jobs $ tail -f output.log # 終了させたい場合 $ kill %1 ``` `> output.log 2>&1` を忘れると画面が荒れる。この 2 つはセットで覚える。 ::: ::: tip **コピペ用:ログアウト後も継続させる型** ```bash # 起動時から nohup で $ nohup ./long_script.sh > output.log 2>&1 & [1] 12345 # あるいは起動後に disown $ ./long_script.sh > output.log 2>&1 & [1] 12345 $ disown %1 # tmux で画面ごと持ち越すのがより確実(推奨) $ tmux $ ./long_script.sh # Ctrl+b → d でデタッチ、後で tmux attach で復帰 ``` ::: ## ミニ課題で手を動かそう {#practice} > **結論**: Ctrl+Z と fg の往復、3 ジョブの並行制御、出力をファイルへ逃がす実行の 3 課題で操作を定着させる。 ::: dialogue @linny: 知識だけ入れても定着しない。3 つ手を動かしてみよう。 ::: ::: warning **この課題を試す場所**: ジョブ制御はシェルの機能なので、本サイトの仮想ターミナルでは動かない。手元の Linux、WSL、macOS のターミナルで実行しよう。題材は `sleep` なので、失敗してもファイルは壊れない。 ::: ### 課題 1: Ctrl+Z → fg の往復 {#challenge-1} **やること**: `sleep 30` を実行しよう。途中で一時停止して別のコマンドを打ち、そのあと元に戻そう。 :::details ヒント1(方向づけ)を見る 「終了」ではなく「一時停止」のキーを使う。止めたあとは、一覧で状態を確かめてから元に戻そう。 ::: :::details ヒント2(コマンド名)を見る 一時停止は `Ctrl+Z`。状態の確認は `jobs`。元に戻すのは `fg`。 ::: :::details 答えを見る ```bash $ sleep 30 # 5 秒経ったところで Ctrl+Z で一時停止 $ jobs # → [1]+ Stopped sleep 30 を確認 $ ls # 別コマンドを打ってみる $ fg # sleep に戻って残り時間を待つ ``` ::: ### 課題 2: 3 つのジョブを並行で動かす {#challenge-2} **やること**: 3 つのコマンドを最初から裏で動かそう。そのうち真ん中の 1 つだけを終了させよう。 :::details ヒント1(方向づけ)を見る 最初から裏で動かす書き方がある。終了させるときは、ジョブの一覧で番号を確かめてから指定しよう。 ::: :::details ヒント2(コマンド名)を見る 裏で動かすのは末尾の `&`。一覧は `jobs`。終了は `kill %2` のように `%` を付けて指定する。 ::: :::details 答えを見る ```bash $ sleep 100 & $ sleep 200 & $ sleep 300 & $ jobs # → [1] [2] [3] の 3 つが見えるはず $ kill %2 # 真ん中だけ終了 $ jobs # → [2] が消えていることを確認 ``` ::: ### 課題 3: 出力をファイルへ逃がしながら裏で実行 {#challenge-3} **やること**: 出力のあるコマンドを裏で動かそう。他のコマンドの出力と混ざらないよう、結果をファイルへためよう。 :::details ヒント1(方向づけ)を見る 裏で動かすだけでは、出力が画面に割り込んでくる。出力の行き先をファイルへ変える書き方を思い出そう。 ::: :::details ヒント2(コマンド名)を見る 行き先の変更は `> ファイル名 2>&1`。裏で動かすのは末尾の `&`。ためた内容は `cat` で見られる。 ::: :::details 答えを見る ```bash $ ls -R / > result.log 2>&1 & [1] 12345 $ jobs # Running を確認 $ tail result.log # ファイルへ出力が溜まっていく # ジョブが終わったら自動的に # [1]+ Done ls -R / > result.log 2>&1 # と通知される ``` `2>&1` を付けているので、権限が無くて読めないディレクトリのエラーもファイル側へ流れる。画面には出てこない。 ::: ::: dialogue @lina: できました!`Ctrl+Z` の安心感、半端ないですね…これさえ知っていれば、vim を `Ctrl+C` で殺す事故は二度と起きません。 @linny: それが「ジョブ制御を覚える」という体験だよ。シェルを使う日常が一段階楽になる。 ::: ## 今日の 3 行まとめ {#summary} - **`Ctrl+Z` で一時停止して端末を解放**、戻るときは **`fg`**、裏で動かすなら **`bg`**。これだけで vim を `Ctrl+C` で殺す事故が消える - **ジョブ指定には必ず `%`**(`kill %1` と `kill 1` は別物)。PID と取り違えると init を終了させかねない - 長い処理を裏で動かすなら **`& + > file 2>&1`** を癖にする。ログアウトを跨ぐなら **`nohup` / `disown` / `tmux`** を選ぶ ## 次に学ぶこと {#next} - [ps・top・killの使い方](/articles/tutorials/process-management-basics) - プロセスの概念全般を体系的に - [プロセス管理の実践(pkill / nice / 優先度)](/articles/tutorials/process-management-practical) - 一段先のプロセス制御テクニック - [tmux 入門 - ターミナル多重化の基本](/articles/tutorials/tmux-basics) - SSH 切断にも耐える「画面の保存」を覚える - [Penguin Gym Linux で実践練習](/terminal) - 仮想ターミナルで `ps` / `kill` を試す(ジョブ制御は手元の Linux や WSL のターミナルで実行しよう) # journalctlの使い方 - Linuxログ調査の基本と障害対応 Source: https://penguin-gym-linux.com/articles/tutorials/journalctl-basics ## この記事で解決できること {#intro} - Ubuntuで「どこにログがあるのか分からない」状態を卒業できます。 - `journalctl` でサービス障害の原因を最短で追えます。 - 「直近だけ見る」「時間で絞る」「再現しながら追う」といった実務の型が身につきます。 - OOM やカーネル異常をカーネルログから確認できます。 ::: tip **結論(最短ルート)** 障害調査でまずやることはだいたいこれです。 1. **サービスが死んでるか:** `systemctl status ` 2. **直近ログ:** `journalctl -u -n 200` 3. **再現しながら追う:** `journalctl -u -f` 4. **OS側の異常(OOM等):** `journalctl -k | grep -i oom` ::: ::: warning **前提(対象環境)** - OS:Ubuntu - 対象:サーバ触り始めた新人 - systemd が使われている(多くのUbuntuでそう) - `sudo` できる前提(ログが読めない場合があるため) ::: ::: tip **この記事のコマンドはすべて「読むだけ」です** `journalctl` はログを表示するコマンドです。 サービスを止めたり設定を書き換えたりはしないので、本番サーバでも安全に実行できます。 唯一の注意点は、行数や期間を絞らずに実行すると出力が大量になることです。絞り方は本文で説明します。 ::: ## 0. journalctl って何?(最小限) {#what} > **結論**: systemdのログはjournald に集約され、それを読むのが`journalctl`。起動失敗やOOMはjournalに出やすい。 Ubuntuでは、サービスのログがファイル(/var/log/〜)ではなく、**journald(ジャーナル)** に集約されていることがあります。 その "まとめログ" を読むのが `journalctl` です。 **先に用語を整理します。** | 用語 | 一行の意味 | 補足 | | ------------------ | ------------------------------------------------------ | ----------------------------------------------------- | | systemd | Linux の起動処理とサービスを管理する仕組み | 「init システム」とも呼ばれます | | journald | systemd がログを集めて保管するデーモン | 正式なユニット名は `systemd-journald` です | | journal(ジャーナル)| journald が保管しているログの入れ物 | 「ジャーナルログ」と呼ばれることもあります | | ユニット(unit) | systemd が管理する対象の総称 | `-u` オプションの `u` はこの unit の頭文字です | ::: tip Nginx/Apacheなどはファイルログもありますが、「サービスの起動失敗」「設定エラー」「OOMで落ちた」などは journal に出ることが多いです。 ::: ## 1. まず service 名を確定する(ここで迷う人が多い) {#service-name} > **結論**: ログ調査はまず正確なサービス名の特定から。`systemctl status`の出力で名前を確認できる。 例: - nginx → `nginx` - apache → `apache2` - ssh → `ssh` - php-fpm → `php8.1-fpm` など(環境で変わる) まず状態を見て名前を確認します。 ```bash $ sudo systemctl status nginx ``` この画面に、だいたいサービス名が出ます。 ## 2. "直近だけ"を見る(新人が一番使う) {#recent} > **結論**: `journalctl -u -n 200`で直近ログを確認。まず200行程度が過不足なく見やすい。 `-u` は「このサービス(ユニット)のログだけ」という指定です。 `-n 200` は「末尾から 200 行だけ」という指定です。 ### 2-1. 直近200行(サービス指定) ```bash $ sudo journalctl -u nginx -n 200 ``` ```output Dec 15 13:02:11 web01 systemd[1]: Starting A high performance web server and a reverse proxy server... Dec 15 13:02:11 web01 systemd[1]: Started A high performance web server and a reverse proxy server. Dec 15 13:41:55 web01 nginx[1234]: 2025/12/15 13:41:55 [error] 1234#1234: *12 open() "/var/www/html/missing.html" failed (2: No such file or directory) ``` 1 行は「日時 / ホスト名 / プロセス名[PID] / メッセージ」の順に並びます。 Apacheの場合: ```bash $ sudo journalctl -u apache2 -n 200 ``` ### 2-2. もっと短く(直近50行) ```bash $ sudo journalctl -u nginx -n 50 ``` ::: tip 最初は200行くらいがちょうど良いです。少なすぎると「肝心のエラーが出てない」がよく起きます。 ::: ## 3. "再現しながら追う" のが最強(-f) {#follow} > **結論**: `journalctl -u -f`でログを追いながら別タブで再現すると、原因に最短で当たれる。 これが最短で原因に当たります。 ```bash $ sudo journalctl -u nginx -f ``` `-f`(follow)は新しいログが出るたびに画面へ追記し続けるモードです。 終了するときは `Ctrl+C` を押します。ログを見ているだけなので、途中で止めてもサービスには影響しません。 別タブで `curl` やブラウザで再現 → 直後のログを見る。 これで、原因がほぼ見えます。 ## 4. 時間で絞る(調査が一気に速くなる) {#time} > **結論**: 壊れた時刻が分かるなら`--since`/`--until`で時間を絞る。調査速度が一気に上がる。 「いつ壊れたか分かる」場合は絶対に時間で絞るべきです。 ### 4-1. 直近1時間 ```bash $ sudo journalctl -u nginx --since "1 hour ago" ``` ### 4-2. 今日だけ ```bash $ sudo journalctl -u nginx --since "today" ``` ### 4-3. 明確な時間帯(例) ```bash $ sudo journalctl -u nginx --since "2025-12-15 13:00" --until "2025-12-15 14:00" ``` ::: warning `--since` の時刻はサーバのタイムゾーンで解釈されます。 サーバが UTC 設定だと、手元の時計と数時間ずれることがあります。 迷ったら `timedatectl` で現在のタイムゾーンを確認してください(これも表示するだけのコマンドです)。 ::: ## 5. エラーだけ見たい(-p で優先度を絞る) {#error} > **結論**: `-p err` で優先度から機械的に絞り、足りなければ grep で文字列を重ねる。 ### 5-1. 優先度で絞る(journalctl 標準の方法) `journalctl` はログ 1 行ごとに「優先度(priority)」を記録しています。 これは本文とは別のデータなので、`-p` で機械的に絞り込めます。 ```bash $ sudo journalctl -u nginx -p err --since "today" ``` 優先度は重大な順に `emerg` / `alert` / `crit` / `err` / `warning` / `notice` / `info` / `debug` の 8 段階です。 `-p err` は「err 以上(より重大)」の行だけを表示します。本文に "error" という語がないエラー行も拾えます。 ### 5-2. 文字列で絞る(grep との併用) `-p` で絞ってもまだ多いときは、文字列でさらに拾います。 ```bash $ sudo journalctl -u nginx --since "today" | grep -iE "error|fail|fatal|panic|denied|refused|timeout" ``` `grep -i` は大文字小文字を区別しない指定、`-E` は `|`(または)を使える指定です。 ::: tip grepは万能ではないですが、初動の当たり付けには強いです。 ::: ## 6. OS側の異常:OOM / kernel ログを見る(決定打になりがち) {#kernel} > **結論**: アプリ突然死の裏でOOMやカーネル異常が起きていることがある。`journalctl -k`で確認する。 アプリが突然落ちる、プロセスが死ぬ、502が増える、などの裏で OOM Killer(メモリ不足)やカーネル異常が起きていることがあります。 OOM Killer とは、メモリが足りなくなったときに Linux が自分でプロセスを強制終了する仕組みです。 「OOM」は Out Of Memory(メモリ不足)の略で、アプリ側のログには何も残らないことがあります。 ### 6-1. カーネルログ(-k) ```bash $ sudo journalctl -k -n 200 ``` ### 6-2. OOMだけ拾う ```bash $ sudo journalctl -k | grep -i oom | tail -n 50 $ sudo journalctl -k | grep -i "killed process" | tail -n 50 ``` ## 7. "起動失敗" を調べる(よくある) {#boot} > **結論**: 起動失敗時は`systemctl status`とjournalをセットで見る。`-b`で今回起動分だけに絞れる。 サービスが起動しないときは、statusとjournalをセットで見ます。 ### 7-1. status(要点がまとまってる) ```bash $ sudo systemctl status nginx ``` ### 7-2. 直近の起動ログ(サービス指定) ```bash $ sudo journalctl -u nginx -n 200 ``` ### 7-3. 直近の起動試行だけ見たい(-b と組み合わせ) 「今回の起動(ブート)だけ」のログが欲しい時: ```bash $ sudo journalctl -u nginx -b -n 200 ``` ::: tip `-b` は "今回起動してからのログ" という意味です。 ::: ## 8. 失敗例(あるある)と対策 {#failures} > **結論**: ログが出ない・多すぎる・grepで出ない等は典型。`sudo`付与、時間/行数絞り、生ログ確認で解決する。 ### 8-1. "ログが出ない" - そもそもそのサービスがjournaldに出していない(ファイルログのみ) - 権限が足りず読めない **対策:** - `sudo` を付ける - Nginx/Apacheは `/var/log/nginx/` や `/var/log/apache2/` も見る ### 8-2. "ログ多すぎて読めない" **対策:** - `--since` で時間絞り - `-n` で行数絞り - `-u` でユニット絞り - 再現して `-f` で追う(最短) ### 8-3. "grepで何も出ない"のに壊れてる **対策:** - grepの条件が狭いだけのことが多いので、まず生ログを `-n 200` で見る - statusのエラー行を見る ::: warning **事故防止:「やってはいけない」** - **再起動を連打してログを流す** - ログが流れて原因が見えなくなります。まず `journalctl -u -n 200` を取ってから。 - **時間を絞らずに全部読む** - 時間が溶けます。`--since` を使うだけで生産性が跳ねます。 - **アプリのログだけ見てOS側を見ない** - OOMやカーネル異常はアプリログに出ないことが多いです。`journalctl -k` は必ず使う。 ::: ## 9. ログが消える・保存されない場合 {#persistence} > **結論**: journal は既定で再起動をまたがない構成があり、`journalctl -b -1` の可否で保存有無を判断する。 「再起動したらログが消えた」ときは、journald の保存先が原因のことがあります。 journald は保存先を `/run/log/journal`(メモリ上・再起動で消える)か `/var/log/journal`(ディスク上・再起動後も残る)のどちらかにします。 まず、前回起動時のログが読めるかを確認します。 ```bash $ sudo journalctl -b -1 -n 20 ``` 前回分が表示されれば保存されています。 前回起動が見つからない旨のメッセージが返る場合、そのサーバでは再起動をまたいで保存されていません。 ::: warning 保存を有効にするには `/etc/systemd/journald.conf` の変更と journald の再起動が必要です。 設定変更はサーバの挙動を変える操作なので、本番では変更前のファイルを控え、ディスク使用量の上限(`SystemMaxUse`)とあわせて検討してください。 調査目的なら、まず `journalctl` の出力をファイルへ書き出して保全する方が安全です。 ::: ```bash $ sudo journalctl -u nginx -n 2000 > ~/nginx-journal-$(date +%Y%m%d).log ``` ## 10. 調査完了チェックリスト {#checklist} > **結論**: 状態・絞り込み・時刻・OS 側・保全の 5 点を押さえれば、ログ調査は次の工程へ進んでよい。 - [ ] `systemctl status ` で現在の状態を確認した - [ ] `-u` でサービスを絞り、`-n` または `--since` で範囲を絞って読んだ - [ ] `timedatectl` でタイムゾーンを確認し、事象の時刻とログの時刻を突き合わせた - [ ] `journalctl -k` で OOM やカーネル異常の有無を確認した - [ ] 必要なログをファイルへ書き出して保全した ::: tip **コピペ用:journalctl 調査テンプレ** ```bash # 1) まず状態 sudo systemctl status # 2) 直近ログ sudo journalctl -u -n 200 # 3) 再現しながら追う(最強) sudo journalctl -u -f # 4) 今日だけ sudo journalctl -u --since "today" # 5) OS側(OOMなど) sudo journalctl -k | grep -i oom | tail -n 50 sudo journalctl -k | tail -n 200 ``` ::: ## 次に読む {#next} - [サービスの起動・停止・状態確認](/articles/tutorials/systemctl-basics) - [Nginx/Apacheのログの見方](/articles/troubleshooting/nginx-apache-log) - [メモリ不足のときのOOM確認](/articles/troubleshooting/memory-troubleshooting) # journalctl 実践フィルタ - 大量ログから必要な行を抜く Source: https://penguin-gym-linux.com/articles/tutorials/journalctl-practical ## この記事で解決できること {#intro} journalctl は systemd のログ管理ツールで、正しいフィルタを組み合わせれば、数百万行のログから目的の行を数秒で抽出できる。以下が実務で最頻出のコマンドパターンだ。 ::: tip **よく使う組み合わせ(コピペ用)** ```bash # 直近1時間のnginxエラー journalctl -u nginx -p err --since "1 hour ago" # 今日のブートから全エラー journalctl -b -p err # リアルタイムでユニット監視 journalctl -u myapp.service -f ``` ::: ## ユニットを絞るには {#unit} 特定のサービスだけを見るには `-u` オプション。最も使用頻度が高い。 ```bash # 単一ユニット journalctl -u nginx.service # 複数ユニットを同時指定 journalctl -u nginx.service -u php-fpm.service ``` ユニット名は `.service` を省略できる(`-u nginx` も動く)。 ## 時間範囲を指定するには {#time} `--since` / `--until` で期間を絞る。相対表記と絶対表記の両方が使える。 ```bash # 直近1時間 journalctl --since "1 hour ago" # 直近30分 journalctl --since "30 min ago" # 特定日時から現在まで journalctl --since "2026-05-30 14:00:00" # 範囲指定 journalctl --since "2026-05-30 10:00" --until "2026-05-30 12:00" ``` ::: tip `-S` は `--since`、`-U` は `--until` の短縮形。スクリプト内では長い形式のほうが可読性が高い。 ::: ## エラーだけを抜くには {#priority} `-p` で syslog 優先度レベルを絞る。指定したレベル**以上**の深刻度が表示される。 ```bash # errレベル以上(err, crit, alert, emerg) journalctl -p err # warningレベル以上 journalctl -p warning # 特定レベルのみ(範囲指定) journalctl -p err..err ``` | 数値 | キーワード | 意味 | | ---- | ---------- | ---------------- | | 0 | emerg | システム使用不可 | | 1 | alert | 即座の対処必要 | | 2 | crit | 重大なエラー | | 3 | err | エラー | | 4 | warning | 警告 | | 5 | notice | 通常だが重要 | | 6 | info | 情報メッセージ | | 7 | debug | デバッグ | ## リアルタイムで監視するには {#follow} `-f` は `tail -f` 相当。新しいログが出力されるたびに追加表示される。 ```bash # デプロイ後のリアルタイム監視 journalctl -u myapp.service -f # エラーのみリアルタイム監視 journalctl -u myapp.service -p err -f ``` `Ctrl+C` で終了。`-n` で最新 N 行から開始できる。 ```bash # 最新50行から始めてフォロー journalctl -u nginx -n 50 -f ``` ## ブート単位で確認するには {#boot} `-b` は「現在のブートのログ」に絞る。再起動を跨いだ比較は `--list-boots` で番号を確認してから指定する。 ```bash # 現在のブートのログ journalctl -b # 過去のブート一覧を確認 journalctl --list-boots # 1回前のブート journalctl -b -1 # 2回前のブート journalctl -b -2 ``` ```output -3 d7a3f8... Mon 2026-05-27 09:00 JST—Mon 2026-05-27 18:30 JST -2 a1b2c3... Tue 2026-05-28 08:45 JST—Tue 2026-05-28 20:00 JST -1 e5f6a7... Wed 2026-05-29 09:10 JST—Wed 2026-05-29 23:45 JST 0 b8c9d0... Thu 2026-05-30 08:00 JST—present ``` ## フィールド・プロセスで絞るには {#field} journalctl はフィールドキーを直接指定してフィルタできる。`man systemd.journal-fields` に全フィールドが記載されている。 ```bash # PIDを指定 journalctl _PID=12345 # コマンド名で絞る journalctl _COMM=sshd # syslogの識別子で絞る journalctl SYSLOG_IDENTIFIER=nginx # UID 1000 のプロセスのログ journalctl _UID=1000 ``` ::: tip `journalctl -F _COMM` で `_COMM` フィールドに存在する値の一覧を確認できる。どんな識別子があるか不明なときに便利。 ::: ## grep と組み合わせるには {#grep} `--grep` オプション(v233 以降)か、パイプで grep する。 ```bash # 組み込みの --grep journalctl -u nginx --grep "error" # 正規表現使用 journalctl -u nginx --grep "5[0-9][0-9]" # パイプで grep journalctl -u nginx | grep -i "connection refused" # 時間とgrepの組み合わせ journalctl --since "2 hours ago" | grep "ERROR" ``` `--grep` はメッセージフィールドのみを検索する。フィールド全体を検索したい場合はパイプ経由の grep が確実。 ## 出力形式を変えるには {#output} `-o` で出力形式を制御する。ログ解析や他ツールとの連携に使う。 ```bash # JSON形式(1ログ = 1行) journalctl -u nginx -o json # 整形済みJSON journalctl -u nginx -o json-pretty # タイムスタンプを短縮表示 journalctl -o short # メッセージのみ(メタデータなし) journalctl -o cat ``` JSON 形式は jq でパースするときに有用。 ```bash # エラーのタイムスタンプとメッセージだけ抽出 journalctl -u nginx -p err -o json | jq -r '.__REALTIME_TIMESTAMP + " " + .MESSAGE' ``` ::: warning `--no-pager` をつけないとページャーが起動する。スクリプト内では必須。 ```bash journalctl -u nginx --no-pager | wc -l ``` ::: ## まとめ:よく使うフィルタ早見表 {#summary} フィルタは自由に組み合わせられる。まず `-u` でユニットを絞り、`-p err` でエラーだけに絞り込むのが実務での基本パターン。 | 目的 | コマンド | | ---------------- | ------------------------------------ | | ユニット指定 | `journalctl -u nginx` | | 時間指定 | `journalctl --since "1 hour ago"` | | エラーのみ | `journalctl -p err` | | 現在ブート | `journalctl -b` | | フォロー | `journalctl -f` | | PID指定 | `journalctl _PID=1234` | | grepと組み合わせ | `journalctl -u nginx --grep "error"` | | JSON出力 | `journalctl -o json` | | ページャー無効 | `journalctl --no-pager` | ```bash # 実務でよく使う組み合わせ例 journalctl -u nginx.service -p err --since "today" --no-pager journalctl -b -p warning -u ssh.service -f ``` ## 次に読む {#next} - [journalctlの使い方 - ログ調査の基本](/articles/tutorials/journalctl-basics) - [systemd ユニットファイルを書く](/articles/tutorials/systemd-unit-creation) - [systemd timer と cron の使い分け](/articles/tutorials/systemd-timer-vs-cron) # jq 入門 - シェルで JSON を扱う Source: https://penguin-gym-linux.com/articles/tutorials/jq-basics ## この記事で解決できること {#intro} - `curl` や API 出力の JSON を **シェルから読みやすい形** に整形できる - `select` / `map` / `-r` など **jq の必須語彙** を最短で身につく - パイプ運用で「クオートが付いてシェルに渡せない」「文字化けする」などの **定番事故を防ぐ型** が手に入る ::: tip **結論(実務の型)** - とりあえず整形 → `jq .` - 値だけ抜く → `jq -r '.field'` - 配列を 1 行 1 JSON にバラす → `jq -c '.[]'` - 条件で絞る → `jq '.[] | select(.status=="ok")'` - 取得失敗時に止めない → `jq '.field // empty'` ::: ::: warning **前提(対象環境)** - jq 1.6 以降(Ubuntu 20.04 以降の `apt install jq` で十分。1.7 系の機能差は本文で都度言及) - 公式マニュアル: [jq Manual](https://jqlang.org/manual/) ::: ## jq とは何か? {#what} jq は **JSON 専用のフィルタ言語兼コマンド**。`grep` や `awk` で JSON を無理に切るより、構造を理解した上で値を抜けるため、API 連携・ログ集計・CI スクリプトで事故が減る。 - 標準入力 → フィルタ → 標準出力という Unix パイプの素直なモデル - フィルタ式は **左から右へ** 評価される(パイプと同じ感覚) - 数値・文字列・配列・オブジェクト・null・boolean を扱える型システムを持つ ## jq をどうインストールするか? {#install} 最短は OS のパッケージマネージャ。バージョン違いで挙動差があるため、スクリプトに組み込む場合は `jq --version` で揃えるのが安全。 ```bash # Ubuntu / Debian $ sudo apt update && sudo apt install jq # RHEL / Rocky / AlmaLinux $ sudo dnf install jq # macOS (Homebrew) $ brew install jq # バージョン確認 $ jq --version ``` ::: tip 古い CentOS 7 系では epel-release 経由になる。CI 用に静的バイナリが欲しいときは [公式 Releases](https://github.com/jqlang/jq/releases) の単一ファイルバイナリを `/usr/local/bin/jq` に置くのが速い。 ::: ## 基本フィルタの読み方は? {#basic-filter} jq のフィルタは「**入力に対する変換式**」。何も書かなければ恒等関数(整形して返す)、`.field` でオブジェクトから値を取り、`.[]` で配列を展開する。 ### 1. 整形して表示する ```bash $ echo '{"name":"linny","age":3}' | jq . ``` ```output { "name": "linny", "age": 3 } ``` ### 2. オブジェクトからキーで取り出す ```bash $ echo '{"name":"linny","age":3}' | jq '.name' ``` ```output "linny" ``` ### 3. 配列を 1 要素ずつバラす ```bash $ echo '[{"id":1},{"id":2}]' | jq '.[]' ``` ```output {"id":1} {"id":2} ``` ::: warning シングルクォートで囲むのは **シェル変数展開を抑える** ため。ダブルクォートを使うと `$` や ` ` `が解釈されて事故る。jq の式は常に`'...'` で囲うのが鉄則。 ::: ## オブジェクトと配列をどう扱うか? {#object-array} `.[].field` で配列内の各要素から値を抜き、`,` で複数式を並列に評価、`|` でフィルタを連結する。`grep | awk` の感覚そのまま。 ### 配列の各要素から値を抜く ```bash $ echo '[{"id":1,"tag":"a"},{"id":2,"tag":"b"}]' \ | jq '.[].tag' ``` ```output "a" "b" ``` ### 複数フィールドをタプルで取り出す ```bash $ echo '[{"id":1,"tag":"a"},{"id":2,"tag":"b"}]' \ | jq '.[] | {id, tag}' ``` ```output {"id":1,"tag":"a"} {"id":2,"tag":"b"} ``` `{id, tag}` は `{id: .id, tag: .tag}` の省略記法。**スネークケースなど予約語以外のキー** に限る。 ### ネストの取り出し(安全演算子 `?`) ```bash $ echo '{"a":{"b":{"c":42}}}' | jq '.a.b.c' 42 # 存在しないキーは null だが、配列添字エラーは ? で抑制 $ echo '{}' | jq '.a.b?.c?' null ``` ## select でどう絞り込むか? {#select} `select(条件)` は **条件が真の値だけを通す** フィルタ。配列を `.[]` で展開してから渡すのが定石。SQL の `WHERE` 句に近い役割を持つ。 ### 等値で絞る ```bash $ echo '[{"s":"ok"},{"s":"ng"},{"s":"ok"}]' \ | jq '.[] | select(.s=="ok")' ``` ```output {"s":"ok"} {"s":"ok"} ``` ### 数値比較・複合条件 ```bash $ jq '.[] | select(.score >= 80 and .active)' $ jq '.[] | select(.tag=="a" or .tag=="b")' $ jq '.[] | select(.tag | startswith("v1"))' ``` ### キーの有無で絞る ```bash $ echo '[{"a":1},{"b":2}]' \ | jq '.[] | select(has("a"))' ``` ```output {"a":1} ``` ::: tip **フィルタ後にフィールドを抜く型** ```bash jq '.[] | select(.status=="error") | .message' ``` 「絞ってから取り出す」順序を守るとデバッグしやすい。最後に `// empty` を付けると `null` を出さずに済む。 ::: ## map と length と keys で何ができるか? {#map-length-keys} `map(f)` は配列の各要素に `f` を適用し、`length` は長さ、`keys` はオブジェクトのキー一覧を返す。**配列全体に処理を流す** ときに使う。 ### map:配列を加工する ```bash $ echo '[1,2,3]' | jq 'map(. * 10)' ``` ```output [ 10, 20, 30 ] ``` `map(f)` は `[.[] | f]` と等価。`.[] | f` だと **1 要素ずつ流れて** くるのに対し、`map` は **配列のまま** 返るのが違い。 ### length:要素数を数える ```bash $ echo '[{"id":1},{"id":2},{"id":3}]' | jq 'length' 3 ``` 文字列なら文字数、オブジェクトならキー数、null なら 0 を返す。型ごとに意味が変わる点に注意。 ### keys:キー一覧を取り出す ```bash $ echo '{"a":1,"c":3,"b":2}' | jq 'keys' ``` ```output [ "a", "b", "c" ] ``` `keys` は **ソート済み**、`keys_unsorted` は挿入順。再現性が必要なら `keys` の方が安全。 ## 出力をどう整形するか?(-r / -c) {#output} 人間が読む整形は `jq .`、シェル変数に渡すなら `-r`(raw 出力)、grep や `xargs` に流すなら `-c`(compact)。**出力モードの選び方で 8 割の事故が決まる**。 ### -r:文字列のクオートを外して生で出す ```bash $ echo '{"name":"linny"}' | jq '.name' "linny" $ echo '{"name":"linny"}' | jq -r '.name' linny ``` シェルで `NAME=$(...)` のように受けるなら **必ず `-r`**。`"linny"` を変数に入れると展開時にダブルクォート混入する。 ### -c:1 行 1 JSON で出す ```bash $ echo '[{"id":1},{"id":2}]' | jq -c '.[]' ``` ```output {"id":1} {"id":2} ``` `-c` は **行指向ツールとの連携用**。`while read line` ループや `xargs -I {}` での再投入に向く。 ### tsv / csv 出力(@tsv / @csv) ```bash $ echo '[{"id":1,"name":"a"},{"id":2,"name":"b"}]' \ | jq -r '.[] | [.id, .name] | @tsv' ``` ```output 1 a 2 b ``` `@csv` は値をクオートし、`@tsv` はタブ区切り。**配列に詰めてから渡す** のが正しい使い方。 ::: warning `-r` は **文字列のみ** クオートを外す。数値・オブジェクトはそのまま JSON として出る。配列を行に展開したいなら `.[]` を必ず挟む。 ::: ## 実務でよく使うパターン {#practical} API レスポンスの整形、ログの集計、設定ファイル更新の 3 つは現場で頻出。**型を覚えておけばコピペで使い回せる**。 ### curl で取って必要なフィールドだけ抜く ```bash $ curl -sS "https://api.example.com/users" \ | jq -r '.users[] | "\(.id)\t\(.name)"' ``` `-sS` は静かに(成功時無音、エラー時のみ表示)。`"\(.id)\t\(.name)"` の **文字列補間** で自由な区切り文字を作れる。 ### group_by で集計する ```bash $ jq '[.[] | {status}] | group_by(.status) | map({status: .[0].status, count: length})' ``` `group_by(f)` は **同じ f の結果でグループ化** した配列の配列を返す。`map({key: ..., count: length})` でカウント表に整形する。 ### 設定ファイルの一部を書き換える ```bash # package.json の version を更新 $ jq '.version = "1.2.3"' package.json > package.json.tmp \ && mv package.json.tmp package.json ``` ::: danger **`jq ... file > file` は中身が消える**。シェルが先に `>` で空ファイルを作るため。**必ず一時ファイル経由** か `sponge`(moreutils)を使う。 ::: ```bash # sponge を使う安全版 $ jq '.version = "1.2.3"' package.json | sponge package.json ``` ### 配列に要素を追加する ```bash $ echo '{"items":[1,2]}' | jq '.items += [3]' ``` ```output { "items": [ 1, 2, 3 ] } ``` `|=` は **再代入オペレータ**、`+=` は **加算代入**。`.items |= map(. * 2)` のように深い場所を一括変換できる。 ## よくある落とし穴は? {#pitfalls} `null` の伝播・型エラー・引数渡しの 3 つで詰まる人が多い。**エラーメッセージを読んでから検索する** だけで解決時間が半分になる。 ### 1. null をパイプに流して `jq: error (at :0): Cannot iterate over null` ```bash # .users が存在しない場合 $ echo '{}' | jq '.users[]' jq: error (at :1): Cannot iterate over null (null) ``` 対処:`// []` でデフォルト値を与える。 ```bash $ echo '{}' | jq '.users // [] | .[]' # 何も出力されない(正常終了) ``` ### 2. シェル変数を式に埋め込みたい ```bash # NG: クオートが衝突する $ KEY="name" $ jq ".$KEY" file.json # シェル展開後に '."name"' になることがある # OK: --arg で明示的に渡す $ jq --arg key "$KEY" '.[$key]' file.json ``` `--arg` は **文字列として**、`--argjson` は **JSON として** 値を渡す。数値や配列を渡したいなら `--argjson`。 ### 3. 文字列が配列っぽく見えるのに `.[]` できない ```bash $ echo '"abc"' | jq '.[]' jq: error (at :1): Cannot iterate over string ("abc") ``` 文字列は配列ではない。**型を確認するには `type` フィルタ** を使う。 ```bash $ echo '"abc"' | jq 'type' "string" ``` ### 4. 終了コードを判定したい `jq -e` は **出力が `false` か `null` のとき終了コード 1** を返す。条件分岐に組み込める。 ```bash $ echo '{"ok":false}' | jq -e '.ok' || echo "失敗" false 失敗 ``` ::: tip **コピペ用:安全テンプレ** ```bash # 整形してページャに流す jq . file.json | less # 値を変数に受ける(必ず -r) NAME=$(curl -sS "$URL" | jq -r '.name') # 配列を 1 行 JSON にして xargs に流す curl -sS "$URL" | jq -c '.users[]' \ | xargs -I {} sh -c 'echo "USER: {}"' # null 安全に値を抜く jq -r '.path.to.value // "DEFAULT"' ``` ::: ## 次に読む {#next} - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [xargs 実践活用 - 標準入力をコマンド引数に変換する](/articles/tutorials/xargs-practical) - [sort と uniq の使い方 - データを並べ替えて重複を削る](/articles/tutorials/sort-uniq-basics) - [sed 入門 - ストリームエディタでテキスト置換](/articles/tutorials/sed-basics) # less/more/tail でログを読む - ページャ徹底活用 Source: https://penguin-gym-linux.com/articles/tutorials/less-more-tail ## ログって怖くない!まずは読んでみよう {#intro} ::: dialogue @lina: ライニー先輩、「ログを確認して」って言われたんですけど、ログファイルって何万行もあってどうやって読めばいいんですか? @linny: それは `less` コマンドの出番だよ!`cat` でドバッと全部出すんじゃなくて、ページをめくるように読めるんだ。 ::: ログファイルは長い。`cat` で開いたら画面が流れて終わる。 そこで登場するのが**ページャコマンド**。`less`・`more`・`tail` の3つを覚えれば、どんな長いファイルも怖くない。 ## この記事でわかること {#what-you-will-learn} - `less` でログをページ送り・検索しながら読む方法 - `more` の特徴と `less` との使い分け - `tail` でファイル末尾(最新部分)だけを表示する方法 - `tail -f` でログをリアルタイム監視する方法 - エラーログを調べる実践的な手順 ## less の基本 - ページャの王様 {#less} > **結論**: `less` はページ送り・前後移動・キーワード検索ができるページャの王様で、迷ったらこれを使えばよい。 ::: dialogue @lina: `less` って名前が面白いですね。`cat` より少ない? @linny: 「less is more(少ない方が豊か)」って言葉から来てるんだよ。`more` というコマンドがすでにあったから、それより高機能なのに `less` って名付けたんだ。 @lina: 逆じゃないですか! @linny: Linux あるある(笑) ::: ### less を起動する ```bash less /var/log/syslog ``` ```output Nov 1 10:00:01 myserver systemd[1]: Started Daily apt download activities. Nov 1 10:00:23 myserver kernel: [12345.678] eth0: renamed from veth1234 Nov 1 10:00:30 myserver sshd[1234]: Accepted publickey for user from 192.168.1.1 ... ``` ファイルが開いたら、キーボードで操作する。 ### less の基本操作 {#less-keys} | キー | 動作 | | ---------------- | ------------------ | | `スペース` / `f` | 次のページ | | `b` | 前のページ | | `↓` / `j` | 1行下 | | `↑` / `k` | 1行上 | | `G` | 最後の行へジャンプ | | `g` | 最初の行へジャンプ | | `q` | 終了 | | `/キーワード` | 前向き検索 | | `?キーワード` | 後ろ向き検索 | | `n` | 次の検索結果 | | `N` | 前の検索結果 | ::: tip **とりあえず覚える 3 つ** - `スペース`:次のページ - `q`:終了 - `/エラー`:「エラー」という文字を検索 ::: ### less で検索する ::: dialogue @lina: ログって長くて、エラーがどこにあるか探せません… @linny: `/` を押してキーワードを入力すれば検索できるよ!入力して Enter を押すと、その文字が含まれる行にジャンプするんだ。 ::: ```bash less /var/log/syslog ``` `less` が開いたら `/error` と入力して `Enter` を押す。 「error」を含む行にジャンプし、`n` で次のヒット、`N` で前のヒットに移動できる。 ::: tip `less` は検索結果をハイライト表示してくれるので、長いログでもエラー箇所がすぐに見つかる。 ::: ## more の基本 - シンプルなページャ {#more} > **結論**: `more` はスペースで進むだけのシンプルなページャだが、前のページには戻れないのが弱点。 ::: dialogue @lina: `more` コマンドも似たようなものですか? @linny: `more` はもっとシンプル。「もっと見る」ってスペースを押すだけ。ただし**後ろには戻れない**のが弱点だよ。 ::: ```bash more /var/log/syslog ``` `more` では: | キー | 動作 | | ---------- | ---------- | | `スペース` | 次のページ | | `Enter` | 1行進む | | `q` | 終了 | ::: warning `more` は前のページに戻れない。すでに読んだ部分を確認したい場合は `less` を使おう。 ::: ## less と more の使い分け {#comparison} > **結論**: 前に戻れて検索もできる `less` が基本。`more` は `less` 未導入の環境やシンプル操作で十分なときに使う。 ::: dialogue @lina: じゃあ `less` だけ覚えれば `more` はいらないですか? @linny: 基本的にはそう!でも `more` はシステムによっては `less` がインストールされていない場合に重宝するし、シンプルな操作で十分なこともある。迷ったら `less` を使えば間違いないよ。 ::: | 比較 | `less` | `more` | | -------------- | ------------------------ | ---------------- | | 前に戻る | できる | できない | | 検索 | できる(ハイライトあり) | 基本機能のみ | | パイプ対応 | できる | できる | | デフォルト搭載 | 多くの環境 | ほぼすべての環境 | **結論**:迷ったら `less` を使う。 ## tail の基本 - ファイルの末尾を見る {#tail} > **結論**: `tail` はファイル末尾(最新部分)を表示し、デフォルトは最後の10行、`-n` で行数を指定できる。 ::: dialogue @lina: ログって、新しいものは後ろに追記されていくんですよね? @linny: そう!だから `tail` コマンドでファイルの末尾(最新部分)だけ表示するのが便利なんだ。デフォルトで最後の10行を表示するよ。 ::: ```bash tail /var/log/syslog ``` ```output Nov 1 11:59:43 myserver systemd[1]: Starting Session 42 of User user. Nov 1 11:59:43 myserver systemd-logind[456]: New session 42 of user user. Nov 1 11:59:44 myserver sshd[5678]: session opened for user user by (uid=0) Nov 1 11:59:58 myserver systemd[1]: session-42.scope: Deactivated successfully. ``` ### 行数を指定する {#tail-n} ```bash tail -n 50 /var/log/syslog ``` `-n 50` で最後の50行を表示できる。`-n` は省略して `-50` とも書ける。 ```bash tail -50 /var/log/syslog ``` ::: tip `head` コマンドはファイルの**先頭**を表示する。`tail` と `head` はセットで覚えよう。 ::: ## tail -f でリアルタイム監視 {#tail-f} > **結論**: `tail -f` はファイルへの追記をリアルタイム表示し、ログ監視に活躍する。終了は `Ctrl+C`。 ::: dialogue @lina: ログって、リアルタイムで増えていくものですよね?それを追いかけたいときはどうすればいいですか? @linny: `tail -f` がまさにそれ!`-f` は「follow(追いかける)」の意味で、ファイルに追記されるたびに画面に表示してくれるんだ。 @lina: おお!それは便利ですね! @linny: Web サーバのアクセスログを監視したり、デプロイ中のアプリログを追いかけたりするときに大活躍だよ。 ::: ```bash tail -f /var/log/nginx/access.log ``` ```output 192.168.1.1 - - [01/Jun/2026:12:00:01 +0900] "GET / HTTP/1.1" 200 1234 192.168.1.2 - - [01/Jun/2026:12:00:05 +0900] "GET /api/users HTTP/1.1" 200 5678 192.168.1.1 - - [01/Jun/2026:12:00:07 +0900] "POST /api/login HTTP/1.1" 200 89 ``` `Ctrl+C` で終了する。 ::: warning `tail -f` は終了操作(`Ctrl+C`)を忘れるとずっと動き続ける。終わったら必ず `Ctrl+C` で止めよう。 ::: ### 複数ファイルを同時に監視する ```bash tail -f /var/log/nginx/access.log /var/log/nginx/error.log ``` 複数のファイルを指定すると、どのファイルに追記があったかファイル名付きで表示してくれる。 ```output ==> /var/log/nginx/access.log <== 192.168.1.3 - - [01/Jun/2026:12:01:00 +0900] "GET /image.png HTTP/1.1" 200 9876 ==> /var/log/nginx/error.log <== 2026/06/01 12:01:01 [error] 1234#0: *5 No such file or directory ``` ## 実践 - エラーログを調べる {#practice} > **結論**: `tail -n 100` で最新ログを確認し、`less` で検索、`tail -f` でリアルタイム追跡する流れが基本。 ::: dialogue @lina: じゃあ、サーバでエラーが起きたとき、どうやってログを調べればいいですか? @linny: 手順を一緒に見てみよう。まず `tail -n 100` で最新100行を確認して、それから `less` で詳しく調べるのがおすすめだよ。 ::: **ステップ 1 - 最新のログを確認** ```bash tail -n 100 /var/log/syslog ``` **ステップ 2 - エラーが多そうなら `less` で検索** ```bash less /var/log/syslog ``` `less` が開いたら `/error` や `/failed` で検索する。 **ステップ 3 - リアルタイムで状況を追う** アプリを再起動したり操作したりしながら、ログをリアルタイムで確認したい場合: ```bash tail -f /var/log/syslog ``` ::: tip **grep と組み合わせる** パイプで `grep` と組み合わせると特定のキーワードだけ絞り込める。 ```bash tail -f /var/log/syslog | grep -i error ``` 「error」(大文字小文字問わず)を含む行だけリアルタイムで表示する。 ::: ## 次に読む {#next} - [ログファイルの読み方 - システムログ解析入門](/articles/tutorials/log-reading-basics) - [journalctl の使い方 - Linuxログ調査の基本と障害対応](/articles/tutorials/journalctl-basics) - [パイプとリダイレクト入門 - データの流れを理解する](/articles/tutorials/pipe-redirect-basics) # Linuxエラーメッセージ辞典 - よくあるエラーの原因と対処法 Source: https://penguin-gym-linux.com/articles/tutorials/linux-error-messages-dictionary ## この辞典の使い方 {#intro} エラーメッセージ文字列をそのまま見出しにしている。ターミナルに出た文言で `Ctrl+F` 検索し、該当セクションの「原因」「切り分け」「対処」を上から順に読めば最短で復旧できる。 ::: tip **共通の鉄則** - エラー文は**最後の1行だけでなく最初の1行**を読む(根本原因は先頭に出ることが多い) - 推測で `sudo` を付けない。まず原因を切り分ける - 切り分けは「**どのコマンドが/どのファイルに/どの権限で**」の3点を特定する順で行う ::: ::: warning **前提(対象環境)** - OS: Ubuntu / Debian / RHEL 系の一般的なディストリビューション - シェル: bash(zsh でも大半は同じ) - 一般ユーザー権限で操作中を想定 ::: ## command not found とは? {#command-not-found} シェルが指定された名前の実行ファイルを `PATH` 上で見つけられない状態。コマンド名のタイプミス、未インストール、`PATH` 設定漏れのいずれかが原因のほぼ全て。 ```output bash: dokcer: command not found ``` 切り分け: ```bash command -v docker # 実体パスが出れば PATH 上にある which docker # 同上(外部コマンド版) echo "$PATH" # 期待するディレクトリが含まれるか type docker # alias / 関数 / 実体のどれかを判別 ``` 対処: - **タイプミス**: 上の例は `dokcer`。正しくは `docker` - **未インストール**: `sudo apt install <パッケージ名>`(RHEL 系は `dnf install`) - **PATH 漏れ**: 実体はあるのに見つからない場合。`export PATH="$PATH:/usr/local/bin"` を `~/.bashrc` に追記 - **sudo 時だけ not found**: `sudo` は `secure_path` を使うため `PATH` が別。フルパス指定か `sudo env "PATH=$PATH" cmd` ::: tip インストール直後に not found が続く場合、シェルがコマンド位置をキャッシュしている。`hash -r` でキャッシュをクリアする。 ::: 詳細は [「command not found」の対処法](/articles/tutorials/command-not-found) を参照。 ## Permission denied はなぜ出るのか? {#permission-denied} 操作対象に対して、実行ユーザーが必要な権限(読み/書き/実行)を持っていないときに出る。ファイル権限・所有者・ディレクトリの実行ビット・SELinux/AppArmor のいずれかが原因。 ```output bash: ./deploy.sh: Permission denied -bash: /var/log/app.log: Permission denied ``` 切り分け: ```bash ls -l deploy.sh # 実行ビット x があるか、所有者は誰か ls -ld /var/log # 親ディレクトリの権限 whoami # 自分のユーザー名 id # 所属グループ ``` 対処: - **スクリプトが実行できない**: `chmod +x deploy.sh` - **ファイルに書けない**: 所有者なら `chmod u+w file`、他人の所有なら `sudo` か所有者変更 `sudo chown $USER file` - **ディレクトリに入れない**: ディレクトリには実行ビット `x` が必要。`chmod +x dir` - **`sudo` が必要な領域**(`/etc`, `/var/log` 等): 設定変更は `sudo` 経由で行う ::: warning `chmod 777` で「とりあえず通す」のは事故の元。必要最小限の権限(ファイル `644`/`755`、ディレクトリ `755`)に留める。 ::: 詳細は [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) を参照。 ## No such file or directory の本当の意味 {#no-such-file} 指定したパスが存在しない、または途中のディレクトリが存在しない。シンボリックリンク切れ、相対パスの誤解、インタプリタ行(shebang)のパス誤りでも発生する。 ```output cat: config.yml: No such file or directory bash: ./run.sh: /bin/bash^M: bad interpreter: No such file or directory ``` 切り分け: ```bash pwd # いまどこにいるか ls -la # ファイルが本当にあるか(隠しファイル含む) file run.sh # 改行コード(CRLF)混入の確認 readlink -f link # シンボリックリンクの最終リンク先 ``` 対処: - **相対パスの誤解**: `cat ./config.yml` が無いなら絶対パスで確認 `cat /etc/app/config.yml` - **shebang で not found**: `^M` が出たら Windows 改行(CRLF)。`sed -i 's/\r$//' run.sh` で除去 - **shebang のパス誤り**: `#!/bin/bash` のはずが `/usr/bin/bash` 等。`which bash` で実体を確認 - **リンク切れ**: `ls -l` で矢印先が赤表示ならリンク先が消えている。張り直す ## No space left on device の調べ方 {#no-space} 書き込み先のファイルシステムに空き容量が無い。実ブロックの枯渇だけでなく、**inode 枯渇**(小さいファイルが大量)でも同じメッセージが出る点が落とし穴。 ```output cp: error writing 'backup.tar': No space left on device ``` 切り分け: ```bash df -h # 容量の使用率(Use%) df -i # inode の使用率(IUse%)← 見落としがち du -sh /var/* 2>/dev/null | sort -h # どこが太いか ``` 対処: - **ブロック枯渇**: 巨大ログ・古いアーカイブを削除。`journalctl --vacuum-size=200M` でログ圧縮 - **inode 枯渇**: `df -i` が 100% なら容量に空きがあっても書けない。不要な小ファイル群(キャッシュ・セッション)を削除 - **削除したのに減らない**: プロセスが掴んだままのファイル。`lsof +L1` で確認し、該当プロセスを再起動 ::: warning `rm` した直後でも、そのファイルを開いているプロセスが生きている限り容量は解放されない。サービス再起動まで `df` は減らない。 ::: 詳細は [ディスクがいっぱいになったときの調べ方](/articles/troubleshooting/no-space-left-on-device) を参照。 ## Connection refused と timed out の違い {#connection-errors} `Connection refused` は**相手に届いたがポートで拒否**(サービス未起動 or ポート違い)。`Connection timed out` は**応答が返ってこない**(経路・ファイアウォール・ホスト停止)。この区別が切り分けの起点になる。 ```output ssh: connect to host 10.0.0.5 port 22: Connection refused curl: (28) Failed to connect to api.example.com port 443: Connection timed out ``` 切り分け: ```bash ping -c3 10.0.0.5 # ホストまで到達するか nc -vz 10.0.0.5 22 # ポートが開いているか ss -tlnp | grep :22 # サーバ側でサービスが LISTEN しているか ``` 対処: - **refused**: 対象サービスが起動しているか確認 `systemctl status sshd`。ポート番号の指定ミスも確認 - **timed out**: ファイアウォール/セキュリティグループの許可、経路(`traceroute`)、ホスト自体の死活を確認 - **名前解決で失敗**: `Could not resolve host` なら DNS 問題。`/etc/resolv.conf` と `getent hosts <名前>` を確認 詳細は [SSH で接続できないとき](/articles/troubleshooting/ssh-troubleshooting) と [ネットワークコマンド入門](/articles/tutorials/network-commands-basics) を参照。 ## Address already in use の対処 {#address-in-use} 起動しようとしたプロセスが使うポートを、別のプロセスが既に占有している。前回プロセスの残留、二重起動、`TIME_WAIT` 状態の残骸が主因。 ```output Error: listen EADDRINUSE: address already in use :::3000 bind: Address already in use ``` 切り分け: ```bash ss -tlnp | grep :3000 # どの PID がポートを掴んでいるか lsof -i :3000 # 同上(プロセス名つき) ``` 対処: - **残留プロセスを停止**: `kill `。応答しなければ `kill -9 ` - **二重起動**: systemd やプロセスマネージャ(pm2 等)で多重起動していないか確認 - **TIME_WAIT で再バインド不可**: アプリ側で `SO_REUSEADDR` を有効化、または数十秒待つ ## Killed / Out of memory が出たら {#oom} `Killed` の多くは OOM Killer がメモリ不足時にプロセスを強制終了したもの。アプリのログに痕跡が無くても、カーネルログには記録が残る。 ```output $ ./train.py Killed ``` 切り分け: ```bash dmesg -T | grep -i -E 'oom|killed process' # OOM の証跡 journalctl -k | grep -i oom # systemd 環境 free -h # 現在のメモリ/swap ``` 対処: - **物理メモリ不足**: 処理のバッチサイズ縮小、不要プロセス停止、swap 追加 - **特定プロセスが肥大**: `ps aux --sort=-%mem | head` でメモリ食いを特定 - **コンテナの制限**: `--memory` 制限に当たっていないか。制限値を見直す ::: tip OOM 以外の `Killed` は手動 `kill` やタイムアウト killer の可能性。`dmesg` に OOM 記録が無ければそちらを疑う。 ::: ## bad interpreter / Exec format error {#exec-format} 実行ファイルをカーネルが起動できない状態。shebang の誤り、CRLF 混入、アーキテクチャ不一致(ARM バイナリを x86 で実行等)が原因。 ```output ./run.sh: /bin/sh^M: bad interpreter: No such file or directory ./app: cannot execute binary file: Exec format error ``` 切り分け: ```bash head -1 run.sh # shebang 行を目視 file app # バイナリのアーキテクチャ(x86-64 / ARM aarch64) uname -m # 実行ホストのアーキテクチャ ``` 対処: - **`^M` 付き**: CRLF を除去 `sed -i 's/\r$//' run.sh` - **shebang のパス誤り**: 実体を `which` で確認して修正 - **アーキテクチャ不一致**: そのホスト向けにビルドし直す。クロス実行が必要なら `qemu-user` 等を検討 ## segmentation fault (core dumped) の初動 {#segfault} プロセスが不正なメモリ領域にアクセスして OS に強制終了された。アプリ自体のバグが多いが、破損した共有ライブラリやバージョン不整合でも起きる。 ```output Segmentation fault (core dumped) ``` 切り分け: ```bash dmesg -T | tail # segfault の発生アドレス ldd /path/to/app # 共有ライブラリの解決状況(not found が無いか) ulimit -c # core ファイル生成の可否 ``` 対処: - **ライブラリ不整合**: `ldd` で `not found` があれば該当ライブラリを再インストール - **再現性のあるバグ**: `ulimit -c unlimited` 後に再実行し、`gdb ` で `bt`(バックトレース)を取得 - **アップデート直後**: 依存パッケージの整合性を確認(`apt install --reinstall `) 詳細は [パッケージ管理入門](/articles/tutorials/apt-yum-basics) を参照。 ## エラー早見表 {#cheatsheet} | メッセージ | 主因 | まず打つコマンド | | ------------------------- | --------------------- | ------------------------- | | command not found | 未インストール / PATH | `command -v cmd` | | Permission denied | 権限 / 所有者 | `ls -l target` | | No such file or directory | パス誤り / CRLF | `ls -la` / `file f` | | No space left on device | 容量 / inode 枯渇 | `df -h` / `df -i` | | Connection refused | サービス未起動 | `ss -tlnp` | | Connection timed out | 経路 / FW | `nc -vz host port` | | Address already in use | ポート占有 | `lsof -i :PORT` | | Killed | OOM | `dmesg -T \| grep -i oom` | | Exec format error | アーキ不一致 | `file app` | | Segmentation fault | アプリ / ライブラリ | `ldd app` | ::: warning **やってはいけないこと** - メッセージを読まずに `sudo` を付ける - `chmod 777` で権限問題を握りつぶす - `df` だけ見て `df -i`(inode)を確認しない - `Killed` をアプリログだけで判断(カーネルログを見ない) ::: ## 次に読む {#next} - [「command not found」の対処法](/articles/tutorials/command-not-found) - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) - [ディスクがいっぱいになったときの調べ方](/articles/troubleshooting/no-space-left-on-device) - [SSH で接続できないときのチェックリスト](/articles/troubleshooting/ssh-troubleshooting) - [ネットワークコマンド入門](/articles/tutorials/network-commands-basics) - [journalctl の使い方:ログ調査の基本](/articles/tutorials/journalctl-basics) # locate/which/whereis - コマンドの場所を探す 3 つの方法 Source: https://penguin-gym-linux.com/articles/tutorials/locate-which-whereis ## この記事で解決できること {#intro} ::: dialogue @lina: ねえライニー先輩、コマンドをインストールしたはずなのに、どこに入ったかわからなくて困ってます……。 @linny: あーそれあるある!Linux には「コマンドの場所を探す」専用コマンドが 3 つあるんだ。`which`、`whereis`、`locate` — それぞれ特徴が違うから一緒に見ていこう。 ::: この記事では次のことが分かります。 - `which` でコマンドの実行ファイルパスを確認する方法 - `whereis` でバイナリ・マニュアルをまとめて確認する方法 - `locate` でファイル名を高速検索する方法 - 3 つの使い分けの基準 --- ## 1. which — 「今 PATH で使われるコマンドはどこ?」 {#which} > **結論**: `which` は PATH を順に調べ、今実際に実行される実行ファイルのパスを 1 つ表示する。 ::: dialogue @lina: `which` ってどんな場面で使うんですか? @linny: たとえば `python` ってコマンドを打ったとき、Python 2 が動くのか Python 3 が動くのか、どのインストール先が優先されているか調べたいときに使うよ。 ::: ### 基本的な使い方 ```bash which python3 ``` ```output /usr/bin/python3 ``` `which` は **PATH に登録されているディレクトリ**を順番に調べて、最初に見つかった実行ファイルのパスを表示します。 ```bash which git ``` ```output /usr/bin/git ``` ::: tip `which` の検索範囲は `$PATH` 変数に入っているディレクトリだけ。`echo $PATH` でどのディレクトリが対象か確認できます。 ::: ### コマンドが見つからない場合 ```bash which nonexistent-command ``` ```output ``` 何も表示されない(終了コード 1)か、ディストリビューションによっては次のように表示されます。 ```output which: no nonexistent-command in (/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin) ``` ::: dialogue @lina: 何も表示されないのはなぜですか? @linny: PATH の中にそのコマンドがないってこと。インストールされていないか、インストール先が PATH に入っていないかのどちらかだね。 ::: ### 複数バージョンが入っているときに便利 ```bash which python which python3 ``` ```output /usr/bin/python /usr/bin/python3 ``` どの python が実際に動くか一目でわかります。 --- ## 2. whereis — 「バイナリ・マニュアル・ソースをまとめて確認」 {#whereis} > **結論**: `whereis` は PATH に縛られず、実行ファイル・man ページ・ソースの場所をまとめて表示する。 ::: dialogue @lina: `which` で場所はわかりました。`whereis` は何が違うんですか? @linny: `which` は「今使われる実行ファイルひとつ」しか教えてくれないけど、`whereis` は実行ファイル(バイナリ)、マニュアル(man ページ)、ソースコードの場所をまとめて教えてくれるんだ。 ::: ### 基本的な使い方 ```bash whereis ls ``` ```output ls: /usr/bin/ls /usr/share/man/man1/ls.1.gz ``` `/usr/bin/ls` が実行ファイル、`/usr/share/man/man1/ls.1.gz` が man ページです。 ```bash whereis python3 ``` ```output python3: /usr/bin/python3 /usr/lib/python3 /usr/share/man/man1/python3.1.gz ``` ### フィールドの意味 | 出力フィールド | 内容 | | ------------------------------ | ------------------------------------------ | | `コマンド名:` の後の最初のパス | 実行バイナリ | | `/usr/share/man/...` | man ページ | | `/usr/lib/...` | ライブラリ・追加ファイル | | `/usr/src/...` | ソースコード(インストールされている場合) | ### 特定のフィールドだけ表示するオプション ```bash whereis -b ls # バイナリのみ whereis -m ls # man ページのみ whereis -s ls # ソースのみ ``` ```output ls: /usr/bin/ls ls: /usr/share/man/man1/ls.1.gz ls: ``` ::: tip `whereis` は `which` と違い、現在の PATH に縛られません。標準的なシステムディレクトリ(`/bin`、`/usr/bin`、`/usr/local/bin` など)を固定で検索するため、PATH が壊れていてもコマンドを見つけられることがあります。 ::: --- ## 3. locate — 「ファイル名でファイルを高速検索」 {#locate} > **結論**: `locate` は事前構築データベースを検索するため、任意のファイルをファイル名で高速に探せる。 ::: dialogue @lina: `locate` はどんなときに使うんですか? @linny: コマンドだけじゃなく、設定ファイルや任意のファイルをファイル名で素早く探したいときに使うよ。`find` より断然速い! ::: ### 基本的な使い方 ```bash locate ssh_config ``` ```output /etc/ssh/ssh_config /usr/share/doc/openssh-client/examples/ssh_config ``` ### なぜ速いの? `locate` はファイルシステムをリアルタイムに走査しません。あらかじめ作成された**データベース**(インデックス)を検索するため高速です。 ::: warning `locate` のデータベースは定期的に(通常は 1 日 1 回)`updatedb` コマンドで自動更新されます。新しく作ったファイルはすぐに `locate` で見つからないことがあります。 ::: ### データベースを手動更新する ```bash sudo updatedb ``` これを実行してから `locate` すると、最新の状態で検索できます。 ### 大文字・小文字を無視して検索 ```bash locate -i README ``` ```output /home/user/projects/readme.md /usr/share/doc/curl/README /usr/share/doc/git/README.md ``` ### 結果を絞り込む(パターンを含む行のみ) ```bash locate nginx | grep conf ``` ```output /etc/nginx/nginx.conf /etc/nginx/conf.d/default.conf ``` ### 件数だけ確認したい ```bash locate -c python ``` ```output 248 ``` ::: tip `locate` が使えない場合は `sudo apt install mlocate` または `sudo dnf install mlocate` でインストールできます。 ::: --- ## 4. 3 つのコマンドの使い分けまとめ {#comparison} > **結論**: 実行パスは `which`、関連ファイルは `whereis`、ファイル名検索は `locate`、最新状態は `find` と使い分ける。 ::: dialogue @lina: 結局、どれをいつ使えばいいのか、まとめてほしいです! @linny: こんな感じで覚えよう! ::: | やりたいこと | 使うコマンド | | ------------------------------------------------------ | ------------ | | 「今実際に動くコマンド」のパスを知りたい | `which` | | コマンドの実行ファイル・man ページをまとめて確認したい | `whereis` | | ファイル名でファイルをすばやく探したい | `locate` | | 最新状態でファイルを探したい(新規作成直後など) | `find` | ::: highlight **よく使う場面の例** - `python` コマンドが Python 2 か 3 かを確認 → `which python` - git の man ページがどこにあるか確認 → `whereis git` - `nginx.conf` がどこにあるか調べる → `locate nginx.conf` - インストール直後のファイルを探す → `sudo updatedb && locate ファイル名` ::: --- ## 5. よくある疑問と落とし穴 {#faq} > **結論**: `which` が空のときは `whereis` や `locate` で広く探し、`locate` が古ければ `updatedb` で更新する。 ### Q. `which` で何も表示されない → コマンドは本当にインストールされている? ::: dialogue @lina: `which node` を実行したら何も表示されませんでした。インストールしたはずなのに……。 @linny: まず `whereis node` と `locate node | grep bin` を試してみて。それでも見つからなければインストール自体が失敗しているかも。あとは `echo $PATH` で PATH の内容も確認しよう。 ::: ```bash # PATH を確認 echo $PATH # より広いディレクトリで探す whereis node # インストール先を探す locate node | grep "/bin/" ``` ### Q. `locate` が古いデータを返す 新しく作成したファイルが `locate` で見つからないとき: ```bash sudo updatedb locate 探したいファイル名 ``` ### Q. `locate` コマンドが存在しない ```bash # Debian / Ubuntu sudo apt install mlocate # Fedora / RHEL / CentOS sudo dnf install mlocate ``` --- ## 次に読む {#next} - [「command not found」の対処法](/articles/tutorials/command-not-found) - [apt/yum のパッケージ管理入門](/articles/tutorials/apt-yum-basics) - [find・grep・awk でファイルを検索する](/articles/tutorials/find-grep-awk-basics) # log ファイルの読み方 - システムログ解析入門 Source: https://penguin-gym-linux.com/articles/tutorials/log-reading-basics ## この記事で解決できること {#intro} - `/var/log/` 配下から、症状に応じた調査対象のログファイルを選べる。 - `tail`・`less`・`grep` を目的に応じて使い分けられる。 - `syslog`・`auth.log`・`kern.log` など代表的なログの役割の違いが分かる。 - syslog 形式の 1 行から、発生源(プロセス名・PID)と時刻を読み取れる。 - 障害対応で使う実践的な調査パターンが身につく。 ::: tip **結論(ログ調査の基本型)** 1. まず `ls /var/log/` で対象ファイルを特定する 2. `tail -n 100` で末尾から確認、リアルタイム監視は `tail -f` 3. `grep` でエラーキーワードを絞り込む 4. `less` で前後の文脈をたどる ::: ::: tip **この記事で扱うコマンドはすべて「読むだけ」です** `ls` / `tail` / `less` / `cat` / `grep` はファイルの中身を表示するだけで、内容は書き換えない。 本番サーバでも安全に実行できる。 ただしログファイルの**削除や切り詰め**は別である。危険な操作は本文の警告で明示する。 ::: ## /var/log ディレクトリとは何か {#var-log} > **結論**: Linux のシステムログは `/var/log/` に集約され、障害対応はここを見ることから始まる。 Linux のシステムログはほぼすべて `/var/log/` 以下に集約されている。カーネル・認証・アプリケーションが書き込んだ記録がここにあり、障害対応の出発点になる。 **先に用語を整理する。** | 用語 | 一行の意味 | 混同されやすい言い方 | | ------------------ | -------------------------------------------------- | ------------------------------------------------ | | ログ | プログラムが「いつ何をしたか」を書き残した記録 | 「ログファイル」「履歴」も同じものを指す | | デーモン | 画面を持たず裏で動き続けるプログラム | 「常駐プロセス」「サービス」とも呼ばれる | | syslog | ログを 1 か所へ集める仕組みと、その形式の呼び名 | 同名のファイル `/var/log/syslog` とは別物 | | rsyslog | 現在の Ubuntu で syslog の役割を担うデーモン | `syslog-ng` という別実装もある | | PID | 動作中のプロセスに OS が振る番号 | 「プロセスID」も同義 | | ログローテーション | ログが肥大化しないよう自動で分割・圧縮する仕組み | 退避後は `.1`、さらに古い世代は `.2.gz` で残る | ```bash ls /var/log/ ``` ```output auth.log dpkg.log kern.log syslog ubuntu-advantage.log boot.log faillog lastlog ufw.log wtmp ``` ::: tip `/var/log/` に書き込む主体は syslog デーモン(`rsyslog` / `syslog-ng`)と各アプリケーション自身の2種類がある。 ::: ## 代表的なログファイルとその役割とは {#log-files} > **結論**: 用途別にファイルが分かれているため、症状から見るべきファイルを先に決めると早い。 `/var/log/` 配下には用途別にファイルが分かれており、調査対象を素早く絞り込むためにそれぞれの役割を把握しておく必要がある。 | ファイル | 内容 | 調査用途 | | ---------- | ------------------------ | ------------------------ | | `syslog` | システム全般のメッセージ | 原因不明の障害 | | `auth.log` | 認証・sudo・SSH | 不正ログイン・sudo 失敗 | | `kern.log` | カーネルメッセージ | ハードウェア障害・OOM | | `dpkg.log` | パッケージ管理履歴 | インストール・更新の追跡 | | `ufw.log` | UFW ファイアウォール | 遮断された通信 | | `boot.log` | 起動時ログ | 起動失敗の原因特定 | `.1` や `.gz` が付いたファイルは、ローテーションで退避された過去分である。数日前の事象を追うときはこちらも対象になる。 ```bash ls /var/log/syslog* ``` ```output /var/log/syslog /var/log/syslog.1 /var/log/syslog.2.gz ``` 圧縮済みファイルは展開せずに `zgrep` / `zless` で読める。 ```bash zgrep -i "error" /var/log/syslog.2.gz ``` ::: warning Ubuntu 20.04 以降は `syslog` が存在しない構成もある。その場合は `journalctl` でシステムログを確認する。 ::: ## ログを読むための基本コマンドとは {#commands} > **結論**: 末尾は `tail`、前後の文脈は `less`、短いファイルだけ `cat` と使い分けるのが基本。 ログファイルの閲覧には `cat` / `less` / `tail` の3つを状況に応じて使い分けるのが基本だ。 ### tail — 末尾から読む ```bash # 末尾50行を表示 tail -n 50 /var/log/syslog # リアルタイムで追跡(Ctrl+C で終了) tail -f /var/log/syslog ``` `-f`(follow)は障害発生中にログの流れをリアルタイムで監視するときに使う。 `-f` はプロンプトが戻らないまま出力を待ち続ける。終了するには `Ctrl+C` を押す。読んでいるだけなので、途中で止めてもログやサービスには影響しない。 ### less — スクロールして読む ```bash less /var/log/auth.log ``` `less` 内での主要操作: - `G`:末尾へジャンプ - `g`:先頭へジャンプ - `/キーワード`:前方検索 - `n`:次のマッチへ - `q`:終了 ### cat — 全体を一気に流す(短いファイル向き) ```bash cat /var/log/boot.log ``` ログが長いファイルに `cat` を使うと大量出力になるため、通常は `less` か `tail` を優先する。 ::: danger **ログファイルを消して容量を空けようとしない** ディスクが逼迫すると `rm /var/log/syslog` で消したくなるが、これは 2 つの理由で危険である。 - 書き込み中のプロセスがファイルを掴んだままなので、削除しても容量は解放されない。 - 削除したログは復元できず、障害の証拠が失われる。 容量を空けたい場合は、まず何が使っているかを確認する。次の 2 コマンドはどちらも表示するだけで、何も削除しない。 ```bash du -sh /var/log/* | sort -hr | head sudo lsof +L1 ``` その上で、恒久対策は `logrotate` の設定で行う。手動で切り詰める必要がある場合も `rm` ではなく `truncate -s 0 `(内容だけ空にする)を使い、実行前に対象ファイル名を必ず確認する。 ::: ## grep でログをフィルタリングする方法とは {#grep} > **結論**: 長大なログは `grep` で絞り、`-i` や `-A` / `-B` を足して文脈ごと確認する。 長大なログから目的の行を絞り込むには `grep` を使う。コマンド単体よりも `tail` や `less` との組み合わせが実務では多い。 ### エラー行を抽出する ```bash grep -i "error" /var/log/syslog ``` `-i` で大文字小文字を区別しないマッチになる。 ### 複数キーワードで絞り込む ```bash grep -E "error|warn|failed" /var/log/syslog ``` `-E` は `|`(または)を使える拡張正規表現モードの指定である。 ### リアルタイムで grep する ```bash tail -f /var/log/syslog | grep "error" ``` ### 特定の日付だけ確認する ```bash grep "May 31" /var/log/auth.log ``` ### 前後の文脈も表示する ```bash grep -A 5 -B 5 "Failed password" /var/log/auth.log ``` `-A 5` はマッチした行の後5行、`-B 5` は前5行を表示する。 ::: tip `sudo` なしで読めないログファイルがある。`Permission denied` が出たら `sudo less /var/log/kern.log` のように実行する。 ::: ## ログのフォーマットを読み解く方法 {#format} > **結論**: syslog 形式は固定の並びなので、時刻・ホスト・プロセス・PID の順に読めばよい。 syslog 形式のログは固定したフォーマットで書かれており、パターンを知っていると素早く情報を抽出できる。 ```output May 31 10:23:45 myserver sshd[12345]: Failed password for root from 192.168.1.100 port 22 ssh2 ``` | フィールド | 値 | 意味 | | -------------- | -------------------- | ------------------------ | | タイムスタンプ | `May 31 10:23:45` | ログが記録された日時 | | ホスト名 | `myserver` | メッセージを発したホスト | | プロセス名 | `sshd` | ログを書き込んだプロセス | | PID | `[12345]` | プロセスID(追跡に使う) | | メッセージ | `Failed password...` | 実際のログ内容 | PID が分かれば、同じプロセスが出した行だけを追える。 ```bash grep "sshd\[12345\]" /var/log/auth.log ``` ::: warning ログのタイムスタンプはサーバのローカルタイムで記録される。タイムゾーンが UTC の場合、表示と実際の時刻がずれていることに注意する。 現在の設定は `timedatectl` で確認できる(これも表示するだけのコマンドである)。 ::: ## よくあるログ調査パターンとは {#patterns} > **結論**: 症状ごとに見るべきファイルと検索語はほぼ決まっており、型を覚えておくと調査が速い。 障害対応でよく使う実践的なパターンを一覧にまとめる。 | 症状 | 見るファイル | 検索する語 | | -------------------------- | ------------ | ------------------------------ | | SSH でログインできない | `auth.log` | `Failed password` | | 誰が何をしたか追いたい | `auth.log` | `COMMAND` | | サーバが不安定・突然落ちる | `kern.log` | `error` / `oops` / `panic` | | 更新後に動かなくなった | `dpkg.log` | `install` | | 起動に失敗した | `boot.log` | (全体を `less` で読む) | ### SSH ログイン失敗を確認する ```bash sudo grep "Failed password" /var/log/auth.log | tail -20 ``` ### sudo の使用履歴を確認する ```bash sudo grep "sudo" /var/log/auth.log | grep "COMMAND" ``` ### カーネルエラーを確認する ```bash sudo grep -iE "error|oops|panic" /var/log/kern.log | tail -30 ``` ### パッケージのインストール履歴を確認する ```bash grep " install " /var/log/dpkg.log ``` ### 直近の起動時エラーを確認する ```bash sudo less /var/log/boot.log ``` ## journalctl との使い分け方とは {#journalctl} > **結論**: systemd 管理下のログは `journalctl`、ファイルに直接書くログは `/var/log/` を見る。 Ubuntu 16.04 以降、`systemd` が採用されてから `journalctl` が主要なログ確認手段となった。`/var/log/` のファイルと `journalctl` は並存する。 | 場面 | 使うべきツール | | -------------------------------------- | ----------------------------- | | systemd サービスのログ | `journalctl -u nginx` | | カーネルメッセージ | `journalctl -k` | | 起動時のログ | `journalctl -b` | | 伝統的なファイルに書かれたアプリのログ | `tail -f /var/log/アプリ.log` | | `/var/log/auth.log` の内容 | どちらでも可 | ```bash # journalctl でリアルタイム追跡 journalctl -f # 特定サービスのログを tail する journalctl -u nginx -f ``` ::: tip `journalctl` は systemd の journal に蓄積されたログを読む。`/var/log/` のテキストファイルとは別の仕組みだが、`rsyslog` の設定によっては両方に書き込まれることもある。 ::: ## 調査完了チェックリスト {#checklist} > **結論**: 時刻・発生源・再現性の 3 点を押さえられていれば、ログ調査は次の工程へ進んでよい。 - [ ] 事象が起きた時刻を特定し、その前後のログを読んだ - [ ] 見たファイルが症状に対応している(認証なら `auth.log` 等) - [ ] ローテーション済みファイル(`.1` / `.gz`)も必要なら確認した - [ ] タイムスタンプのタイムゾーンを確認した - [ ] ログを削除・切り詰めせずに調査を終えた ## 次に読む {#next} - [journalctl の使い方](/articles/tutorials/journalctl-basics) - [grep・awk の基礎から実践](/articles/tutorials/find-grep-awk-basics) - [head・tail・パイプの使い方](/articles/tutorials/file-operations-advanced) # logrotate 入門 - ログを溜めずに回す Source: https://penguin-gym-linux.com/articles/tutorials/logrotate-basics ## logrotate とは何か? {#what} logrotate はログファイルを定期的にローテーション(切り替え・圧縮・削除)するツール。放置すると `/var/log` 配下が無限に膨れてディスクを食い尽くすため、Ubuntu / RHEL 系ともにデフォルトでインストールされており、cron から毎日自動実行される。 ::: tip **ひとことまとめ** - ログを自動で切り替え・圧縮・削除する常駐タスク - 設定は `/etc/logrotate.conf`(グローバル)と `/etc/logrotate.d/`(アプリ別)の 2 層構造 - デバッグは `logrotate -d` から始める ::: ## ログを回さないとどうなるか? {#why} ログを回さない場合、nginx・MySQL・syslog のログが数 GB に達してディスクが満杯になり、サービスが `No space left on device` で停止する。定期的に cron から `/etc/cron.daily/logrotate` が呼ばれており、設定さえ書けば自動で管理される。 ```bash cat /etc/cron.daily/logrotate ``` ```output #!/bin/sh /usr/sbin/logrotate /etc/logrotate.conf ``` ## 設定ファイルの構造はどうなっているか? {#config-structure} logrotate は 2 層の設定を持つ。`/etc/logrotate.conf` がグローバルデフォルトを定義し、末尾の `include` 行で `/etc/logrotate.d/` 配下を一括取込む。 ```output /etc/logrotate.conf ← グローバル設定 /etc/logrotate.d/ ← アプリ別設定(主戦場) ├── nginx ├── mysql-server ├── rsyslog └── ... ``` 典型的な `/etc/logrotate.conf`: ```bash cat /etc/logrotate.conf ``` ```output weekly rotate 4 create dateext compress include /etc/logrotate.d ``` ::: tip `weekly` / `rotate 4` はグローバルデフォルト。アプリ別設定で上書きできる。 ::: `/etc/logrotate.d/nginx` の例(パッケージが自動生成したもの): ```bash cat /etc/logrotate.d/nginx ``` ```output /var/log/nginx/*.log { daily missingok rotate 52 compress delaycompress notifempty create 0640 www-data adm sharedscripts postrotate if [ -f /run/nginx.pid ]; then kill -USR1 `cat /run/nginx.pid` fi endscript } ``` ## 主要ディレクティブを押さえる {#directives} 実務で使うディレクティブをまとめる。 | ディレクティブ | 意味 | | ------------------------------ | --------------------------------------------------------- | | `daily` / `weekly` / `monthly` | ローテーション頻度 | | `rotate N` | 保持する世代数(例: `rotate 7` = 7 世代分保持) | | `size N` | 指定サイズ超過時にローテーション(例: `size 100M`) | | `compress` | gz 圧縮する | | `delaycompress` | 直前世代の圧縮を 1 サイクル遅らせる | | `missingok` | ログファイルが無くてもエラーにしない | | `notifempty` | 空ファイルはローテーションしない | | `create MODE USER GROUP` | ローテーション後に空ファイルを生成 | | `dateext` | 世代ファイルに日付サフィックスを付ける | | `sharedscripts` | ワイルドカード対象全ファイルで postrotate を 1 回だけ実行 | | `postrotate` / `endscript` | ローテーション後に実行するコマンド | ::: warning `compress` と `delaycompress` はセットで使うのが実務の鉄則。ローテーション直後のファイルをプロセスが書き続けている場合、即時圧縮すると書き込みが失敗する。 ::: ## カスタム設定ファイルを書く {#custom-config} 独自アプリのログを管理する場合は `/etc/logrotate.d/myapp` を新規作成する。 ```bash /var/log/myapp/*.log { daily rotate 30 compress delaycompress missingok notifempty create 0640 www-data adm dateext sharedscripts postrotate systemctl reload myapp > /dev/null 2>&1 || true endscript } ``` **ポイント:** - `sharedscripts` を付けないと `*.log` にマッチしたファイルの数だけ `postrotate` が実行される - `postrotate` の最後に `|| true` を付けると、サービスが停止中でも logrotate がエラー終了しない - nginx は `kill -USR1`、Apache は `apachectl graceful`、systemd サービスは `systemctl reload` でログファイルを再オープンする ## 動作確認はどうするか? {#debug} 設定を書いたらまず `logrotate -d`(ドライラン)で確認する。ファイルを一切変更しない。 ```bash logrotate -d /etc/logrotate.d/myapp ``` ```output reading config file /etc/logrotate.d/myapp Allocating hash table for state file, size 15360 B Handling 1 logs rotating pattern: /var/log/myapp/*.log after 1 days (30 rotations) ... rotating log /var/log/myapp/access.log, log->rotateCount is 30 ... not rotating log, since it was already rotated less than 1 days ago ``` 「already rotated less than 1 days ago」と出た場合は `-f` で強制実行できる。 ```bash logrotate -f /etc/logrotate.d/myapp ``` ::: warning `-f` は `/var/lib/logrotate/status` のタイムスタンプを無視して強制実行する。同一設定を短時間に複数回 `-f` すると世代ファイルが上書きされるため、テスト後は状態ファイルを確認する。 ::: ローテーション状態を確認: ```bash cat /var/lib/logrotate/status ``` ```output logrotate state -- version 2 "/var/log/myapp/access.log" 2026-6-1-3:0:0 "/var/log/nginx/access.log" 2026-6-1-3:0:0 ``` ## よくあるミスと対処 {#pitfalls} ### postrotate が効かない ローテーション後もサービスが旧ファイルに書き続ける場合、`postrotate` のシグナル送信を忘れているか、`sharedscripts` の有無が問題の場合が多い。`lsof` で旧ファイルを掴んだままのプロセスを確認できる。 ```bash lsof | grep deleted | grep log ``` ### dateext 重複エラー 同一日に `logrotate -f` を 2 回実行すると `dateext` のサフィックスが重複してエラーになる。テスト環境では `dateformat -%Y%m%d-%s`(秒単位)を追加するか、世代ファイルを手動で削除してから再実行する。 ### ディレクトリ・ユーザーが存在しない `create 0640 www-data adm` のユーザー・グループがシステムに存在しないとローテーションに失敗する。`logrotate -v` で詳細エラーを確認し、`id www-data` でユーザーの存在を確認する。 ```bash logrotate -v /etc/logrotate.d/myapp 2>&1 | head -30 ``` ## 次に読む {#next} - [journalctl の使い方](/articles/tutorials/journalctl-basics) - [ログファイルの読み方](/articles/tutorials/log-reading-basics) - [cron の基本](/articles/tutorials/cron-basics) # lsblk / blkid 入門 - ブロックデバイスとUUIDを確認する Source: https://penguin-gym-linux.com/articles/tutorials/lsblk-blkid-basics ## lsblk と blkid は何が違う? {#intro} > **結論**: `lsblk` はデバイスの「ツリー構造とサイズ」を見るツール、`blkid` は「UUID・LABEL・FS 種別」を引くツール。役割が違うので両方使う。 ストレージ作業の最初の一歩は「どのデバイスがどう繋がっていて、どの UUID か」を正確に把握することだ。ここを間違えると、`fstab` に誤った行を書いて起動不能にする事故につながる。 - `lsblk`: ディスク → パーティション → LVM/暗号化 の **階層**、サイズ、マウント先を一覧する - `blkid`: 各デバイスの **UUID / LABEL / TYPE(ファイルシステム種別)/ PARTUUID** を引く ::: tip **使い分けの目安** - 全体像・容量・マウント状況を見る → `lsblk` / `lsblk -f` - `fstab` 用に UUID を 1 個だけ抜く → `blkid -s UUID -o value ` ::: ::: warning **前提(対象環境)** - ディストリ:Ubuntu / RHEL 系どちらも対象(`util-linux` 付属コマンド) - フルスキャンには root 権限(`sudo`)が必要な場合がある ::: ## lsblk でブロックデバイスを一覧するには? {#lsblk} > **結論**: 引数なしの `lsblk` で全ブロックデバイスをツリー表示できる。`TYPE` 列で disk / part / lvm / crypt を見分ける。 ```bash $ lsblk ``` ```output NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 100G 0 disk ├─sda1 8:1 0 1G 0 part /boot └─sda2 8:2 0 99G 0 part / sdb 8:16 1 14.5G 0 disk └─sdb1 8:17 1 14.5G 0 part /media/usb nvme0n1 259:0 0 476.9G 0 disk └─nvme0n1p1 259:1 0 476.9G 0 part /data ``` 主要な列の意味: - `NAME`:デバイス名(ツリーのインデントが親子関係) - `RM`:リムーバブルなら `1`(USB メモリ等) - `SIZE`:容量 - `RO`:読み取り専用なら `1` - `TYPE`:`disk`(実ディスク)/ `part`(パーティション)/ `lvm` / `crypt`(暗号化)/ `rom` / `loop` - `MOUNTPOINTS`:マウント先(未マウントなら空) ::: tip `lsblk` は sysfs / udev を読むだけなので、**一般ユーザーでも実行できる**(root 不要)。 ::: ### よく使うオプション {#lsblk-options} ```bash $ lsblk -p # フルパス表示(/dev/sda1 のように) $ lsblk -d # パーティションを畳んでディスクだけ表示 $ lsblk -b # サイズをバイト単位で表示(スクリプト向け) $ lsblk -J # JSON 出力(jq で処理しやすい) ``` ## lsblk -f で UUID とファイルシステムを見るには? {#lsblk-f} > **結論**: `lsblk -f` でファイルシステム種別・LABEL・UUID・使用率をまとめて確認できる。日常の確認はまずこれで足りる。 ```bash $ lsblk -f ``` ```output NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS sda ├─sda1 ext4 1.0 boot 6e1f...-a2c1 812M 12% /boot └─sda2 ext4 1.0 9b3d...-77e0 61.2G 35% / nvme0n1 └─nvme0n1p1 xfs data c4a8...-1f93 410G 8% /data ``` `-f`(`--fs`)は内部で libblkid を使い、`blkid` 相当の情報を **ツリー表示に合成**してくれる。FS 種別・UUID・空き容量を一望できるため、実務ではこれが起点になる。 任意の列だけ出したいときは `-o` で指定する。 ```bash $ lsblk -o NAME,SIZE,FSTYPE,UUID,MOUNTPOINT ``` ::: highlight 利用可能な列名は `lsblk --help` の COLUMNS 一覧、または `lsblk -O`(全列)で確認できる。 ::: ## blkid で UUID・LABEL・種別を確認するには? {#blkid} > **結論**: `blkid` は各デバイスのファイルシステム属性を引く専用ツール。`-s` と `-o value` を組み合わせると UUID だけを抽出できる。 引数なしで実行すると、認識済みデバイスの属性を列挙する。 ```bash $ sudo blkid ``` ```output /dev/sda1: LABEL="boot" UUID="6e1f...-a2c1" TYPE="ext4" PARTUUID="2b1c...-01" /dev/sda2: UUID="9b3d...-77e0" TYPE="ext4" PARTUUID="2b1c...-02" /dev/nvme0n1p1: LABEL="data" UUID="c4a8...-1f93" TYPE="xfs" PARTUUID="8f0a...-01" ``` 特定デバイスだけ調べる場合は引数に渡す。 ```bash $ sudo blkid /dev/sda2 ``` ### UUID だけを抜き出す(スクリプト向け) {#blkid-value} `fstab` への転記やスクリプトでは、値だけが欲しい。`-s`(属性名)と `-o value`(値のみ出力)を使う。 ```bash $ sudo blkid -s UUID -o value /dev/sda2 ``` ```output 9b3d...-77e0 ``` ::: warning `blkid` はデバイスの **スーパーブロックをプローブ**するため、フルスキャンには root 権限が要る。一般ユーザーで実行すると、キャッシュ(`/run/blkid/blkid.tab`)に載っている分しか返らず、空振りすることがある。 ::: ## UUID と PARTUUID はどう違う? {#uuid-partuuid} > **結論**: `UUID` はファイルシステムが持つ ID、`PARTUUID` はパーティションテーブル(主に GPT)が持つ ID。フォーマットし直すと UUID は変わるが PARTUUID は残る。 | 識別子 | 由来 | フォーマットで変わる | 主な用途 | | ----------- | ---------------------------------- | -------------------------- | ------------------------------------ | | `UUID` | ファイルシステムのスーパーブロック | 変わる | `fstab` のマウント指定(最も一般的) | | `LABEL` | ファイルシステムのラベル文字列 | 任意に変更可 | 人間が読みやすい識別 | | `PARTUUID` | GPT パーティションテーブル | 残る(再フォーマット耐性) | ブートローダ / FS を持たない領域 | | `PARTLABEL` | GPT パーティションラベル | 任意に変更可 | パーティション単位の識別 | ::: tip swap や未フォーマット領域には FS の `UUID` が無いことがある。その場合は `PARTUUID` で指すと安定する。 ::: ## fstab で UUID を使うには? {#fstab} > **結論**: `/dev/sda2` のようなデバイス名は起動順で変わり得るため、`fstab` では `UUID=` 指定が安全。`blkid` で取得した値を貼り付ける。 `/dev/sdX` の番号は、ディスクを増設したり起動タイミングがずれたりすると入れ替わることがある。固定の `UUID` で指定すれば、その心配がない。 ```bash # 1. UUID を取得 $ sudo blkid -s UUID -o value /dev/sda2 9b3d...-77e0 # 2. /etc/fstab に記述(タブ/スペース区切り) # UUID=9b3d...-77e0 /data xfs defaults 0 2 ``` ::: danger `fstab` を編集したら、再起動前に必ず `sudo mount -a` で文法と実マウントを検証する。誤記のまま再起動すると **緊急モードで止まり、起動不能**になり得る。エラーが出なければOK。 ::: 詳しい手順は [マウントとfstab入門](/articles/tutorials/mount-fstab-basics) を参照。 ## つまずきポイントと対処 {#troubleshoot} > **結論**: 「UUID が出ない」「デバイスが見えない」の多くは、権限不足・未フォーマット・キャッシュ未更新が原因。 - **`blkid` が空 / 一部しか出ない** → `sudo` を付ける。プローブには root が必要 - **新しく繋いだ USB が `lsblk` に出ない** → `lsblk` を再実行。それでも無ければ `dmesg | tail` で認識ログを確認 - **`UUID` が表示されない領域がある** → 未フォーマット、または swap。`PARTUUID` で指す - **`fstab` 記述後に起動できない** → 編集前に `mount -a` で検証する習慣をつける ::: highlight **コピペ用:fstab 行を 1 コマンドで生成** ```bash DEV=/dev/sda2 echo "UUID=$(sudo blkid -s UUID -o value "$DEV") /data $(sudo blkid -s TYPE -o value "$DEV") defaults 0 2" ``` ::: ## まとめ / 次に読む {#next} - [ディスク管理入門(fdisk/lsblk)](/articles/tutorials/disk-management-basics) - [マウントとfstab入門](/articles/tutorials/mount-fstab-basics) - [読み取り専用ファイルシステムの対処](/articles/troubleshooting/read-only-file-system) # lsof 入門 - 開いているファイルとポートを調べる Source: https://penguin-gym-linux.com/articles/tutorials/lsof-basics ## この記事で解決できること {#intro} - `lsof` で **どのプロセスがどのファイル・ポートを開いているか** を特定できる - 「`device is busy` でアンマウントできない」「ポートが既に使われている」を即切り分けできる - 削除したのに **ディスクが空かない** 原因(deleted ファイル)を見つけられる ::: tip **結論(実務の型)** - **ポート調査** → `lsof -i :8080` - **プロセスが開くファイル** → `lsof -p ` - **ファイルを掴む犯人** → `lsof /path/to/file` - 出力が空・遅いときは `sudo` と `-n -P` を足す ::: ::: warning **前提(対象環境)** - OS:Ubuntu / RHEL 系(`lsof` は別パッケージのことがある) - 未導入なら `sudo apt install lsof` / `sudo dnf install lsof` - 他ユーザーのプロセスまで見るには `sudo` が必要 ::: ## lsof とは何か? {#what} > **結論**: `lsof`(list open files)は「いま開かれているファイル」を一覧するコマンド。Linux ではソケットやパイプもファイル扱いなので、ネットワーク調査にも使える。 Linux では **ほぼすべてがファイル** として扱われる。通常ファイルやディレクトリだけでなく、ネットワークソケット・パイプ・デバイス・ブロックデバイスまで「開いているファイル」に含まれる。`lsof` はこれらを横断的に一覧できるため、トラブル調査の万能ツールになる。 引数なしで実行すると、システム全体の開いているファイルが大量に表示される。 ```bash $ sudo lsof | head ``` ```output COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME systemd 1 root cwd DIR 254,1 4096 2 / systemd 1 root txt REG 254,1 1620224 393431 /usr/lib/systemd/systemd sshd 812 root 3u IPv4 18654 0t0 TCP *:ssh (LISTEN) ``` 各列の意味は次のとおり。 - `COMMAND` / `PID` / `USER`:開いているプロセスとその所有者 - `FD`:ファイルディスクリプタ番号(`cwd`=カレントディレクトリ、`txt`=実行ファイル、`3u`=fd3 を読み書きで開いている等) - `TYPE`:`REG`(通常ファイル)/ `DIR` / `IPv4` / `unix`(UNIX ソケット)など - `NAME`:ファイルパスや接続先 ::: tip 引数なしの全件出力は実務では使わない。次章以降の **絞り込み** が本番。 ::: ## ポートを使っているプロセスは? {#port} > **結論**: `lsof -i :ポート番号` で、そのポートを開いているプロセスを PID 付きで特定できる。`Address already in use` の犯人探しの定番。 最も使う場面が「ポートの占有調査」。`-i` はネットワーク接続に絞り込むオプション。 ```bash # 8080 番ポートを使っているプロセス $ sudo lsof -i :8080 ``` ```output COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME node 24531 hide 22u IPv4 184213 0t0 TCP *:8080 (LISTEN) ``` PID(この例では `24531`)が分かれば、そのまま停止できる。 ```bash $ kill 24531 ``` LISTEN 中のポートだけを一覧したいときは、プロトコルと状態で絞り込む。 ```bash # TCP で待ち受け中のポートをすべて表示 $ sudo lsof -iTCP -sTCP:LISTEN -P -n ``` - `-P`:ポート番号を `http` などの名前に変換しない(数字のまま) - `-n`:IP アドレスを DNS で名前解決しない ::: tip `-P -n` を付けると名前解決を省略でき、出力が速く・読みやすくなる。調査では基本セット。 ::: 特定のホストやポートだけを見ることもできる。 ```bash $ sudo lsof -i TCP:22 # 22番(SSH)関連の接続 $ sudo lsof -i @192.168.1.10 # 特定ホストとの接続 ``` ## プロセスが開いているファイルを見るには? {#process} > **結論**: `lsof -p ` でそのプロセスが開いている全ファイル(ログ・ソケット・ライブラリ)を一覧できる。挙動が読めないプロセスの調査に有効。 ```bash # PID 24531 が開いているファイル $ sudo lsof -p 24531 ``` ログファイルが書き込まれている場所、読み込んでいる設定ファイル、接続中のソケットがまとめて分かる。複数 PID はカンマ区切りで指定する。 ```bash $ sudo lsof -p 24531,800 ``` ユーザー単位・コマンド名単位での絞り込みも可能。 ```bash $ sudo lsof -u hide # ユーザー hide が開いているファイル $ sudo lsof -c nginx # コマンド名 nginx で始まるプロセス ``` ::: warning `-u` と `-c`、`-i` を同時に指定すると **OR 条件**(いずれかに合致)になる。AND で絞りたい場合は `-a` を加える。 ```bash # nginx かつ TCP 接続のみ(AND) $ sudo lsof -a -c nginx -i TCP ``` ::: ## このファイルを掴んでいるのは誰? {#file} > **結論**: `lsof /path` で、指定ファイル(やディレクトリ)を開いているプロセスを逆引きできる。`device is busy` でアンマウントできないときの切り札。 USB やネットワークドライブをアンマウントしようとして `target is busy` と怒られた経験は多いはず。原因は「そのディレクトリ配下のファイルを誰かが開いている」こと。 ```bash # /mnt/usb を掴んでいるプロセスを探す $ sudo lsof /mnt/usb ``` ディレクトリ配下を再帰的に調べるには `+D` を使う。 ```bash $ sudo lsof +D /mnt/usb ``` 犯人の PID が分かったら、プロセスを終了するかカレントディレクトリを移動してもらえばアンマウントできる。 ::: tip **kill と組み合わせる型** `-t` は PID だけを出力するオプション。`kill` にそのまま渡せる。 ```bash # /mnt/usb を掴むプロセスをすべて終了 $ sudo kill $(sudo lsof -t /mnt/usb) ``` 強制終了は最終手段。まずは何のプロセスか `-t` なしで確認すること。 ::: ## 削除したのにディスクが空かない {#deleted} > **結論**: プロセスが開いたままのファイルを削除しても、プロセスが終了するまで領域は解放されない。`lsof | grep deleted` で掴んでいるプロセスを特定する。 「巨大なログを `rm` したのに `df` の空き容量が増えない」——典型的な落とし穴。ファイルを開いているプロセスがある間は、`rm` してもリンクが消えるだけで実体(inode)は残り続ける。 ```bash # 削除済みなのに開かれ続けているファイルを探す $ sudo lsof -nP | grep '(deleted)' ``` ```output java 3120 app 5w REG 254,1 2147483648 394102 /var/log/app/huge.log (deleted) ``` この場合、領域を解放するには **プロセスを再起動**(または該当 fd を閉じる)必要がある。`huge.log` を `rm` しただけでは不十分だと分かる。 ::: warning サービスのログファイルは `rm` ではなく、ローテーション(`logrotate` の `copytruncate` 等)か `truncate -s 0` で空にするのが安全。`rm` は deleted 状態を生みやすい。 ::: ## よく使うオプション早見表 {#cheatsheet} > **結論**: 調査の起点は `-i`(ポート)・`-p`(プロセス)・パス指定(ファイル)の 3 つ。あとは `-n -P` で高速化、`-t` で kill 連携。 | やりたいこと | コマンド | | -------------------------- | ------------------------------- | | ポート占有を調べる | `lsof -i :8080` | | LISTEN 中のポート一覧 | `lsof -iTCP -sTCP:LISTEN -P -n` | | プロセスの開くファイル | `lsof -p ` | | ユーザーの開くファイル | `lsof -u ` | | ファイルを掴むプロセス | `lsof /path/to/file` | | ディレクトリ配下を再帰調査 | `lsof +D /path` | | 削除済みで開かれたファイル | `lsof -nP \| grep '(deleted)'` | | PID のみ出力(kill 連携) | `lsof -t /path` | ::: tip **コピペ用:調査テンプレ** ```bash # ポートの占有犯を探す sudo lsof -i :8080 -P -n # device is busy の犯人を探す sudo lsof +D /mnt/usb # 削除済みで容量を食っているファイル sudo lsof -nP | grep '(deleted)' ``` ::: ## 次に読む {#next} - [netstat/ss でポートと接続状態を確認する](/articles/tutorials/netstat-ss-basics) - [ps・top・kill でプロセスを管理する](/articles/tutorials/process-management-basics) - [strace でシステムコールを追う](/articles/tutorials/strace-basics) # LVMとパーティション管理の実践 - ストレージの柔軟な運用 Source: https://penguin-gym-linux.com/articles/tutorials/lvm-partition-practical ## LVM とは何か — なぜ使うのか {#what-is-lvm} LVM(Logical Volume Manager)は物理ディスクを仮想的なボリュームとして抽象化し、**容量の動的拡張・スナップショット・複数ディスクの統合**を可能にする仕組み。通常のパーティションは一度切ると変更が難しいが、LVM はダウンタイムなしで容量を増減できる。 結論:「ディスクを追加してオンラインで容量を増やしたい」「定期バックアップ前にスナップショットを取りたい」というユースケースに LVM を使う。 ::: tip **LVM が向いているケース** - 本番稼働中のサーバで `/var` や `/home` の容量が逼迫している - 将来的にディスク追加が見込まれるシステム - DB やコンテナ環境でスナップショットバックアップを使いたい ::: ## LVM の 3 層構造を理解する {#lvm-architecture} LVM は 3 つの抽象レイヤーで構成される。 | 層 | コンポーネント | 役割 | | ------ | --------------------- | ----------------------------------- | | 物理層 | PV(Physical Volume) | 実際のディスク / パーティション | | 中間層 | VG(Volume Group) | PV をまとめたストレージプール | | 論理層 | LV(Logical Volume) | VG から切り出した仮想パーティション | ``` /dev/sdb /dev/sdc ←── PV(物理ボリューム) ↘ ↙ vg_data ←── VG(ボリュームグループ) ↓ ↓ lv_app lv_logs ←── LV(論理ボリューム) ``` 1 つの VG に複数の PV をまとめられるため、ディスクをまたいだ単一ボリュームを作れる。LV のサイズは VG の空き容量の範囲で自由に変更できる。 ## 現在の LVM 状態を確認する {#check-status} 操作前に必ず現状を把握する。`pvs` / `vgs` / `lvs` が基本コマンド。 ```bash sudo pvs ``` ```output PV VG Fmt Attr PSize PFree /dev/sdb vg_data lvm2 a-- 100.00g 50.00g ``` ```bash sudo vgs ``` ```output VG #PV #LV #SN Attr VSize VFree vg_data 1 1 0 wz--n- 100.00g 50.00g ``` ```bash sudo lvs ``` ```output LV VG Attr LSize Pool Origin Data% lv_app vg_data -wi-ao---- 50.00g ``` 詳細が必要な場合は `pvdisplay` / `vgdisplay` / `lvdisplay` を使う。 ```bash sudo pvdisplay /dev/sdb sudo vgdisplay vg_data sudo lvdisplay /dev/vg_data/lv_app ``` ## LVM を新規に構築する {#create-lvm} ### 1. 物理ボリューム(PV)の作成 ```bash sudo pvcreate /dev/sdb ``` ```output Physical volume "/dev/sdb" successfully created. ``` ::: danger `pvcreate` はディスクの既存データを消去する。`lsblk` で対象デバイスを確認してから実行すること。 ```bash lsblk # NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT # sdb 8:16 0 100G 0 disk ← 未使用であることを確認 ``` ::: ### 2. ボリュームグループ(VG)の作成 ```bash sudo vgcreate vg_data /dev/sdb ``` ```output Volume group "vg_data" successfully created ``` 複数ディスクをまとめてプールにする場合: ```bash sudo vgcreate vg_data /dev/sdb /dev/sdc ``` ### 3. 論理ボリューム(LV)の作成 ```bash # サイズを指定して作成 sudo lvcreate -L 50G -n lv_app vg_data # VG の空き容量をすべて使う sudo lvcreate -l 100%FREE -n lv_app vg_data ``` ### 4. ファイルシステムの作成とマウント ```bash sudo mkfs.ext4 /dev/vg_data/lv_app sudo mkdir -p /mnt/app sudo mount /dev/vg_data/lv_app /mnt/app ``` 再起動後も自動マウントするには fstab に追記する。 ```bash # UUID を確認 sudo blkid /dev/vg_data/lv_app ``` ```output /dev/vg_data/lv_app: UUID="a1b2c3d4-e5f6-7890-abcd-ef1234567890" TYPE="ext4" ``` `/etc/fstab` に以下を追記: ``` UUID=a1b2c3d4-e5f6-7890-abcd-ef1234567890 /mnt/app ext4 defaults 0 2 ``` ## 論理ボリュームを拡張する {#extend-lv} LVM 最大の利点:**マウントしたまま(オンラインで)容量を拡張できる**。 ### VG に空きがある場合 — 最も簡単 ```bash # 10GB 追加し、ファイルシステムも同時に拡張(-r で resize2fs を自動実行) sudo lvextend -L +10G -r /dev/vg_data/lv_app ``` ```output Size of logical volume vg_data/lv_app changed from 50.00 GiB (12800 extents) to 60.00 GiB (15360 extents). Logical volume vg_data/lv_app successfully resized. resize2fs 1.46.5 (30-Dec-2021) Filesystem at /dev/vg_data/lv_app is mounted on /mnt/app; on-line resizing required The filesystem on /dev/vg_data/lv_app is now 15728640 blocks long. ``` `-r` を使わない場合は `lvextend` 後に手動でファイルシステムを拡張する。 ```bash sudo lvextend -L +10G /dev/vg_data/lv_app sudo resize2fs /dev/vg_data/lv_app # ext4 # sudo xfs_growfs /mnt/app # XFS の場合 ``` ::: warning XFS は **縮小できない**。拡張のみ対応。ext4 は `resize2fs` で縮小も可能だが、オフライン(アンマウント)が必要。 ::: ### VG の空きが足りない場合 — 新しいディスクを追加 ```bash # 1. 新ディスクを PV として初期化 sudo pvcreate /dev/sdc # 2. 既存 VG に追加 sudo vgextend vg_data /dev/sdc # 3. VG の空き容量が増えたことを確認 sudo vgs vg_data # 4. LV を拡張 sudo lvextend -L +100G -r /dev/vg_data/lv_app ``` ::: tip **拡張前の確認コマンド** ```bash # VG の空き容量 sudo vgs vg_data # LV の現在サイズ sudo lvs /dev/vg_data/lv_app # ファイルシステムの使用状況 df -h /mnt/app ``` ::: ## スナップショットを作成・活用する {#snapshot} スナップショットはある時点の LV の状態を保持するコピー。更新前のバックアップや安全なシステム変更に使う。 ```bash # 元 LV: /dev/vg_data/lv_app # スナップショット名: lv_app_snap(5GB を確保) sudo lvcreate -L 5G -s -n lv_app_snap /dev/vg_data/lv_app ``` スナップショットをマウントして参照: ```bash sudo mkdir -p /mnt/app_snap sudo mount -o ro /dev/vg_data/lv_app_snap /mnt/app_snap ``` スナップショットから復元(元の LV を上書き): ```bash sudo umount /mnt/app sudo lvconvert --merge /dev/vg_data/lv_app_snap # マージ後スナップショットは自動削除される ``` ::: warning スナップショットのサイズが満杯になると**無効化**される。元 LV への書き込み量を見積もって余裕を持ったサイズを確保すること。 ```bash # スナップショットの使用率を確認(Data% 列) sudo lvs /dev/vg_data/lv_app_snap ``` ::: ## 論理ボリュームと VG を削除する {#remove-lv} ```bash # 1. アンマウント sudo umount /mnt/app # 2. LV 削除 sudo lvremove /dev/vg_data/lv_app # 3. VG 削除(LV が存在しない状態で) sudo vgremove vg_data # 4. PV 削除 sudo pvremove /dev/sdb ``` ::: danger 削除操作は取り消せない。実行前に `lvs` / `vgs` / `pvs` で対象を必ず確認し、データのバックアップを取ること。 ::: ## よくあるトラブルと対処法 {#troubleshooting} ### `lvextend` 後に `df` でサイズが変わらない ```bash # ファイルシステムの拡張を忘れている sudo resize2fs /dev/vg_data/lv_app # ext4 sudo xfs_growfs /mnt/app # XFS ``` ### `vgextend` が失敗する — デバイスが見つからない ```bash # PV として初期化されているか確認 sudo pvs sudo pvscan ``` ### 論理ボリュームが inactive になっている ```bash sudo lvchange -ay /dev/vg_data/lv_app ``` ### `lvs` で `Data%` がスナップショットで高くなっている スナップショットの書き込みが増え容量が逼迫している。拡張か新スナップショットへの置き換えを検討する。 ```bash # スナップショットを拡張 sudo lvextend -L +5G /dev/vg_data/lv_app_snap ``` ## 次に読む {#next} - [ディスク管理入門 - fdisk/lsblkでストレージを確認する](/articles/tutorials/disk-management-basics) - [マウントとfstab入門 - ファイルシステムの接続方法](/articles/tutorials/mount-fstab-basics) - [du と df の違い - ディスク容量を正しく測る](/articles/tutorials/du-df-difference) # mktemp 入門 - 安全な一時ファイル・ディレクトリを作る Source: https://penguin-gym-linux.com/articles/tutorials/mktemp-temp-files ## この記事で解決できること {#intro} - `mktemp` で **一時ファイル / ディレクトリを安全に作る** 型が分かる - `file.$$` や固定名の一時ファイルが **なぜ危険か** を理解できる - `trap` と組み合わせて **後始末まで含めた定石** が書ける ::: tip **結論(実務の型)** - 一時ファイル → `tmp=$(mktemp)` - 一時ディレクトリ → `dir=$(mktemp -d)` - 作ったら必ず `trap 'rm -rf "$dir"' EXIT` で **自動削除** ::: ::: warning **前提(対象環境)** - GNU coreutils(`mktemp --version` で確認、本記事は 9.4 で検証) - 一般的な Linux ディストリビューション(Ubuntu / RHEL 系など) - BSD / macOS の `mktemp` はオプション体系が異なる ::: ## mktemp とは何か? {#what} > **結論**: `mktemp` は一意な名前の一時ファイル・ディレクトリを安全に作成し、そのパスを標準出力に返す coreutils コマンド。 `mktemp` は名前が衝突しない一時ファイルを **作成しつつ**、そのパスを出力する。アプリケーションが自前でファイル名を組み立てる必要がなくなる。 ```bash $ mktemp /tmp/tmp.A1b2C3d4E5 ``` テンプレートを省略すると `$TMPDIR`(未設定なら `/tmp`)に `tmp.XXXXXXXXXX` という名前で作られる。`X` の部分がランダムな文字に置換される。 man の記述(GNU coreutils 9.4): ```output Create a temporary file or directory, safely, and print its name. Files are created u+rw, and directories u+rwx, minus umask restrictions. ``` ポイントは **「safely」** と **「print its name」** の 2 点。作成と命名がアトミックに行われ、所有者だけが読み書きできる権限が付く。 ## なぜ mktemp を使うのか? {#why} > **結論**: 固定名や `$$`(PID)由来の名前は予測可能で、競合・上書き・シンボリックリンク攻撃の温床になる。`mktemp` はこれを原理的に防ぐ。 ### やりがちな危険パターン ```bash # NG: 固定名 tmpfile=/tmp/myapp.tmp # NG: PID は予測されやすく、同時実行で衝突する tmpfile=/tmp/myapp.$$ ``` これらの問題点: - **競合(race condition)**: 同じスクリプトを複数同時に走らせると名前が衝突し、片方のデータを壊す - **シンボリックリンク攻撃**: 攻撃者が予測した名前で `/tmp` に事前にシンボリックリンクを張っておくと、スクリプトが意図しないファイルを書き換えてしまう - **権限の取りこぼし**: `touch` や `>` での作成は umask 次第で他人に読まれうる ### mktemp が解決すること ```bash $ tmpfile=$(mktemp) $ stat -c '%a %n' "$tmpfile" 600 /tmp/tmp.A1b2C3d4E5 ``` `mktemp` は **作成と命名を一度の不可分な操作** で行い、既存ファイルがあれば失敗する。ファイルは `u+rw`(umask 適用後で通常 `600`)、ディレクトリは `u+rwx`(通常 `700`)で作られるため、最初から所有者専用になる。 ::: warning 予測可能な名前を使う限り、後から `chmod` しても競合の窓は塞げない。**作成の瞬間に安全であること** が重要。 ::: ## 一時ファイル・ディレクトリを作るには? {#basic} > **結論**: ファイルは `mktemp`、ディレクトリは `mktemp -d`。返ってきたパスを変数に取り、以降はその変数を使う。 ### 一時ファイル ```bash $ tmpfile=$(mktemp) $ echo "data" > "$tmpfile" $ cat "$tmpfile" data ``` ### 一時ディレクトリ ```bash $ tmpdir=$(mktemp -d) $ echo "$tmpdir" /tmp/tmp.Xy9Zq2Lk7P ``` `-d`(`--directory`)でファイルではなくディレクトリを作る。複数の中間ファイルをまとめて置きたいときに使い、後始末は `rm -rf "$tmpdir"` 一発で済む。 ::: tip 変数に取るときは必ず `"$tmpfile"` のように **ダブルクォート** すること。`/tmp` 以外の `TMPDIR` にスペースを含むパスが入っていても壊れない。 ::: ## 作成場所や名前を細かく指定するには? {#template} > **結論**: テンプレートで名前の形を、`-p` で作成先ディレクトリを、`--suffix` で拡張子を制御できる。テンプレートには末尾に 3 個以上の連続した `X` が必要。 ### テンプレートを渡す ```bash $ mktemp myapp.XXXXXX myapp.k3Df9a ``` `X` がランダム文字に置換される。**最後の構成要素に連続した `X` が 3 個以上** 必要というのが GNU の仕様。 ### 作成先を指定する(-p / --tmpdir) ```bash $ mktemp -p /var/tmp myapp.XXXXXX /var/tmp/myapp.q7Zb2K ``` `-p DIR`(`--tmpdir[=DIR]`)でベースディレクトリを指定する。`/tmp` は再起動で消えることがあるため、長めに残したい一時データは `/var/tmp` を選ぶといった使い分けに使う。 ### 拡張子を付ける(--suffix) ```bash $ mktemp --suffix=.log myapp.XXXXXX myapp.a8Kd2p.log ``` ツールが拡張子で形式を判定する場合に便利。`SUFF` にスラッシュは使えない。 ::: details テンプレートと -p / -t の関係(補足) - `-p DIR` 指定時、テンプレートは **絶対パスにできない**。スラッシュは含められるが、`mktemp` が作るのは最終構成要素のみ。 - `-t` は「テンプレートを単一のファイル名要素として `$TMPDIR` 等の下に作る」古いオプションで、現在は **deprecated**。新規スクリプトでは `-p` を使う。 ::: ## スクリプトで確実に後始末するには? {#cleanup} > **結論**: `trap 'rm -rf "$tmpdir"' EXIT` を作成直後に仕掛ければ、正常終了でもエラー終了でも一時ファイルが残らない。 一時ファイルを作るスクリプトの定石: ```bash #!/usr/bin/env bash set -euo pipefail tmpdir=$(mktemp -d) trap 'rm -rf "$tmpdir"' EXIT # ここで $tmpdir を自由に使う curl -s https://example.com/data.json > "$tmpdir/data.json" jq '.items' "$tmpdir/data.json" # スクリプト終了時、trap が自動で $tmpdir を削除する ``` ポイント: - `mktemp -d` の **直後** に `trap` を仕掛ける(作成と後始末をセットで書く) - `EXIT` を捕捉するので、`set -e` による途中終了でもクリーンアップが走る - ディレクトリごと作っておけば、中に何ファイル増えても `rm -rf` 一発 詳しくは [trap でシグナルと後始末を扱う](/articles/tutorials/trap-signal-handling) を参照。 ::: warning `trap` の対象を `$tmpdir`(mktemp が返した一意なパス)に限定すること。`/tmp/*` のような広いパターンを `rm -rf` するスクリプトは事故の元。 ::: ## 知っておきたいオプションと落とし穴 {#pitfalls} > **結論**: `-u`(dry-run)は名前だけ返して作成しないため安全性が崩れる。基本は作成までさせる素の `mktemp` を使う。 | オプション | 意味 | 注意 | | --------------- | ----------------------------------------- | ------------------------------------ | | `-d` | ファイルでなくディレクトリを作る | 後始末は `rm -rf` | | `-u` | 名前を表示するだけで作成しない(dry-run) | **競合の窓が開く。原則使わない** | | `-q` | 作成失敗時の診断メッセージを抑制 | スクリプトで自前にエラー処理する場合 | | `-p DIR` | 作成先ディレクトリを指定 | テンプレートは絶対パス不可 | | `--suffix=SUFF` | 末尾に拡張子等を付与 | スラッシュ不可 | ::: danger `-u` で得た名前を後から自分で `touch` するのは、`mktemp` を使う意味を失わせる典型的アンチパターン。作成からファイル名取得までを `mktemp` 本体に任せること。 ::: 返り値の取り違えにも注意: ```bash # NG: 作成に失敗してもそのまま進むと空文字を rm しかねない tmpfile=$(mktemp) # OK: 失敗を検知して止める tmpfile=$(mktemp) || exit 1 ``` ## まとめ / 次に読む {#next} - [trap でシグナルと後始末を扱う](/articles/tutorials/trap-signal-handling) - [/tmp の掃除とクリーンアップ](/articles/tutorials/tmp-cleanup) - [シェルスクリプト入門](/articles/tutorials/shell-scripting-basics) # modprobe / lsmod 入門 - カーネルモジュールの確認とロード Source: https://penguin-gym-linux.com/articles/tutorials/modprobe-lsmod ## この記事で解決できること {#intro} - `lsmod` で **いまロードされているモジュール** を確認できる - `modprobe` で **依存関係ごとモジュールをロード / アンロード** できる - `modinfo` / `blacklist` / 起動時自動ロードまで **実務の型** が身につく ::: tip **結論(実務の型)** - **状態を見る** → `lsmod`(読み取り専用、`/proc/modules` を整形表示) - **ロード / 削除する** → `modprobe`(依存解決つき。`insmod` / `rmmod` は基本使わない) - **設定する** → `/etc/modprobe.d/*.conf`(オプション・blacklist)/ `/etc/modules-load.d/*.conf`(起動時ロード) ::: ::: warning **前提(対象環境)** - OS:Ubuntu / RHEL 系どちらでも共通 - モジュール本体は `/lib/modules/$(uname -r)/` 配下に存在 - `modprobe` / `modinfo` での変更操作は基本 `root`(`sudo`)が必要 ::: ## lsmod とは? {#lsmod} > **結論**: `lsmod` は現在ロード済みのカーネルモジュール一覧を表示する読み取り専用コマンド。実体は `/proc/modules` の整形出力。 ```bash $ lsmod ``` ```output Module Size Used by nf_conntrack 172032 2 nf_nat,xt_conntrack xfs 2068480 1 vfat 24576 1 ``` 列の意味: - **Module**:モジュール名 - **Size**:メモリ上のサイズ(バイト) - **Used by**:参照カウントと、依存している側のモジュール名 ::: tip `Used by` のカウントが `0` でないモジュールは、他から使用中のためそのままでは外せない。先に依存側を外すか、`modprobe -r` に依存解決を任せる。 ::: 特定モジュールがロード済みか確認したいだけなら `grep` と組み合わせる。 ```bash $ lsmod | grep nf_conntrack ``` ## modprobe で何ができるのか? {#modprobe} > **結論**: `modprobe` は依存関係を自動解決してモジュールをロード / アンロードする。手動の `insmod` / `rmmod` と違い依存モジュールも面倒を見る。 ### ロードする ```bash $ sudo modprobe nf_conntrack ``` 依存しているモジュールがあれば、それらも自動で先にロードされる。`.ko` ファイルのパスを指定する必要はなく、モジュール名だけで済む。 ### アンロードする(-r) ```bash $ sudo modprobe -r nf_conntrack ``` `-r`(`--remove`)は対象モジュールに加え、それだけが使っていた依存モジュールも一緒に外す。使用中(`Used by` が非ゼロ)の場合は失敗する。 ### 何が起きるか先に見る(-n -v) ```bash $ modprobe -n -v nf_conntrack ``` ```output insmod /lib/modules/6.8.0-106-generic/kernel/net/netfilter/nf_conntrack.ko.zst ``` `-n`(`--dry-run`)は実際にはロードせず、`-v`(`--verbose`)と組み合わせると **実行されるはずの手順** を表示する。本番前の確認に有効。 ::: warning `modprobe` はモジュール本体を `/lib/modules/$(uname -r)/` 配下から探す。`Module not found` が出る場合、稼働カーネルとモジュールのバージョン不一致(カーネル更新後に再起動していない等)を疑う。 ::: ## insmod / rmmod との違いは? {#insmod} > **結論**: `insmod` / `rmmod` は単一の `.ko` を直接操作する低レベルコマンド。依存解決をしないため、実務では `modprobe` を使う。 | 操作 | 推奨(依存解決あり) | 低レベル(依存解決なし) | | ---------- | -------------------- | ------------------------ | | ロード | `modprobe mod` | `insmod /path/mod.ko` | | アンロード | `modprobe -r mod` | `rmmod mod` | | 名前指定 | モジュール名 | フルパス(insmod) | `insmod` は依存モジュールを自動ではロードしないため、依存が満たされないと `Unknown symbol` 等で失敗する。特別な理由がない限り `modprobe` を使う。 ## modinfo でモジュールの詳細を見る {#modinfo} > **結論**: `modinfo` はモジュールのファイルパス・説明・依存・設定可能パラメータを表示する。ロード前の調査やパラメータ確認に使う。 ```bash $ modinfo nf_conntrack ``` ```output filename: /lib/modules/6.8.0-106-generic/.../nf_conntrack.ko.zst license: GPL description: Netfilter connection tracking core depends: nf_defrag_ipv4,nf_defrag_ipv6,libcrc32c parm: expect_hashsize:uint ``` 注目すべき行: - **depends**:依存モジュール(`modprobe` が自動解決する対象) - **parm**:起動時 / ロード時に渡せるパラメータ - **filename**:実体の `.ko` パス(稼働カーネルと一致しているか確認) パラメータ付きで一時的にロードする例: ```bash $ sudo modprobe nf_conntrack expect_hashsize=2048 ``` ## モジュールのパラメータや無効化を永続化するには? {#config} > **結論**: 永続設定は `/etc/modprobe.d/*.conf` に書く。`options` でパラメータ、`blacklist` で自動ロード抑止を指定する。 ### パラメータを永続化(options) `/etc/modprobe.d/nf_conntrack.conf`: ```output options nf_conntrack expect_hashsize=2048 ``` ### モジュールを無効化(blacklist) 特定モジュールの自動ロードを抑止したいとき(例: 競合するドライバを止める)。 `/etc/modprobe.d/blacklist-mymod.conf`: ```output blacklist mymod ``` ::: warning `blacklist` は **他モジュールの依存としてロードされる経路までは止めない**。完全に阻止したい場合は `install mymod /bin/true` を併用する。また initramfs に組み込まれるモジュールを止めるには `sudo update-initramfs -u`(Debian 系)での再生成が必要。 ::: ### 起動時に必ずロードする `/etc/modules-load.d/mymod.conf` にモジュール名を1行ずつ書くと、`systemd-modules-load.service` が起動時にロードする。 ```output nf_conntrack ``` ## depmod は何のためにある? {#depmod} > **結論**: `depmod` はモジュール間の依存マップ(`modules.dep`)を生成する。`modprobe` の依存解決はこのファイルに依存している。 新しい `.ko` を `/lib/modules/$(uname -r)/` に追加したり、カーネルを更新したときは依存マップを更新する。 ```bash $ sudo depmod -a ``` 通常はパッケージ管理(`apt` / `dnf`)やカーネル更新時に自動実行されるため、手動で叩くのは独自ビルドのモジュールを入れた場合などに限られる。 ## トラブルシューティング {#troubleshoot} > **結論**: 失敗の多くは「バージョン不一致」「使用中」「権限不足」のいずれか。エラー文言から切り分ける。 | 症状 | 主な原因 | 対処 | | -------------------------- | ------------------------------------- | --------------------------------------------------------- | | `Module not found` | 稼働カーネルと不一致 / 未インストール | `uname -r` 確認 → 再起動 / `depmod -a` | | `Module ... is in use` | `Used by` が非ゼロ | 依存側を停止、または `modprobe -r`(単体 `rmmod` は不可) | | `Operation not permitted` | 権限不足 | `sudo` を付ける | | `Unknown symbol in module` | 依存未解決(`insmod` 使用時) | `modprobe` を使う / `depmod -a` | ロード / アンロード時のカーネル側エラーは `dmesg` に出る。 ```bash $ sudo dmesg | tail ``` ## まとめ:確認と操作を分けて考える {#summary} > **結論**: 状態確認は `lsmod` / `modinfo`、操作は `modprobe`、永続設定は `/etc/modprobe.d/` と `/etc/modules-load.d/` に集約する。 - 見る → `lsmod`(一覧)/ `modinfo`(詳細) - 操作 → `modprobe`(ロード)/ `modprobe -r`(削除) - 設定 → `options`(パラメータ)/ `blacklist`(無効化)/ `modules-load.d`(起動時) - 困ったら → `dmesg` と `modprobe -n -v` で切り分け ## 次に読む {#next} - [dmesg でカーネルメッセージを読む](/articles/tutorials/dmesg-kernel-messages) - [journalctl でログを追う](/articles/tutorials/journalctl-basics) - [lsblk / blkid でブロックデバイスを確認する](/articles/tutorials/lsblk-blkid-basics) # マウントとfstab入門 - ファイルシステムの接続方法 Source: https://penguin-gym-linux.com/articles/tutorials/mount-fstab-basics ## この記事で解決できること {#intro} - Linux の「マウント」の概念と仕組みが分かる - `mount` コマンドで USB ドライブや追加ディスクを手動マウントできる - `/etc/fstab` を読み書きして再起動後も自動マウントを維持できる - UUID でデバイスを安全に指定できる - `umount` で安全にアンマウントできる ::: tip **結論(実務の型)** - **マウント** = デバイスをディレクトリ(マウントポイント)に接続する操作 - **手動マウント**: `sudo mount /dev/sdX /mnt/point` - **永続化**: `/etc/fstab` に記述 → 再起動後も自動マウント - **デバイス名でなく UUID を使う**(デバイス名は再起動・接続順で変わる) ::: ::: warning **前提(対象環境)** - Ubuntu / Debian 系を主に想定(RHEL 系でも基本コマンドは同一) - `mount` / `umount` / `fstab` 編集は root 権限が必要(`sudo` を使用) ::: ## マウントとは何か? {#what-is-mount} ストレージデバイスをディレクトリに「接続」してファイルとしてアクセスできるようにする操作がマウント。マウント前はデバイスが存在しても中身は見えない。 ``` / ← ルート(/ にマウント済み) ├── home/ ← 別パーティションの場合は /home にマウント ├── mnt/ │ └── usb/ ← USBドライブをここにマウントすると中身が見える └── var/ ``` Windows のドライブレター(C:\、D:\)と異なり、Linux は単一のディレクトリツリーにすべてを統合する。デバイスの「入り口」となるディレクトリをマウントポイントと呼ぶ。 ## マウント済みの状態を確認するには? {#check-mount} `findmnt` が最も見やすい。`mount` コマンドの出力でも確認できる。 ```bash $ findmnt ``` ```output TARGET SOURCE FSTYPE OPTIONS / /dev/sda1 ext4 rw,relatime ├─/sys sysfs sysfs rw,nosuid,nodev,noexec ├─/proc proc proc rw,nosuid,nodev,noexec ├─/dev udev devtmpfs rw,nosuid ├─/run tmpfs tmpfs rw,nosuid,nodev └─/home /dev/sda2 ext4 rw,relatime ``` 特定のマウントポイントだけ確認する場合: ```bash $ findmnt /home TARGET SOURCE FSTYPE OPTIONS /home /dev/sda2 ext4 rw,relatime ``` ## デバイス名と UUID を確認するには? {#check-devices} マウント前に対象デバイスを把握する。 ```bash $ lsblk ``` ```output NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 20G 0 disk ├─sda1 8:1 0 19G 0 part / └─sda2 8:2 0 1G 0 part /home sdb 8:16 1 8G 0 disk └─sdb1 8:17 1 8G 0 part ``` `sdb1` の MOUNTPOINTS が空 = まだマウントされていない。 UUID を確認する: ```bash $ sudo blkid ``` ```output /dev/sda1: UUID="a1b2c3d4-1111-2222-3333-444455556666" TYPE="ext4" /dev/sda2: UUID="b2c3d4e5-2222-3333-4444-555566667777" TYPE="ext4" /dev/sdb1: UUID="c3d4e5f6-3333-4444-5555-666677778888" TYPE="vfat" ``` 特定デバイスだけ確認したい場合: ```bash $ sudo blkid /dev/sdb1 /dev/sdb1: UUID="c3d4e5f6-3333-4444-5555-666677778888" TYPE="vfat" ``` ## mount コマンドで手動マウントする {#mount-command} マウントポイント(空のディレクトリ)を作成してからマウントする。 ```bash # マウントポイントを作成 $ sudo mkdir -p /mnt/usb # マウント(ファイルシステムは自動検出) $ sudo mount /dev/sdb1 /mnt/usb # 確認 $ findmnt /mnt/usb TARGET SOURCE FSTYPE OPTIONS /mnt/usb /dev/sdb1 vfat rw,relatime ``` ファイルシステムを明示する場合(`-t`): ```bash $ sudo mount -t ext4 /dev/sdb1 /mnt/data $ sudo mount -t vfat /dev/sdb1 /mnt/usb $ sudo mount -t ntfs /dev/sdb1 /mnt/win ``` ::: tip `-t` を省略すると `mount` がファイルシステムを自動判別する。ext4 / vfat / ntfs は省略可。 ::: マウントオプション(`-o`): | オプション | 意味 | | ---------- | ---------------------------- | | `ro` | 読み取り専用 | | `rw` | 読み書き可能(デフォルト) | | `noexec` | 実行ファイル禁止 | | `nosuid` | SUID ビット無効化 | | `remount` | 既存マウントのオプション変更 | ```bash # 読み取り専用でマウント $ sudo mount -o ro /dev/sdb1 /mnt/usb # 読み書きに変更して再マウント $ sudo mount -o remount,rw /mnt/usb ``` ## umount でアンマウントする {#umount} デバイスを物理的に取り外す前にアンマウントが必要。書き込みバッファをフラッシュしてデータ損失を防ぐ。 ```bash $ sudo umount /mnt/usb # またはデバイス名で指定 $ sudo umount /dev/sdb1 ``` ::: warning **「device is busy」エラーが出る場合** ```bash # 使用中のプロセスを確認 $ lsof /mnt/usb ``` `lsof` でプロセスを特定して終了させてから再実行する。対象ディレクトリに `cd` している場合も busy になるため、別のディレクトリに移動してから `umount` する。 ::: ## /etc/fstab とは何か? {#fstab-what} `/etc/fstab`(filesystem table)は OS 起動時に自動マウントするデバイスの設定ファイル。ここに記述したエントリは起動時に自動でマウントされる。 ```bash $ cat /etc/fstab ``` ```output # UUID=a1b2c3d4-... / ext4 defaults 0 1 UUID=b2c3d4e5-... /home ext4 defaults 0 2 UUID=f9g0h1i2-... none swap sw 0 0 ``` ::: warning `/etc/fstab` を誤って書くと**起動不能**になる。編集後は必ず `sudo mount -a` で動作確認してから再起動すること。 ::: ## fstab エントリの書き方 {#fstab-format} 各フィールドの意味: | フィールド | 説明 | | --------------- | ----------------------------------------------------------------- | | `` | デバイス(UUID 推奨)またはデバイスパス | | `` | マウント先ディレクトリ(swap は `none`) | | `` | ファイルシステム(ext4 / vfat / ntfs / swap など) | | `` | マウントオプション(`defaults` = rw,suid,exec,auto,nouser,async) | | `` | dump バックアップ対象(0: 無効、1: 有効)。通常は 0 | | `` | 起動時 fsck 実行順(0: スキップ、1: ルート最初、2: その他) | ```bash # 基本的なエントリ例(ext4 パーティション) UUID=c3d4e5f6-3333-4444-5555-666677778888 /mnt/data ext4 defaults 0 2 ``` ## UUID を使う理由は? {#uuid-why} `/dev/sdb` のようなデバイス名は USB の接続順序やカーネルの認識順で変化する。UUID は固定値なので、デバイスを差し替えても正しいデバイスをマウントできる。 ```bash # デバイス名指定(非推奨) /dev/sdb1 /mnt/data ext4 defaults 0 2 # ↑ 再起動後 sdc1 になることがある # UUID 指定(推奨) UUID=c3d4e5f6-3333-4444-5555-666677778888 /mnt/data ext4 defaults 0 2 ``` ## fstab に書いて永続化する {#fstab-persist} 追加ディスクや USB を再起動後も自動マウントする手順: ```bash # 1. UUID を確認する $ sudo blkid /dev/sdb1 /dev/sdb1: UUID="c3d4e5f6-3333-4444-5555-666677778888" TYPE="ext4" # 2. マウントポイントを作成する $ sudo mkdir -p /mnt/data # 3. fstab を編集する $ sudo nano /etc/fstab ``` 以下の行を追加: ```bash UUID=c3d4e5f6-3333-4444-5555-666677778888 /mnt/data ext4 defaults 0 2 ``` ```bash # 4. fstab を即時適用して動作確認する(再起動不要) $ sudo mount -a # 5. マウントを確認する $ findmnt /mnt/data TARGET SOURCE FSTYPE OPTIONS /mnt/data /dev/sdb1 ext4 rw,relatime ``` ::: tip `sudo mount -a` は fstab の全エントリをマウントするコマンド。再起動の代わりに使える動作確認の必須手順。エラーなく終了すれば fstab の書き方は正しい。 ::: ## 次に読む {#next} - [ディスク管理入門 - fdisk/lsblkでストレージを確認する](/articles/tutorials/disk-management-basics) - [systemdユニットファイルを書く - 自作サービスの登録](/articles/tutorials/systemd-unit-creation) - [du と df の違い - ディスク容量を正しく測る](/articles/tutorials/du-df-difference) # nano エディタ入門 - Vim より先に覚える編集 Source: https://penguin-gym-linux.com/articles/tutorials/nano-basics ## この記事でわかること {#what-you-will-learn} - nano を Vim より先に覚える理由 - nano の起動・編集・保存・終了の基本操作 - まず覚えるべき 4 つのショートカット(Ctrl+O / Ctrl+X / Ctrl+W / Ctrl+K) - 検索・置換の使い方 - sudo nano で設定ファイルを安全に編集する方法 ## なぜ nano を先に覚えるのか {#why-nano} > **結論**: nanoはショートカットが画面下に常時表示されるため、初心者がモードを覚えずすぐ使える。 ::: dialogue @lina: テキストファイルを編集したいんですけど、Vim って難しそうで… @linny: そう思うよね!だから最初は nano を覚えよう。Vim より圧倒的に簡単だよ。 ::: Linux にはいくつかのテキストエディタがあります。中でも `nano` は**キーボードショートカットが画面下に常時表示**されるため、初心者でも迷わず使えます。 ::: tip **nano の特徴** - ショートカットが常に画面下に表示される - 特殊なモードなし(開いたらすぐ編集できる) - サーバ管理の現場でも普通に使われる ::: ## nano を起動する {#start} > **結論**: nano ファイル名でファイルを開く。存在しないファイルを指定すると新規作成になる。 ::: dialogue @lina: どうやって起動するんですか? @linny: `nano` コマンドの後にファイル名を書くだけ! ::: ```bash nano ファイル名 ``` 例:`memo.txt` を開く場合 ```bash nano memo.txt ``` ::: tip まだファイルが存在しない場合は、**新規作成**されます。 ::: ### 起動後の画面 {#screen} ```output GNU nano 6.2 memo.txt ▌ ^G ヘルプ ^O 書き込み ^W 検索 ^K 切り取り ^T 実行 ^X 終了 ^R 読み込み ^\ 置換 ^U 貼り付け ^J 整形 ``` ::: dialogue @lina: 上に何もなくて、下にいっぱい書いてあります。 @linny: 下に表示されてるのがショートカット一覧だよ。`^` はキーボードの `Ctrl` キーのこと。`^O` は `Ctrl+O` って意味だね。 ::: ::: highlight **画面の見方** - **一番上の行**: ファイル名 - **真ん中の大きい空白**: 編集エリア - **一番下の 2 行**: ショートカット一覧(`^` = `Ctrl`) ::: ## テキストを編集する {#edit} > **結論**: nanoは開いた瞬間から編集モードで、矢印キーで移動しそのまま入力・Ctrl+Kで行削除できる。 ::: dialogue @lina: 開いたら、そのまま文字を入力すればいいんですか? @linny: そうそう!nano は開いた瞬間から編集モードだから、何も押さなくてもそのまま入力できるよ。Vim みたいに「i を押してから」とか不要。 ::: 開いたらカーソルのある場所にすぐ入力できます。 **移動キー**: | キー | 動作 | | -------------- | ---------------- | | 矢印キー | カーソル移動 | | Home / End | 行頭・行末 | | Page Up / Down | 1 画面スクロール | | Ctrl+A | 行頭へ | | Ctrl+E | 行末へ | **削除**: | キー | 動作 | | --------- | ---------------------- | | Backspace | カーソル前の文字を削除 | | Delete | カーソル後の文字を削除 | | Ctrl+K | 行ごと切り取り | ## 保存する {#save} > **結論**: Ctrl+OでOut(書き出し)を開始しEnterを押すと上書き保存、別名保存はファイル名を変えてEnterする。 ::: dialogue @lina: 編集できました!保存はどうするんですか? @linny: `Ctrl+O` だよ。「O」は「Output(書き出し)」のイメージ。 ::: **`Ctrl+O`** を押すと、画面下にファイル名の確認が表示されます。 ```output ファイル名を入力してください: memo.txt ``` そのまま **Enter** を押せば上書き保存されます。 ::: tip 別名で保存したい場合は、ファイル名を変えてから Enter を押します。 ::: ::: dialogue @lina: `Ctrl+O` → Enter で保存できました! @linny: バッチリ!次は終了だね。 ::: ## 終了する {#exit} > **結論**: Ctrl+Xで終了し、未保存の変更があればY/N/Ctrl+Cで保存・破棄・キャンセルを選択できる。 **`Ctrl+X`** で終了します。 ::: dialogue @lina: 未保存のまま Ctrl+X を押したらどうなりますか? @linny: ちゃんと聞いてくれるよ。捨てる前に確認が出るから安心して。 ::: 未保存の変更がある場合、確認メッセージが表示されます。 ```output 保存しますか(変更されています)(Yes=Y, No=N)? ``` | キー | 動作 | | ------ | -------------------------- | | Y | 保存して終了 | | N | 保存せず終了(変更を破棄) | | Ctrl+C | キャンセル(編集に戻る) | ## よく使うショートカット {#shortcuts} > **結論**: まず覚えるべきはCtrl+O保存・Ctrl+X終了・Ctrl+W検索・Ctrl+K切り取りの4つだけでよい。 ::: dialogue @lina: ショートカット、全部覚えないといけないですか? @linny: 最初は 4 つだけ覚えれば十分!保存・終了・検索・切り取りと貼り付け。 ::: ::: highlight **まず覚える 4 つ** | ショートカット | 動作 | | ------------------- | -------------------------- | | `Ctrl+O` | 保存(ファイルに書き込む) | | `Ctrl+X` | 終了 | | `Ctrl+W` | 検索 | | `Ctrl+K` / `Ctrl+U` | 行の切り取り / 貼り付け | ::: ### その他の便利なショートカット {#more-shortcuts} | ショートカット | 動作 | | -------------- | ---------------------------- | | `Ctrl+G` | ヘルプを表示 | | `Ctrl+C` | カーソル位置(行番号)を表示 | | `Ctrl+V` | 次のページへスクロール | | `Ctrl+Y` | 前のページへスクロール | | `Alt+U` | 元に戻す(アンドゥ) | | `Alt+E` | やり直し(リドゥ) | ## 検索と置換 {#search} > **結論**: Ctrl+Wで検索、Ctrl+\で置換を開始し、置換時はY/N/Aで1件確認か全置換かを選択できる。 ::: dialogue @lina: ファイルが長くなって、特定の文字を探したいときは? @linny: `Ctrl+W` で検索できるよ。置換は `Ctrl+\` だね。 ::: ### 検索(Ctrl+W) {#search-basic} 1. `Ctrl+W` を押す 2. 検索したい文字を入力 3. Enter を押す 次の一致へ進むには **Ctrl+W** → **Enter** を繰り返します。 ### 置換(Ctrl+\) {#replace} 1. `Ctrl+\` を押す 2. 検索する文字を入力 → Enter 3. 置換後の文字を入力 → Enter 4. 1 つずつ確認(Y / N)か、全て置換(A) ## 実践:設定ファイルを編集してみよう {#practice} > **結論**: sudo nano /etc/hostsのようにsudoを付ければroot権限が必要な設定ファイルも編集できる。 ::: dialogue @lina: サーバで設定ファイルを変えるときも nano でいいんですか? @linny: うん!`sudo nano /etc/hosts` みたいに sudo をつければ OK。 ::: ```bash sudo nano /etc/hosts ``` ::: warning **設定ファイルを編集する前に** 重要なファイルを編集するときは、事前にバックアップを取ること。 ```bash sudo cp /etc/hosts /etc/hosts.bak ``` バックアップを作ってから `sudo nano /etc/hosts` で編集しよう。 ::: ## まとめ {#summary} ::: dialogue @lina: nano、思ったより全然難しくなかったです! @linny: でしょ?ショートカットが見えてるから迷わないんだよね。Vim に慣れるまでの間は nano を使って、少しずつ Vim も覚えていくといいよ。 ::: | やること | コマンド / キー | | -------------- | ----------------- | | ファイルを開く | `nano ファイル名` | | 保存する | `Ctrl+O` → Enter | | 終了する | `Ctrl+X` | | 検索する | `Ctrl+W` | | 行を切り取る | `Ctrl+K` | | 貼り付ける | `Ctrl+U` | | 元に戻す | `Alt+U` | ## 次に読む {#next} - [Vim 入門 - nano の次のステップ](/articles/tutorials/vim-basics) - [ファイル操作の基本(cp・mv・rm)](/articles/tutorials/file-operations-basics) - [シェルスクリプトの書き方入門](/articles/tutorials/shell-scripting-basics) # ncdu 入門 - 対話的にディスク使用量を調べて掃除する Source: https://penguin-gym-linux.com/articles/tutorials/ncdu-disk-usage ## この記事で解決できること {#intro-list} - `ncdu` を使って **容量の大きいディレクトリを対話的に辿る** 方法が分かります - 大きいファイルを見つけて **その場で安全に削除** できます - `du` を何度も打つ調査から **キー操作だけの調査** に切り替えられます - `ssh` 越しに **リモートサーバーの容量を調査** できます **対象読者**:Linux 入門者。`du` で容量を探すのに疲れた方。 ::: tip **言葉の整理** - **対話的**:コマンドを打ち直さず、画面を見ながらキー操作で進める方式です。「インタラクティブ」も同じ意味です。 - **ディレクトリ**:Windows や Mac でいうフォルダのことです。 - **スキャン**:対象のファイルを 1 つずつ調べて回ることです。「走査」とも言います。 - **ファイルシステム**:ディスクを区切って使うための仕組みです。この記事では「`/` や `/home` のように独立して容量を数える単位」と考えてください。 - **マウント**:ディスクをディレクトリに結びつけて使える状態にすることです。 - **標準出力 / 標準入力**:コマンドが結果を書き出す先と、コマンドが入力を受け取る口のことです。第 7 章で使います。 `ncdu` は調べるだけの使い方もできます。`-r` を付けて起動すれば、削除機能そのものが無効になります。最初は必ず `-r` を付けて練習してください。 なお `ncdu` は本サイトの仮想ターミナルには入っていません。実機の Linux か WSL で試してください。だからこそ、練習は `-r` から始めてください。 ::: ## 導入:リナの「du 連打」事件 {#intro} ::: dialogue @lina: ライニー先輩、ディスクが満杯になりました。`du` で大きいディレクトリを探しています。 @lina: でも `du` を打って、深い階層に `cd` して、また `du` を打って、の繰り返しです。これでは日が暮れます。 @linny: いわゆる「`du` 連打」だね。みんな一度は通る道だよ。 @linny: 実は `ncdu` という専用ツールを使うと、**矢印キーだけ** で済むんだ。 @lina: 矢印キーだけ、ですか。`cd` も `du` も打たなくていいんですか? @linny: そう。`ncdu` は画面の中をファイルマネージャーのように歩き回れる。大きいディレクトリを辿って、その場で削除までできるよ。 ::: ::: tip **結論を先に** - `ncdu` = **NCurses Disk Usage**。`du` の結果を **対話的な画面** で見られるツール - 矢印キーで階層を移動、`d` でその場削除、`s` でサイズ順ソート - まず `sudo apt install ncdu` で入れて `ncdu /` で起動するだけ ::: ## 1. ncdu とは何か? {#what} > **結論**: `ncdu` は `du` の結果を全画面の対話画面で見られるツール。矢印キーで容量の大きい場所まで掘り下げられる。 ::: dialogue @lina: そもそも `ncdu` は何の略なんですか? @linny: 「**NCurses Disk Usage**」の略だよ。NCurses は、端末の上で全画面の画面を作るライブラリだね。ライブラリは、部品として使えるプログラムの集まりのことだよ。 @linny: そのライブラリを使って、`du` の結果を **画面に表示** してくれるツールなんだ。 @lina: `du` の進化版のようなものですか? @linny: そのイメージで合っているよ。`du` は数字を並べて出すだけだね。 @linny: `ncdu` は大きい順に並べてくれる。Enter で中に入れるし、左矢印で戻れる。だから迷子にならずに、容量を使っている場所まで辿り着けるんだ。 ::: ::: tip **du との違い(ざっくり)** - `du` → 一回コマンドを打つと数字が出ます。その後は自分で `cd` して再実行します - `ncdu` → 一度スキャンすれば、あとは **画面の中を歩き回るだけ** で深掘りできます ::: ## 2. インストールと起動 {#install} > **結論**: `ncdu` は標準では入っていないことが多い。`apt` / `dnf` でインストールし、`ncdu パス` で起動する。 ::: dialogue @lina: 早速使いたいです。どうやって入れるんですか? @linny: パッケージ名はどのディストリビューションでも `ncdu` で共通だよ。まずはインストールからだね。 ::: ### インストール {#install-cmd} ```bash # Ubuntu / Debian 系 $ sudo apt install ncdu # RHEL / Fedora 系 $ sudo dnf install ncdu # 古い CentOS など $ sudo yum install ncdu ``` ### 起動してみる {#launch} ```bash # カレントディレクトリを調査 $ ncdu # パスを指定して調査 $ ncdu /var # ルート全体を調査(sudo 推奨) $ sudo ncdu / ``` ::: dialogue @lina: `ncdu /` を実行したら「Scanning...」と出て、しばらく待たされました。 @linny: それで問題ないよ。`ncdu` は起動時に **指定パス以下を一度スキャン** するんだ。 @linny: `du` と同じで、対象が大きいと時間がかかる。スキャンが終わると一覧画面に切り替わるよ。 ::: ::: warning **システム全体を調べるときは sudo** `/` の下には、一般ユーザーが読めないディレクトリがあります。`sudo` を付けないと、その部分が「Permission denied」で集計から外れます。その結果、数字が実際より小さく出ます。 システムを調べるときは `sudo ncdu /` が基本です。 ::: ## 3. 画面の見方と移動の基本 {#navigation} > **結論**: 上下矢印で項目を選び、Enter(右矢印)でディレクトリに入る。左矢印で親に戻る。`q` で終了。 ::: dialogue @linny: スキャンが終わると、こんな画面になるよ。サイズが **大きい順** に並ぶのがポイントだね。 ::: ```output --- /var ------------------------------------------------------ 1.2 GiB [##########] /log 512.0 MiB [#### ] /cache 24.0 MiB [ ] /lib 4.0 KiB [ ] /games ``` ::: tip **画面の読み方** - 左の数字:そのディレクトリやファイルの **使用量** です - `[#### ]`:親に対する **割合を棒で表したもの** です - 名前の前の `/`:ディレクトリである印です - 一番上の `--- /var ---`:いま見ている場所です ::: ### 基本のキー操作 {#keys} ::: highlight **移動系キー(まずこれだけ覚える)** | キー | 動作 | | ------------------ | ------------------------------- | | `↑` / `↓` | 項目を選択(上下に移動) | | `Enter` または `→` | 選択したディレクトリに **入る** | | `←` | 親ディレクトリに **戻る** | | `r` | いま見ている場所を **測り直す** | | `q` | `ncdu` を **終了** | | `?` | ヘルプ(キー一覧)を表示 | ::: ::: warning 画面内のキー `r` と、起動オプションの `-r` は別物です。キーの `r` は測り直しです。オプションの `-r` は削除機能を無効にします。 ::: ::: dialogue @lina: なるほど。一覧をカーソルで選んで、Enter で中に入るんですね。本当にファイルマネージャーのようです。 @linny: そうだね。`cd` も `du` も打たずに、矢印キーだけで奥まで潜れる。これが `ncdu` の便利なところだよ。 ::: ## 4. 並べ替えと表示の切り替え {#sort} > **結論**: `s` でサイズ順、`n` で名前順、`C` でファイル数順。`g` で割合・グラフの表示を切り替えられる。 ::: dialogue @lina: 並び順は変えられるんですか? @linny: 変えられるよ。既定はサイズの大きい順だね。目的に応じて切り替えられるよ。 ::: ::: highlight **ソート・表示系キー** | キー | 動作 | | ---- | ----------------------------------------------- | | `s` | **サイズ順** にソート(デフォルト) | | `n` | **名前順** にソート | | `C` | **ファイル数順** にソート(大文字 C) | | `g` | 割合 / グラフバーの表示を切り替え | | `a` | 見かけのサイズ(apparent size)と実使用量を切替 | ::: ::: tip **`a`(apparent size)の使いどころ** `ncdu` は既定で **実際にディスクを占有しているサイズ** を表示します。`a` を押すと「見かけ上のサイズ」の表示に切り替わります。 この 2 つは、中身がすかすかのファイルや、小さいファイルが大量にある場合にずれます。違いを知っておくと混乱しません。 ::: ## 5. その場で削除する {#delete} > **結論**: 削除したい項目を選んで `d` を押す。確認プロンプトが出るので、`du` で探して `rm` する往復が不要になる。 ::: dialogue @lina: 大きいディレクトリは見つかりました。消すときは一度 `ncdu` を抜けて `rm` するんですか? @linny: その必要はないよ。`ncdu` の中で消したい項目を選んで `d` を押すだけだね。 @linny: 確認が出てから消える。だから `du` で探して `rm` で消す往復が、まるごと不要になるんだ。 ::: ::: highlight **削除の手順** 1. `↑` / `↓` で消したいファイル / ディレクトリを選択します 2. `d` を押します 3. 確認画面が出ます。選択肢は `yes` / `no` / `don't ask me again` の 3 つです 4. `←` / `→` で選択肢を動かし、`Enter` で決定します。`q` を押せば中止できます ::: ::: danger **3 つ目の `don't ask me again` を選ばないでください** `don't ask me again` を選ぶと、それ以降の削除で確認画面が出なくなります。`d` を押した瞬間に消えるようになります。 初心者のうちは `yes` か `no` だけを使ってください。 ::: ::: danger **`d` は本物の削除。取り消せない** `ncdu` の `d` は `rm` と同じく **実体を削除** します。GUI と違って、ゴミ箱には入りません。すぐに元へは戻せません。 特に `sudo ncdu /` で起動している場合は注意が必要です。システムファイルを誤って消すと、**起動できなくなる** ことがあります。 安全に練習するための 3 つの型を覚えてください。 1. まず `-r` を付けて起動する(`ncdu -r ~`)。削除機能そのものが無効になります 2. 練習用のディレクトリを作り、その中だけで試す(例:`mkdir -p ~/ncdu-practice && cd ~/ncdu-practice`) 3. 消す前に、名前とサイズと更新日時を必ず確認する 自信がないディレクトリは消さないでください。 ::: ::: warning **消す前のチェックリスト** - そのファイルは本当に不要ですか(ログ / キャッシュ / 一時ファイルですか) - 起動中のサービスが使っていませんか - 大きいログは、消すより中身だけを空にする方が安全なこともあります(`truncate -s 0 ファイル名`) 2 つ目について補足します。サービスが開いたままのファイルを消すと、容量が戻りません。この「削除済み開きファイル」の問題は [du と df の違い](/articles/tutorials/du-df-difference) を参照してください。 ::: ## 6. 実務で効くオプション {#options} > **結論**: `-x` でマウント境界を跨がない、`-r` で読み取り専用、`-o` / `-f` でスキャン結果の保存・再利用ができる。 ::: dialogue @linny: 起動時のオプションをいくつか知っておくと、調査がもっと正確で安全になるよ。 ::: ::: highlight **よく使う起動オプション** | オプション | 意味 | | -------------------- | --------------------------------------------------------- | | `-x` | **同じファイルシステムだけ** 集計(別マウントを跨がない) | | `-r` | **読み取り専用** モードで起動(`d` による削除を無効化) | | `-o ファイル` | スキャン結果をファイルに **エクスポート** | | `-f ファイル` | エクスポート結果を **読み込んで** 表示 | | `--exclude パターン` | 特定のパスを集計から除外 | ::: ::: dialogue @lina: `-x` は `du` のときに出てきたものですよね。 @linny: よく覚えていたね。`du -x` と同じだよ。 @linny: たとえば `/home` が別パーティションのとき、**跨いで二重に数えない** ようにできる。`df` の数字と比べたいときに便利だね。 ::: ### 6-1. リナの失敗:`d` を押すつもりが違う行を消しかけた {#failure} ::: dialogue @lina: `/var` を調べていて、大きいキャッシュを消そうとしました。`d` を押したら、確認画面に出てきた名前が思っていたものと違っていて、ひやりとしました。 ::: ```output --- /var ------------------------------------------------------ 1.2 GiB [##########] /log 512.0 MiB [#### ] /cache 24.0 MiB [ ] /lib ``` ::: dialogue @lina: 消したかったのは `/cache` です。でも確認画面には `/log` と出ました。カーソルが 1 行上にあったんですね。 @linny: 気づけてよかった。`ncdu` の `d` は、いま選ばれている行に対して働くんだ。画面のどこを選んでいるかが、すべてを決めるよ。 @lina: 一覧は大きい順なので、上の行ほど中身が多いということですね。それを消しかけたと思うと、こわいです。 @linny: だから最初は `-r` を付けて起動するといい。`d` そのものが効かないから、練習にちょうどいいんだ。 @lina: 納得しました。確認画面の名前を声に出して読んでから `Enter` を押します。 ::: ```bash # 削除できない状態で練習する $ ncdu -r ~ ``` ::: tip **安全重視なら `-r`** うっかり `d` を押す事故を防ぎたいときは `sudo ncdu -r /` で起動します。`-r` を付けると削除機能が無効になります。 つまり **調査専用** で使えます。本番サーバーで「見るだけ」のときに有効です。 ::: ## 7. リモートサーバーの容量を調べる {#remote} > **結論**: リモートで取ったスキャン結果を手元の `ncdu` で開けば、サーバー上で対話操作せずに調査できる(リモート側にも `ncdu` が必要)。 ::: dialogue @lina: サーバーのディスクを調べたいです。でも、そのサーバーの画面で操作するのは大変です。 @linny: いい着眼点だね。`ncdu` には **スキャン結果を書き出したり読み込んだりする** 仕組みがあるんだ。 @linny: それを `ssh` と組み合わせると、手元の画面で調べられるようになるよ。 ::: ### パターン A:リモートでスキャンして手元で見る {#remote-a} ```bash # リモートで du 相当のスキャンだけ実行し、結果を手元の ncdu に流し込む $ ssh user@server ncdu -o- / | ncdu -f- ``` ::: tip **読み方** - `ncdu -o-`:スキャン結果を **標準出力** に書き出します(`-` は標準出力を表します) - `ncdu -f-`:**標準入力** から結果を読み込んで表示します - 間の `|`:リモートの出力を手元の `ncdu` に渡すパイプです 注意点が 1 つあります。`-o` を実行するには、リモート側にも `ncdu` が必要です。リモートに入っていない場合は、先にリモート側へインストールしてください。 ::: ### パターン B:結果をファイルに保存して持ち帰る {#remote-b} その場で流し込むのではなく、いったんファイルに保存する方法です。結果を残しておけば、あとから見返せます。 ```bash # サーバー側で結果を保存(圧縮すると軽い) $ ncdu -o- / | gzip > scan.gz # 手元に転送して読み込む $ scp user@server:scan.gz . $ zcat scan.gz | ncdu -f- ``` ::: dialogue @lina: スキャン結果をファイルにできるんですね。定期的に保存しておけば、あとから「いつ増えたか」も追えます。 @linny: その発想はいいね。書き出したファイルを日付順に残しておこう。容量が増えた原因の調査に、そのまま使えるよ。 ::: ## 8. ミニ課題:自分の環境で試してみよう {#exercise} > **結論**: ホーム配下の最大ディレクトリ特定・ソート切替・読み取り専用起動の3問で操作を定着させる。 ::: dialogue @lina: 知識は入りました。手を動かして試したいです。 @linny: いいね、3 問用意したよ。本番サーバーではなく、まず手元で試そう。どれも `-r` を付ければ削除は起こらないよ。 ::: **課題 1**: ホームディレクトリの直下で、最も大きいディレクトリを見つけよう。 :::details ヒント 1(方向づけ)を見る 調べたい場所を指定して起動します。一覧は既定でサイズの大きい順に並びます。 だから、一番上の行に注目すれば分かります。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `ncdu` です。ホームディレクトリは `~` と書けます。安全のため `-r` を付けます。 中に入るのは `Enter`、戻るのは左矢印、終了は `q` です。 ::: :::details 答えを見る ```bash $ ncdu -r ~ ``` ```output --- /home/user ------------------------------------------------- 2.5 GiB [##########] /Videos 512.0 MiB [## ] /Downloads 64.0 MiB [ ] /Documents ``` 一番上の行が最も大きいディレクトリです。この例では `/Videos` です。 `Enter` で中に入れば、さらに大きい中身を辿れます。左矢印で 1 つ上に戻れます。 ::: **課題 2**: 一覧の並び順を、サイズ順と名前順で切り替えてみよう。 :::details ヒント 1(方向づけ)を見る キーを 1 つ押すだけで切り替わります。コマンドを打ち直す必要はありません。 それぞれ、英語の頭文字がキーになっています。 ::: :::details ヒント 2(コマンド名)を見る サイズ順は `s`(size)です。名前順は `n`(name)です。一覧画面で交互に押してみます。 ::: :::details 答えを見る 一覧画面で `s` と `n` を交互に押します。並び順がその場で変わります。 同じキーをもう一度押すと、昇順と降順が入れかわります。 ::: **課題 3**: 削除キーが効かない状態で起動し、それを自分で確かめよう。 :::details ヒント 1(方向づけ)を見る 起動時のオプションで、削除機能そのものを無効にできます。読み取り専用にする、という考え方です。 英語の read-only の頭文字が手がかりです。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `ncdu -r` です。調べる場所には `/tmp` を指定します。起動したら `d` を押して反応を見ます。 ::: :::details 答えを見る ```bash $ ncdu -r /tmp ``` `-r` を付けて起動すると、`d` キーが無効になります。削除の確認画面は出ません。 代わりに画面の下へ `File deletion disabled in read-only mode.`(削除機能は読み取り専用モードで無効です)と表示されます。これが `-r` が効いている証拠です。 慣れるまでは、この形で練習してください。 ::: ## 9. よくある落とし穴 {#pitfalls} > **結論**: sudo 忘れで数字が小さく出る、`d` の押し間違いで誤削除、ホームではなく `/` を消そうとする事故に注意。 ::: warning **やりがちな失敗 3 つ** 1. **`sudo` を付けずに `/` を調査する** → 読めないディレクトリが外れ、数字が実際より小さく出ます 2. **大きいものを見つけてすぐ `d` を押す** → サービスが使用中だと効果がありません。先に正体を確認します 3. **`-r` を付けずに本番で操作する** → うっかり `d` を押して誤削除します。見るだけなら必ず `-r` を付けます ::: ::: tip **安全な型** - システム調査は `sudo ncdu -x /` です(同じファイルシステムだけを集計します) - 本番サーバーは原則 `-r`(読み取り専用)で起動します - 消す前に名前・サイズ・更新日時を確認します。迷ったら消しません ::: ## 10. 振り返り {#review} ::: dialogue @lina: 整理します。`ncdu` は一度スキャンすれば、あとは矢印キーだけで深掘りできるんですね。 @linny: そのとおり。`Enter` で中に入り、左矢印で戻る。この 2 つで十分だよ。 @lina: `d` は選んでいる行に効く、と覚えました。あやうく `/log` を消すところでした。 @linny: よく覚えていたね。慣れるまでは `-r` を付けて起動する。それだけで事故はほとんど防げるよ。 ::: ## 今日の 3 行まとめ {#three-lines} 1. `ncdu` は `du` の結果を対話的な画面で見るツール。矢印キーだけで深掘りできる 2. `s` でサイズ順、`n` で名前順に並べかえられる。`d` は選んでいる行を削除する 3. 練習と本番の調査では `-r`(読み取り専用)を付ける。システム調査では `sudo` と `-x` も付ける ## 次に読む {#next} - [du と df の違い - ディスク容量を正しく測る](/articles/tutorials/du-df-difference) - [ディスクがいっぱい(No space left on device)](/articles/troubleshooting/no-space-left-on-device) - [ディスク管理の基本](/articles/tutorials/disk-management-basics) # nc (netcat) 入門 - ポート疎通とデバッグの万能ツール Source: https://penguin-gym-linux.com/articles/tutorials/netcat-basics ## この記事で解決できること {#intro} - `nc -zv` でポートが開いているか **1 コマンドで確認** できる - `-l` フラグで **簡易サーバを即座に起動** し、通信テストができる - ファイル転送・バナー取得・UDP 疎通確認など **nc の主要な活用パターン** が身につく - `nc.openbsd` / `ncat` の違いと、`-z` が使えない場合の対処法がわかる ::: tip **結論(実務の型)** - **ポート疎通確認** → `nc -zv host port` - **簡易サーバ** → `nc -l 8080`(受信側) - **ファイル転送** → 受信側 `nc -l 9999 > out.txt`、送信側 `nc host 9999 < in.txt` - タイムアウトは `-w 秒数` で指定する ::: ## nc とは? {#about} nc(netcat)は TCP / UDP のソケットに対して直接 read/write するコマンドライン汎用ツールで「ネットワークの Swiss Army knife」とも呼ばれる。curl は HTTP、ping は ICMP と用途が固定されているのに対し、nc は任意のポートと任意のプロトコルで通信できる点が最大の特長。サービスが起動していない状態でも nc だけで受信側を立ててテストできるため、ネットワーク疎通・アプリ起動前の検証・CI 環境の接続テストに幅広く使われる。 ## ポートが開いているか確認するには? {#port-check} `-z`(zero-I/O モード:データを送らず接続のみ確認)と `-v`(詳細表示)を組み合わせて使う。 ```bash $ nc -zv example.com 80 Connection to example.com 80 port [tcp/http] succeeded! $ nc -zv example.com 22 Connection to example.com 22 port [tcp/ssh] succeeded! ``` ポートが閉じている・到達不可の場合: ```bash $ nc -zv example.com 25 nc: connectx to example.com port 25 (tcp) failed: Connection refused ``` ポート範囲をまとめてスキャンする: ```bash $ nc -zv example.com 20-25 ``` ::: tip `-w` でタイムアウト(秒)を設定するとハングを防げる。 ```bash $ nc -zv -w 3 192.168.1.10 443 ``` ::: ## 簡易サーバ(listen モード)を起動するには? {#listen} `-l` フラグで指定ポートを待ち受ける。接続が来ると標準入出力をソケットに接続する。 ```bash # 受信側(先に起動) $ nc -l 8080 # 送信側(別ターミナル) $ nc localhost 8080 Hello from client! ``` 送信側で入力した内容がリアルタイムに受信側へ流れる。双方向通信になるため、簡易チャットとして使うことも可能。 ::: warning nc はデータを平文で送受信する。認証・暗号化はない。本番環境での永続サーバとして使うことは想定されていない。 ::: ## ファイル転送はできるか? {#file-transfer} リダイレクトを使って受信側 → 送信側の順で起動するだけで転送できる。SCP / rsync が使えない環境での一時的なデータ渡しに有効。 ```bash # 受信側(先に起動) $ nc -l 9999 > received.tar.gz # 送信側 $ nc receiver-host 9999 < archive.tar.gz ``` 転送が終わったら送信側の nc が EOF を送り終了する。受信側が終了しない場合は Ctrl+C で止める。 ::: tip OpenBSD 版は `-q 0`(EOF 受信後すぐに終了)オプションで受信側も自動終了させられる。 ```bash $ nc -l 9999 -q 0 > received.tar.gz ``` ::: ## HTTP / SMTP バナーを確認するには? {#banner} nc でサービスへ接続し、プロトコル仕様に沿ったリクエストを手動送信することで、サービスの応答(バナー)を確認できる。 ### HTTP ヘッダーを確認する ```bash $ printf "HEAD / HTTP/1.0\r\n\r\n" | nc example.com 80 HTTP/1.1 200 OK Server: nginx/1.24.0 ... ``` ### SMTP バナーを確認する ```bash $ nc mail.example.com 25 220 mail.example.com ESMTP Postfix (Ubuntu) ``` バナーに含まれるバージョン情報やサーバ種別を確認することで、ミドルウェアのデプロイ確認やセキュリティ評価ができる。 ## UDP モードを使うには? {#udp} `-u` フラグを付けると UDP ソケットを使う。DNS(53)・NTP(123)・syslog(514)など UDP サービスの疎通確認に使う。 ```bash # UDP ポート疎通確認 $ nc -u -zv 8.8.8.8 53 Connection to 8.8.8.8 53 port [udp/domain] succeeded! # UDP 簡易リッスン $ nc -u -l 5005 ``` ::: warning UDP はコネクションレスのため `-z` での疎通確認が不正確になる場合がある。相手がパケットを受け取っても応答しなければ失敗と判定される。UDP 疎通の正確な確認は専用ツール(nmap -sU 等)を検討する。 ::: ## nc のバリアントはどう違う? {#variants} Linux の nc コマンドには複数の実装が存在し、オプションの互換性が異なる。 | コマンド | パッケージ | 特徴 | | --------------------- | -------------------- | ------------------------------------------- | | `nc` (OpenBSD 版) | `netcat-openbsd` | Ubuntu/Debian 標準。`-z` `-w` `-q` など豊富 | | `nc` (Traditional 版) | `netcat-traditional` | 旧実装。`-z` 非対応、`-e` でシェル実行可 | | `ncat` | `ncat`(nmap 同梱) | SSL/TLS 対応、ブローカーモードあり | 現在のバリアントを確認する: ```bash $ nc -h 2>&1 | head -1 OpenBSD netcat (Debian patchlevel 1.226-1) ``` OpenBSD 版に切り替える(Ubuntu): ```bash $ sudo apt install netcat-openbsd $ sudo update-alternatives --config nc ``` ## よくある事故パターン {#pitfalls} ### 1. `nc: invalid option -- 'z'` `netcat-traditional` が入っている場合は `-z` が使えない。`netcat-openbsd` をインストールして切り替える(前述の「バリアント」参照)。 ### 2. ポートが開いているのに「Connection refused」 アプリが LISTEN していないか、ファイアウォールでブロックされている。 ```bash # LISTEN 状態を確認 $ ss -tlnp | grep ':8080' # UFW 状態確認 $ sudo ufw status ``` ### 3. ファイル転送が終了しない 受信側の nc が EOF を認識せずハングする場合は Ctrl+C で止め、送信側が先に終了していることを確認する。OpenBSD 版では `-q 0` を付けると EOF で自動終了する。 ### 4. `-l` で再利用できない(Address already in use) ポートが TIME_WAIT 状態にある可能性がある。 ```bash # ポート使用状況を確認 $ ss -tlnp | grep ':8080' ``` 別ポートを使うか、少し待ってから再試行する。 ## 次に読む {#next} - [ポートと接続状態の確認(netstat/ss)](/articles/tutorials/netstat-ss-basics) - [ネットワークコマンド入門](/articles/tutorials/network-commands-basics) - [ネットワーク障害対応の実践](/articles/tutorials/network-troubleshooting-practical) # netstat/ss入門 - ポートと接続状態の確認方法 Source: https://penguin-gym-linux.com/articles/tutorials/netstat-ss-basics ## この記事で解決できること {#intro} - `netstat` と `ss` の **使い分けと違い** が分かる - 「どのポートで待ち受けているか」「誰が掴んでいるか」を **即確認** できる - 接続状態(`LISTEN` / `ESTAB` / `TIME-WAIT` 等)を **読み解く型** が身につく ::: tip **結論(実務の型)** - 待ち受け確認は `ss -tlnp`(`netstat -tlnp` の後継) - 確立済み接続は `ss -tnp`、全体サマリは `ss -s` - `netstat` が `command not found` でも壊れていない。`ss` を使えばよい ::: ::: warning **前提(対象環境)** - OS:Ubuntu / RHEL 系など一般的な Linux - `iproute2`(`ss` コマンド)は標準インストール済み - プロセス名表示(`-p`)には `sudo` または管理権限が必要 ::: ## netstat と ss は何が違うのか? {#netstat-vs-ss} `netstat` は旧 net-tools パッケージ付属の古いコマンドで現在は非推奨。`ss`(socket statistics)は後継の iproute2 が提供し、同じ情報をより高速に取得できる。新しい環境では `ss` を使う。 | 項目 | `netstat`(net-tools) | `ss`(iproute2) | | ---------------- | -------------------------- | -------------------- | | ステータス | 非推奨・保守停止 | 現行・推奨 | | デフォルト導入 | 多くのモダン環境で未導入 | 標準導入 | | 大量接続時の速度 | 遅い(`/proc` を逐次走査) | 高速(カーネル API) | | 状態フィルタ | 不可 | `state` で絞り込み可 | ::: tip `netstat` が `command not found` になるのは故障ではなく net-tools が未導入なだけ。`sudo apt install net-tools` で入るが、まず `ss` を使うのが正攻法。 ::: ## 待ち受けポートはどう確認するのか? {#listen} `ss -tlnp` で「どのポートで何のプロセスが待ち受けているか」を一覧表示する。サービスを起動したのに繋がらない時、まずここを見れば待受の有無が一発で分かる。 ```bash $ sudo ss -tlnp ``` ```output State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=812,fd=3)) LISTEN 0 511 127.0.0.1:3000 0.0.0.0:* users:(("node",pid=1340,fd=18)) ``` 読みどころ: - `LISTEN` … そのポートで待ち受け中 - `0.0.0.0:22` … 全インターフェースで 22 番を待受(外部から到達可能) - `127.0.0.1:3000` … ループバックのみ。**外部からは繋がらない**(後述) - `Process` … 掴んでいるプロセス名と PID(`-p` + `sudo` で表示) UDP も含めて見るなら `-u` を足す。 ```bash $ sudo ss -tulnp ``` ## オプション -tulpn は何を意味するのか? {#options} `ss` の主要オプションは1文字ずつ独立した意味を持つ。`-tulpn` は「TCP と UDP の待受を、名前解決せず、プロセス付きで表示」を1語にまとめた定番の組み合わせ。 | オプション | 意味 | | ---------- | ------------------------------------- | | `-t` | TCP ソケット | | `-u` | UDP ソケット | | `-l` | LISTEN(待ち受け)状態のみ | | `-n` | 名前解決しない(数値表示・高速) | | `-p` | 掴んでいるプロセスを表示(要 `sudo`) | | `-a` | 全状態(LISTEN + 確立済み等) | ::: tip 順番は任意で `ss -tlnp` と `ss -plnt` は同じ。`-n` を付けないと DNS 逆引きで遅くなるため、調査時は基本付ける。 ::: ## 確立済み接続はどう見るのか? {#established} `ss -tnp` で現在 `ESTAB`(確立済み)の TCP 接続を一覧表示する。「誰がこのサーバに繋いでいるか」「アプリが外部 API へ接続できているか」を確認する時に使う。 ```bash $ sudo ss -tnp ``` ```output State Recv-Q Send-Q Local Address:Port Peer Address:Port Process ESTAB 0 0 192.168.1.20:22 203.0.113.5:51324 users:(("sshd",pid=2210,fd=4)) ESTAB 0 0 192.168.1.20:48210 10.0.0.8:5432 users:(("node",pid=1340,fd=22)) ``` - `Local Address:Port` … 自サーバ側の接続元 - `Peer Address:Port` … 接続先(相手) - 1行目 … `203.0.113.5` から SSH(22)でログインされている - 2行目 … node アプリが PostgreSQL(5432)へ接続中 接続数だけ素早く把握したいなら `ss -s`(summary)が便利。 ```bash $ ss -s ``` ## 特定ポート/プロセスはどう絞り込むのか? {#filter} `ss` はフィルタ式を直接書ける。`grep` で探す前に、`sport`(送信元ポート)/ `dport`(宛先ポート)/ `state` でカーネル側に絞らせると速く正確。 特定ポートで待ち受けているか: ```bash $ sudo ss -tlnp 'sport = :80' $ sudo ss -tlnp sport = :443 ``` 特定状態だけ抽出: ```bash $ ss -tn state established $ ss -tn state time-wait ``` 特定の宛先への接続を見る: ```bash $ ss -tn dst 10.0.0.8 ``` ::: warning `127.0.0.1:3000` でしか待っていないアプリは、サーバ内部からしか到達できない。外部公開したいなら `0.0.0.0:3000`(または特定 IP)で待つようアプリ側の bind 設定を変更する。`ss` の `Local Address` が `127.0.0.1` のままなら、いくらファイアウォールを開けても繋がらない。 ::: ## 接続状態(State)はどう読むのか? {#states} TCP の状態は接続のライフサイクルを表す。トラブル時に頻出するのは `LISTEN` / `ESTAB` / `TIME-WAIT` / `CLOSE-WAIT` の4つ。意味を押さえると原因の当たりが付く。 | State | 意味 | 読みどころ | | ------------ | --------------------------- | ---------------------------------------------- | | `LISTEN` | 待ち受け中 | サービスが上がっている | | `ESTAB` | 接続確立済み | 正常に通信中 | | `TIME-WAIT` | 切断後のクールダウン | 大量にあっても通常は正常(短命接続が多い) | | `CLOSE-WAIT` | 相手は閉じたが自分が未close | 大量増加はアプリの**ソケットクローズ漏れ**疑い | | `SYN-SENT` | 接続要求を送って応答待ち | 大量なら相手不達・FW 遮断を疑う | ::: tip `CLOSE-WAIT` が増え続けるのはアプリのバグ(ソケットを close していない)のサイン。`ss -tn state close-wait` で件数とプロセスを確認する。`TIME-WAIT` の大量発生は多くの場合は正常で、慌てて `net.ipv4.tcp_tw_reuse` 等をいじらない。 ::: ## netstat → ss 移行チートシート {#cheatsheet} `netstat` の手癖が残っていても、`ss` の同等コマンドを覚えれば移行できる。下表を手元に置けば困らない。 | やりたいこと | 旧(netstat) | 新(ss) | | -------------- | ---------------- | ----------- | | TCP 待受一覧 | `netstat -tlnp` | `ss -tlnp` | | TCP/UDP 待受 | `netstat -tulnp` | `ss -tulnp` | | 全接続表示 | `netstat -an` | `ss -an` | | 確立済み接続 | `netstat -tnp` | `ss -tnp` | | 接続数サマリ | `netstat -s` | `ss -s` | | ルーティング表 | `netstat -rn` | `ip route` | ```bash # コピペ用:状態把握ワンライナー sudo ss -tulnp && ss -s ``` ::: tip `ss -tlnp | grep ':80 '` のような grep 併用も有効だが、`ss -tlnp 'sport = :80'` の方がカーネル側で絞れて速く誤検出も少ない。 ::: ## 次に読む {#next} - [ネットワークコマンド入門(ip/ifconfig)](/articles/tutorials/network-commands-basics) - [ポート疎通の確認方法(ss/lsof/nc/curl)](/articles/troubleshooting/port-connectivity) - [SSH で接続できないときの切り分け](/articles/troubleshooting/ssh-troubleshooting) - [systemctl でサービスを確認する](/articles/tutorials/systemctl-basics) # ネットワークコマンド入門 - ip/ifconfigで接続状態を確認する Source: https://penguin-gym-linux.com/articles/tutorials/network-commands-basics ## この記事で解決できること {#intro} - `ip` と `ifconfig` の **使い分けと違い** が分かる - 「ネットにつながらない」を **どこで切れているか順に切り分け** できる - IPアドレス・リンク・経路・到達性を **確認する型** が身につく **用語の整理**(この記事で使う言葉を先に定義します) - **インターフェース**: ネットワークの出入口となる装置。`eth0` や `enp0s3` のような名前が付きます。「NIC」「ネットワークアダプタ」も同じものを指します - **リンク**: ケーブルや無線がつながっているか、という物理レベルの状態です。「物理層」「L1」とも呼びます - **経路(ルーティング)**: データをどこ宛てに送り出すかの道順です。「ルート」「経路表」「ルーティングテーブル」も同じ意味で使われます - **デフォルトゲートウェイ**: 自分のネットワークの外へ出るときの出口となる機器です。多くの環境ではルータがこれにあたります - **到達性**: 相手までデータが実際に届くかどうかです。英語では reachability と書きます - **名前解決(DNS)**: `example.com` のような名前を IP アドレスに変換する仕組みです - **iproute2 / net-tools**: `ip` コマンドを提供するパッケージが iproute2 です。`ifconfig` を提供する古いパッケージが net-tools です ::: tip **結論(実務の型)** - IPアドレスを見るなら `ip a`(`ifconfig` の後継) - 切り分けは下から上:**リンク → IP → 経路 → 到達性 → 名前解決** の順 - `ifconfig` が「command not found」でも壊れていない。`ip` を使えばよい ::: ::: warning **前提(対象環境)** - OS:Ubuntu / RHEL 系など一般的な Linux - `iproute2`(`ip` コマンド)は標準インストール済み - 一部コマンドは `sudo` または管理権限が必要 ::: ## ip と ifconfig は何が違うのか? {#ip-vs-ifconfig} `ifconfig` は旧 net-tools パッケージに付属する古いコマンドです。現在は非推奨で、新しい環境には最初から入っていないことも多いコマンドです。後継が iproute2 パッケージの `ip` コマンドです。`ip` は IPアドレス・リンク・経路をすべて 1 つのコマンドで扱えます。新しい環境では `ip` を使ってください。 | 項目 | `ifconfig`(net-tools) | `ip`(iproute2) | | -------------- | ------------------------ | -------------------- | | ステータス | 非推奨・保守停止 | 現行・推奨 | | デフォルト導入 | 多くのモダン環境で未導入 | 標準導入 | | 扱える範囲 | IP・リンクのみ | IP・リンク・経路・他 | | 複数IP/サブIF | 表示が不完全 | 正確に表示 | ::: tip `ifconfig` が `command not found` になるのは故障ではありません。net-tools が入っていないだけです。`sudo apt install net-tools` で導入できますが、まず `ip` を使うのが正攻法です。 ::: ## IPアドレスはどう確認するのか? {#ip-addr} `ip a` で全インターフェースのIPアドレスを確認します。`ip a` は `ip addr show` の短縮形です。`ifconfig` に慣れているなら `ifconfig` 単体でも同じ情報が見られます。まずはここで自分のIPを把握してください。 ```bash $ ip a ``` ```output 2: eth0: mtu 1500 ... inet 192.168.1.20/24 brd 192.168.1.255 scope global eth0 ``` 読みどころ: - `eth0` … インターフェース名(環境により `enp0s3` 等) - `inet 192.168.1.20/24` … 割り当てられた IPv4 アドレスです。末尾の `/24` はサブネットの広さを表し、この場合は `192.168.1.0`〜`192.168.1.255` が同じネットワークになります - `UP` … OS 側でインターフェースが有効になっています - `LOWER_UP` … 物理的なリンクがつながっています 特定インターフェースだけ見るなら次のとおり。 ```bash $ ip addr show eth0 ``` `ifconfig` での同等操作: ```bash $ ifconfig $ ifconfig eth0 ``` ## リンク状態はどう見るのか? {#ip-link} `ip link` でリンクの状態を確認します。ここで見るのはケーブル接続と、インターフェースが有効か無効かの 2 点です。IPアドレスが付いていても `state DOWN` なら通信できません。「つながらない」ときはまずここを見てください。 ```bash $ ip link show eth0 ``` ```output 2: eth0: mtu 1500 state UP ... ``` - `state UP` … リンク有効。`state DOWN` なら無効 - `LOWER_UP` が無い … ケーブル未接続・対向機器側の問題を疑う インターフェースを手動で有効/無効化する(要管理権限): ```bash $ sudo ip link set eth0 up $ sudo ip link set eth0 down ``` ::: danger **`ip link set ... down` は自分の接続を切る操作です** リモート(SSH)接続中に、作業に使っているインターフェースを `down` にしないでください。その場で接続が切れます。SSH でしか入れない機器では、自分の手で復旧できなくなります。 **安全なやり方** - 作業前に `ip route get <接続元のIP>` を実行し、SSH が通っているインターフェースを特定する。そのインターフェースには触らない - どうしても操作が必要なら、SSH セッションから独立した復旧予約を先に入れる。ここで注意したいのは、リンクを `up` に戻すだけでは復旧しない場合があることです。`down` にした時点でそのインターフェース経由の経路(デフォルトルート含む)が削除され、`up` では自動的に戻らないためです。ネットワーク設定を再適用する形で予約してください - NetworkManager: `sudo systemd-run --on-active=60 /bin/sh -c 'nmcli networking off; nmcli networking on'` - systemd-networkd: `sudo systemd-run --on-active=60 /bin/sh -c 'systemctl restart systemd-networkd'` - netplan: `sudo systemd-run --on-active=60 /bin/sh -c 'netplan apply'` - systemd が無い環境: `echo 'netplan apply' | at now + 1 minute` などで代替する - コンソール(IPMI / クラウドのシリアルコンソール等)に入れることを先に確認する ::: ## 経路(ルーティング)はどう確認するのか? {#ip-route} `ip route` でデフォルトゲートウェイと経路表を確認します。IPアドレスが正しくても、ゲートウェイが無ければ外部には出られません。「LAN 内は通るのに外部に出られない」ときの定番チェックポイントです。 ```bash $ ip route ``` ```output default via 192.168.1.1 dev eth0 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.20 ``` - `default via 192.168.1.1` … デフォルトゲートウェイ。**これが無い=外部に出られない** - 2行目 … 同一サブネット宛は直接配送 特定宛先への経路を引くなら `ip route get` が便利。 ```bash $ ip route get 8.8.8.8 ``` ## 到達性はどう確認するのか? {#ping} `ping` で相手まで実際にデータが届くかを確認します。`-c` は送る回数の指定です。付けないと止まらずに送り続けます。ゲートウェイ → 外部IP → ドメイン名の順に試してください。この順番で試すと、どこで切れているかが分かります。 ```bash $ ping -c 4 192.168.1.1 $ ping -c 4 8.8.8.8 $ ping -c 4 example.com ``` 切り分けの読み方: - ゲートウェイにも届かない … リンクか経路の問題(`ip link` / `ip route` へ戻る) - `8.8.8.8` は OK だが `example.com` は NG … **名前解決(DNS)の問題** - すべて NG … 経路・ファイアウォール・ISP 側を疑う ::: tip `ping` が `100% packet loss` でも、ネットワークが壊れているとは限りません。相手が ICMP(`ping` が使う通信の種類)を遮断しているだけのこともあります。Web サーバが相手なら `curl -I https://example.com` も実行してください。HTTP レベルで届いているかを併せて確認できます。 ::: ## つながらない時はどう切り分けるのか? {#flow} 下の層から順に潰すのが鉄則です。リンク → IP → 経路 → 到達性 → 名前解決の順に確認してください。最初に失敗した層が原因です。上の層から見ると遠回りになります。 1. **リンク**:`ip link show` → `state UP` か 2. **IP**:`ip a` → IPアドレスが付与されているか 3. **経路**:`ip route` → `default via ...` があるか 4. **到達性**:`ping -c 4 <ゲートウェイ>` → `ping -c 4 8.8.8.8` 5. **名前解決**:`ping -c 4 example.com` が NG なら DNS を疑う ```bash # 上から順に1コマンドずつ ip link show ip a ip route ping -c 4 8.8.8.8 ping -c 4 example.com ``` ::: warning **やりがちな失敗** - いきなりドメイン名で `ping` して「ネットが死んだ」と誤判断(実は DNS だけ) - IPが付いているのを見て安心し、ゲートウェイ未設定を見落とす - SSH 越しに `ip link set ... down` を実行して自分の接続を切る ::: ## 旧コマンドとの対応表(チートシート) {#cheatsheet} `ifconfig` / `route` / `netstat` に慣れている場合も、`ip` / `ss` での同等操作を覚えれば移行できます。下の表を手元に置いてください。`ss` は `netstat` の後継で、同じく iproute2 が提供します。 | やりたいこと | 旧(net-tools) | 新(iproute2) | | -------------- | ------------------ | --------------------- | | IPアドレス確認 | `ifconfig` | `ip a` | | リンク有効化 | `ifconfig eth0 up` | `ip link set eth0 up` | | 経路表表示 | `route -n` | `ip route` | | ARP テーブル | `arp -n` | `ip neigh` | | 待ち受けポート | `netstat -tlnp` | `ss -tlnp` | ```bash # コピペ用:状態把握ワンライナー ip -br a && ip route && ss -tlnp ``` ::: tip `ip -br a`(`-br` = brief)はインターフェースとIPを1行ずつ簡潔表示。一覧把握に最適。 ::: ## 次に読む {#next} - [DNS が引けないときの対処](/articles/troubleshooting/dns-troubleshooting) - [ポート疎通の確認方法](/articles/troubleshooting/port-connectivity) - [SSH で接続できないときの切り分け](/articles/troubleshooting/ssh-troubleshooting) - [scp / rsync でファイルを転送する](/articles/tutorials/scp-rsync-basics) # network 障害対応の実践 - ping/traceroute/digを使った切り分け Source: https://penguin-gym-linux.com/articles/tutorials/network-troubleshooting-practical ## この記事で解決できること {#intro} 「サイトにつながらない」「特定のサーバーにだけ到達できない」——ネットワーク障害は原因の切り分けが9割だ。`ping` / `traceroute` / `dig` の3コマンドで、**到達性 → 経路 → DNS** の順に確認すれば、障害箇所を確実に特定できる。 ::: tip **切り分けの型(実務で使う3ステップ)** 1. `ping` で到達性を確認する 2. `traceroute` でどこで詰まっているかを確認する 3. `dig` で DNS 解決を確認する ::: ::: warning **前提(対象環境)** - OS:Ubuntu / RHEL 系 Linux - CLI 操作が可能な環境 ::: ## 1. ping — 到達性を確認するには? {#ping} `ping` はパケットが相手ホストまで届くかを確認する最初の手段だ。IP アドレスで疎通を確認することで、DNS の問題と経路の問題を分離できる。 ```bash $ ping -c 4 8.8.8.8 ``` ```output PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=118 time=8.45 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=118 time=7.92 ms 64 bytes from 8.8.8.8: icmp_seq=3 ttl=118 time=8.21 ms 64 bytes from 8.8.8.8: icmp_seq=4 ttl=118 time=8.10 ms --- 8.8.8.8 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3004ms rtt min/avg/max/mdev = 7.920/8.170/8.450/0.196 ms ``` ### ping の結果の読み方 {#ping-output} | 結果 | 意味 | | -------------------------- | ---------------------------------------------------- | | 応答あり(0% packet loss) | 物理接続・ルーティングは正常 | | 100% packet loss | ルーティング問題またはファイアウォールによるブロック | | Request timeout | 相手が ICMP を拒否、または経路に問題あり | ### 外部と内部を分けて確認する {#ping-steps} ```bash # ステップ1: ローカルゲートウェイに疎通できるか $ ping -c 4 192.168.1.1 # ステップ2: ISP の DNS サーバーに疎通できるか(インターネット接続確認) $ ping -c 4 8.8.8.8 # ステップ3: ホスト名で疎通できるか(DNS 解決も含めた確認) $ ping -c 4 google.com ``` ステップ2は通るがステップ3が失敗する場合は DNS の問題だ。 ::: tip `-c 4` で送信回数を指定する。無指定だと Ctrl+C まで無限に送信し続けるため、スクリプトや確認作業では必ず `-c` を付ける。 ::: ## 2. traceroute — 経路のどこで止まっているか? {#traceroute} 到達できない場合、`traceroute` でどのルーター(ホップ)で詰まっているかを特定する。 ```bash $ traceroute 8.8.8.8 ``` ```output traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets 1 192.168.1.1 (192.168.1.1) 1.234 ms 1.145 ms 1.089 ms 2 10.0.0.1 (10.0.0.1) 8.456 ms 8.512 ms 8.398 ms 3 * * * 4 203.0.113.1 (203.0.113.1) 15.234 ms 15.198 ms 15.321 ms 5 8.8.8.8 (8.8.8.8) 22.567 ms 22.489 ms 22.601 ms ``` ### `* * *` はどういう意味か? {#asterisk} `* * *` はそのルーターが ICMP の Time Exceeded を返さないことを示す。**必ずしも障害ではない**。問題なく先のホップに到達できていれば、そのルーターが単に ICMP を拒否しているだけだ。 途中のホップから `* * *` が続いて先に進まない場合は、そのルーター以降でパケットが落とされている可能性が高い。 ### traceroute がない場合 {#install} Ubuntu では標準未搭載の場合がある。 ```bash $ sudo apt install traceroute ``` `mtr` は経路を継続的に監視でき、各ホップのパケットロス率もリアルタイムで表示するため実務でよく使われる。 ```bash $ sudo apt install mtr $ mtr 8.8.8.8 ``` ## 3. dig — DNS 解決を確認するには? {#dig} IP で疎通できるがホスト名で届かない場合は DNS の問題だ。`dig` で名前解決の詳細を確認する。 ### 基本的な使い方 {#dig-basic} ```bash # A レコード(IPv4 アドレス)を確認する $ dig google.com A # 結果だけを簡略表示する $ dig +short google.com ``` ```output 142.250.196.46 ``` ```bash # 特定の DNS サーバーに直接問い合わせる(DNS サーバー自体の問題を切り分ける) $ dig @8.8.8.8 google.com A ``` ### dig の出力の読み方 {#dig-output} ```bash $ dig google.com A ``` ```output ; <<>> DiG 9.18.18 <<>> google.com A ;; QUESTION SECTION: ;google.com. IN A ;; ANSWER SECTION: google.com. 83 IN A 142.250.196.46 ;; Query time: 12 msec ;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP) ``` - `ANSWER SECTION` に値があれば DNS 解決は成功 - `NXDOMAIN` が返ってきた場合、そのドメインは存在しない(またはスペルミス) - `SERVFAIL` は DNS サーバーが応答を返せなかった状態を示す ### DNS レコードの種類と確認方法 {#dns-records} | レコード | 用途 | コマンド例 | | -------- | -------------- | ----------------------- | | A | IPv4 アドレス | `dig example.com A` | | AAAA | IPv6 アドレス | `dig example.com AAAA` | | MX | メールサーバー | `dig example.com MX` | | CNAME | 別名 | `dig example.com CNAME` | | NS | ネームサーバー | `dig example.com NS` | | PTR | 逆引き | `dig -x 8.8.8.8` | ## 4. 切り分けフロー — 実務で使うパターン {#flow} 障害切り分けは「どの層で起きているか」を絞り込む作業だ。以下の順序で確認する。 **ステップ1: IP アドレスで ping が通るか** ```bash $ ping -c 4 8.8.8.8 ``` 通らない場合は L3 経路の問題。traceroute で詰まっている箇所を探す。 **ステップ2: ホスト名で ping が通るか** ```bash $ ping -c 4 google.com ``` IP は通るがホスト名が通らない場合は DNS の問題。`dig` で確認する。 **ステップ3: dig で名前解決できるか** ```bash $ dig @8.8.8.8 google.com ``` 設定中の DNS サーバーでは解決できないが `8.8.8.8` では解決できる場合、`/etc/resolv.conf` の DNS 設定に問題がある。 ### よくある障害パターンと対処 {#patterns} **パターン1: ゲートウェイには疎通できるが外に出られない** ```bash $ ping -c 4 192.168.1.1 # OK $ ping -c 4 8.8.8.8 # NG ``` ルーター設定または ISP の問題。`traceroute` でどこまで届くか確認する。 **パターン2: IP では到達できるがホスト名で解決できない** ```bash $ ping -c 4 8.8.8.8 # OK $ ping -c 4 google.com # NG ``` DNS の問題。まず `/etc/resolv.conf` で設定を確認し、別の DNS サーバーで試す。 ```bash $ cat /etc/resolv.conf $ dig @8.8.8.8 google.com $ dig @1.1.1.1 google.com ``` **パターン3: 特定のポートのみつながらない** ```bash $ ping -c 4 example.com # OK(ICMP は通る) $ curl -v https://example.com # NG(443 番ポートが通らない) ``` ファイアウォールの問題。`ufw` / `firewalld` / `iptables` を確認する。 ```bash $ sudo ufw status ``` ::: warning `ping` が通っても HTTP/HTTPS が通らない場合がある。ICMP とアプリケーション層のプロトコルは別物。ポートレベルの疎通は `nc`(netcat)や `curl -v` で確認する。 ::: ## 5. systemd-resolved が干渉する場合 {#resolved} Ubuntu 18.04 以降では `systemd-resolved` が DNS クエリを仲介する。`dig` で問い合わせ先が `127.0.0.53` の場合は systemd-resolved 経由だ。 ```bash # 現在の DNS 設定を確認する $ resolvectl status # 外部 DNS に直接問い合わせて比較する $ dig @8.8.8.8 google.com ``` 外部 DNS では解決できて `127.0.0.53` では解決できない場合、systemd-resolved の設定または `/etc/resolv.conf` のシンボリックリンクに問題がある。 ```bash # /etc/resolv.conf の状態を確認する $ ls -la /etc/resolv.conf ``` `/etc/resolv.conf` が `stub-resolv.conf`(systemd-resolved が管理)へのシンボリックリンクになっていることが正常な状態だ。 ## 次に読む {#next} - [ネットワークコマンド入門 - ip/ifconfigで接続状態を確認する](/articles/tutorials/network-commands-basics) - [netstat/ss入門 - ポートと接続状態の確認方法](/articles/tutorials/netstat-ss-basics) - [ファイアウォール設定入門 - ufwとfirewalldの基礎](/articles/tutorials/firewall-basics) # nice/renice 入門 - プロセス優先度を制御する Source: https://penguin-gym-linux.com/articles/tutorials/nice-renice-basics ## nice/renice とは何か? {#overview} `nice` と `renice` は Linux でプロセスの **CPU スケジューリング優先度** を制御するコマンド。優先度が高いプロセスほど CPU 時間を多く割り当てられる。 ::: tip **結論(実務の使いどころ)** - **重い処理をバックグラウンドで動かしたい** → `nice` で起動時に優先度を下げる - **実行中のプロセスが重すぎる** → `renice` で動的に優先度を下げる - **一般ユーザーは優先度を下げることしかできない**(上げるには root 権限が必要) ::: ## nice 値とは何か? {#nice-value} nice 値は **-20〜19 の整数**で、数値が小さいほど優先度が高い。 | nice 値 | 優先度 | 用途例 | | ------- | ------------------ | ----------------------------- | | -20 | 最高 | リアルタイム処理(root のみ) | | 0 | 通常(デフォルト) | 一般プロセス | | 10 | 低め | バックグラウンドバッチ | | 19 | 最低 | アイドル時のみ実行 | ::: warning 名前に反して「nice 値が高い(19)=他プロセスに優しい=自分の優先度が低い」という逆の関係になっている。 ::: ## nice - 優先度を指定して起動する {#nice-command} ### 基本構文 ```bash nice -n <コマンド> ``` `-n` オプションで nice 値の**増減量**を指定する。省略するとデフォルトの **+10** が適用される。 ### 実例 ```bash # バックアップスクリプトを低優先度(nice値=10)で起動 nice -n 10 tar czf /backup/home.tar.gz /home/ # 最低優先度(nice値=19)で実行 nice -n 19 ./long-batch-job.sh # オプション省略(デフォルトで+10) nice ./heavy-script.sh ``` ### 現在のプロセスの nice 値を確認する ```bash nice ``` ```output 0 ``` 引数なしで実行すると、現在のシェルの nice 値を表示する。 ## renice - 実行中プロセスの優先度を変更する {#renice-command} ### 基本構文 ```bash renice -n -p renice -n -u <ユーザー名> renice -n -g <グループ名> ``` ### PID を指定して変更する ```bash # PID 1234 の nice 値を 15 に変更 renice -n 15 -p 1234 ``` ```output 1234 (process ID) old priority 0, new priority 15 ``` ### ユーザーの全プロセスを変更する ```bash # ユーザー「worker」の全プロセスを nice 値 10 に設定 renice -n 10 -u worker ``` ::: tip `pgrep` と組み合わせると、プロセス名から PID を取得して直接変更できる。 ```bash renice -n 19 -p $(pgrep heavy-job) ``` ::: ## プロセスの nice 値を確認する {#check-nice} ### top で確認する `top` の `NI` 列が nice 値を示す。`PR` 列は OS が計算したスケジューリング優先度(`PR = 20 + NI`)。 ```bash top ``` ```output PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1234 user 30 10 102400 12000 8000 R 15.0 0.2 0:05.23 tar ``` ### ps で確認する ```bash ps -eo pid,ni,comm ``` ```output PID NI COMMAND 1 0 systemd 1234 10 tar 5678 0 bash ``` ## 権限のルール {#permissions} 優先度操作にはユーザー権限による制限がある。 | 操作 | 一般ユーザー | root | | --------------------------------- | ------------ | ---- | | 優先度を下げる(nice 値を増やす) | 可 | 可 | | 優先度を上げる(nice 値を減らす) | 不可 | 可 | | 他ユーザーのプロセスを変更 | 不可 | 可 | 一般ユーザーが nice 値を下げようとするとエラーになる: ```bash renice -n -5 -p 1234 ``` ```output renice: failed to set priority for 1234 (process ID): Permission denied ``` ::: tip **実務パターン** ```bash # サービスへの影響を最小化してバッチジョブを起動 nice -n 15 /opt/scripts/nightly-backup.sh & # 実行中ジョブが重すぎる場合に動的に優先度を下げる renice -n 19 -p $(pgrep heavy-job) # バックグラウンドで圧縮しながら作業を継続 nice -n 10 gzip /var/log/large.log & ``` ::: ::: warning **やってはいけないこと** - root で `nice -n -20` を本番サービスに適用(他のサービスが枯渇する) - nice だけで CPU 使用率を制限しようとする(`cgroups` / `cpulimit` が適切) ::: ## 次に読む {#next} - [プロセス管理基礎 - ps/top/kill の使い方](/articles/tutorials/process-management-basics) - [top/htop 徹底活用 - ボトルネックを見抜く](/articles/tutorials/top-htop-mastery) - [ジョブ制御入門 - jobs/fg/bg/Ctrl+Z](/articles/tutorials/job-control-basics) # nl コマンド入門 - 行番号を付けて出力する Source: https://penguin-gym-linux.com/articles/tutorials/nl-line-numbering ## この記事でわかること {#intro} - **`nl` コマンド** で行番号を付けて出力する方法が分かる - `nl` と `cat -n` の **決定的な違い**(空行の扱い)が分かる - 番号の **桁幅・区切り文字・開始番号・増分・書式** の変え方が分かる - ログやソースの「何行目か」を **すぐ伝えられる** ようになる ::: tip **結論(先に覚えるべき型)** - とりあえず番号を付けたい → `nl file` - 空行にも番号を振りたい → `nl -b a file` - `cat -n` でも全行に番号は振れる(手軽さ優先ならこちら) ::: ## 1. nl コマンドって何? {#what} > **結論**: `nl`(number lines)はテキストの各行の先頭に行番号を付けて出力するコマンド。既定では空行に番号を振らない。 ::: dialogue @lina: 先輩、ファイルの中身を「何行目」って数えながら見たいんですけど、いちいち指で数えるしかないですか? @linny: いや、`nl` コマンドを使えば行番号を付けて表示してくれるよ。`nl` は number lines、つまり「行に番号を振る」の略なんだ。 @lina: 早く知りたかったです。どう使うんですか? @linny: ファイル名を渡すだけ。`nl ファイル名` でいいよ。 ::: `memo.txt` という 3 行のファイルで試してみよう。 ```bash $ cat memo.txt ``` ```output りんご みかん ぶどう ``` ここに `nl` を通すと、各行の先頭に番号が付く。 ```bash $ nl memo.txt ``` ```output 1 りんご 2 みかん 3 ぶどう ``` ::: highlight **nl の基本** `nl ファイル名` で、各行の先頭に **右寄せの行番号** が付く。番号と本文の間は **タブ** で区切られる。 ::: ## 2. nl と cat -n は何が違う? {#vs-cat} > **結論**: 最大の違いは空行の扱い。`nl` は既定で空行に番号を振らないが、`cat -n` はすべての行に番号を振る。 ::: dialogue @lina: 行番号なら `cat -n` でも付きますよね? 何が違うんですか? @linny: いい質問。一番の違いは **空行の扱い** だよ。実際に見てみよう。 ::: 途中に空行が入った `list.txt` を用意する。 ```bash $ cat list.txt ``` ```output 赤 青 緑 ``` まず `nl` を通すと、**空行には番号が振られない**。 ```bash $ nl list.txt ``` ```output 1 赤 2 青 3 緑 ``` 一方 `cat -n` は、**空行も含めて全部の行に番号を振る**。 ```bash $ cat -n list.txt ``` ```output 1 赤 2 3 青 4 5 緑 ``` ::: highlight **使い分けの目安** | やりたいこと | コマンド | | ---------------------------------- | ---------------------- | | 中身のある行だけ数えたい | `nl file` | | 空行も含めて物理的な行数を数えたい | `cat -n file` | | 番号の桁幅や書式を細かく変えたい | `nl`(オプション豊富) | ::: ::: dialogue @lina: なるほど、`nl` は「意味のある行」を数えてくれる感じなんですね。 @linny: そう。あとで紹介するけど、`nl` は番号の幅や区切り文字を細かく指定できるから、見た目を整えたいときにも強いよ。 ::: ## 3. 空行にも番号を付ける:-b a {#body-all} > **結論**: `-b a` を付けると空行を含めた全行に番号が振られる。`-b` は本文の番号付け方式(body)を指定するオプション。 空行にも番号を振りたいときは `-b a` を使う。`a` は all(すべての行)の意味。 ```bash $ nl -b a list.txt ``` ```output 1 赤 2 3 青 4 5 緑 ``` ::: tip `-b` で指定できる主な方式 - `-b a`:すべての行に番号を振る(all) - `-b t`:空行以外に番号を振る(**既定値**、t は text) - `-b n`:番号を振らない(none) ::: ::: dialogue @lina: 既定が `-b t` だから、何も付けないと空行が飛ばされるんですね。 @linny: その通り。「全部に振りたい」と思ったら `-b a` を思い出して。 ::: ## 4. 番号の見た目を整える:-w / -s {#format} > **結論**: `-w` で番号の桁幅、`-s` で番号と本文の区切り文字を変えられる。既定は幅 6・区切りはタブ。 ### 4-1. 桁幅を変える(-w) 既定では番号の幅は 6 桁分とられている。`-w` で幅を指定できる。 ```bash $ nl -w 3 memo.txt ``` ```output 1 りんご 2 みかん 3 ぶどう ``` ### 4-2. 区切り文字を変える(-s) 番号と本文の間は既定でタブ。`-s` で好きな文字列に変えられる。 ```bash $ nl -s '. ' memo.txt ``` ```output 1. りんご 2. みかん 3. ぶどう ``` ::: dialogue @lina: `-s '. '` で「1. りんご」みたいに、箇条書きっぽくできるんですね。 @linny: そう。区切りはスペースを含めてもいいから、`-w` と `-s` を組み合わせると読みやすい一覧が作れるよ。 ::: ## 5. 開始番号と増分を変える:-v / -i {#start-step} > **結論**: `-v` で開始番号、`-i` で増分を指定できる。100 行目から始める・2 ずつ増やす、といった調整ができる。 ### 5-1. 開始番号を変える(-v) `-v` は番号の開始値。100 から始めたいときはこうする。 ```bash $ nl -v 100 memo.txt ``` ```output 100 りんご 101 みかん 102 ぶどう ``` ### 5-2. 増分を変える(-i) `-i` は 1 行ごとに増やす量。2 ずつ増やすには `-i 2`。 ```bash $ nl -i 2 memo.txt ``` ```output 1 りんご 3 みかん 5 ぶどう ``` ::: tip `-v` と `-i` は組み合わせられる。`nl -v 10 -i 10 memo.txt` なら 10, 20, 30 と振られる。 ::: ## 6. ゼロ埋め・左寄せ:-n {#number-format} > **結論**: `-n` で番号の書式を指定する。`rz` でゼロ埋め、`ln` で左寄せ、`rn`(既定)で右寄せ。 `-n` は番号の表示書式を決めるオプション。 ```bash $ nl -n rz -w 3 memo.txt ``` ```output 001 りんご 002 みかん 003 ぶどう ``` ::: highlight **`-n` で指定できる書式** | 値 | 意味 | | ---- | ---------------------------- | | `rn` | 右寄せ・ゼロ埋めなし(既定) | | `rz` | 右寄せ・ゼロ埋め | | `ln` | 左寄せ | ::: ## 7. パイプで使う {#pipe} > **結論**: `nl` はファイル名を省略すると標準入力を読む。他コマンドの出力にパイプでつないで行番号を付けられる。 ファイル名を渡さずパイプでつなぐと、前のコマンドの出力に番号を付けられる。 ```bash $ ls /etc | nl ``` ```output 1 hostname 2 hosts 3 passwd ``` ::: dialogue @lina: 一覧の「何番目か」をすぐ言えますね。 @linny: そう。`grep` の結果に `nl` をつないで「何件目か」を数えるのもよくやる使い方だよ。 ::: ::: warning パイプ元に空行が含まれると、既定では空行に番号が振られない。件数を正確に数えたいときは `-b a` を付けるか `cat -n` を使うこと。 ::: ## 8. ミニ課題:実際にやってみよう {#exercise} > **結論**: ファイル作成・空行を含む番号付け・書式調整の 3 問で、`nl` の基本を手で確かめる。 ::: dialogue @lina: 手を動かして覚えたいです! @linny: いいね、3 問用意したよ。まずは練習用のファイルを作ろう。 @linny: `printf 'a\n\nb\n\nc\n' > sample.txt` ::: **課題1**: `sample.txt` に `nl` で行番号を付けて表示しよう(空行は番号なしでよい)。 :::details ヒントを見る ファイル名を渡すだけ。`nl ファイル名`。 ::: :::details 解答例 ```bash $ nl sample.txt ``` ::: **課題2**: 同じ `sample.txt` で、**空行にも番号を振って**表示しよう。 :::details ヒントを見る 本文の番号付け方式を all にする `-b a` を使う。 ::: :::details 解答例 ```bash $ nl -b a sample.txt ``` ::: **課題3**: `sample.txt` の番号を **3 桁ゼロ埋め**(001, 002, ...)で表示しよう。 :::details ヒントを見る 書式 `-n rz` と桁幅 `-w 3` を組み合わせる。 ::: :::details 解答例 ```bash $ nl -n rz -w 3 sample.txt ``` ::: ## 9. コピペ用テンプレート {#templates} > **結論**: 基本・全行番号・書式調整・パイプのよく使う型をまとめて手元に置いておく。 ::: tip **よく使う型をまとめておく** ```bash # 基本(空行以外に番号) nl file.txt # 空行も含めて全行に番号 nl -b a file.txt # 桁幅3・区切りをドットに nl -w 3 -s '. ' file.txt # 100行目から、2ずつ nl -v 100 -i 2 file.txt # 3桁ゼロ埋め nl -n rz -w 3 file.txt # パイプで他コマンドの出力に番号付け ls /etc | nl ``` ::: ## 次に読む {#next} - [wcで行数・単語数を数える](/articles/tutorials/wc-text-counting) - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [tac / rev で行や文字を逆順にする](/articles/tutorials/tac-rev-text) - [仮想ターミナルでコマンドを試す](/terminal) # nmcli 入門 - NetworkManager をコマンドで操作する Source: https://penguin-gym-linux.com/articles/tutorials/nmcli-network-management ## この記事で解決できること {#intro} - `nmcli` で **ネットワークの状態を確認** できるようになる - **Wi-Fi 接続・固定 IP 設定** をコマンドだけで完結できる - `connection`(プロファイル)と `device`(実機)の **違い** が分かり、設定が消えなくなる ::: tip **結論(実務の型)** - 状態を見る → `nmcli device status` / `nmcli connection show` - 設定を変える → `nmcli connection modify <名前> <キー> <値>` - 反映する → `nmcli connection up <名前>` - 変更は **プロファイルに永続化** される(再起動しても残る) ::: ::: warning **前提(対象環境)** - NetworkManager が稼働する Linux(Ubuntu Desktop / RHEL / CentOS / Fedora / AlmaLinux 等) - `nmcli` は NetworkManager 付属。`systemctl status NetworkManager` で稼働を確認 - サーバ用途で `systemd-networkd` や `netplan` のみの環境では対象外 ::: ## nmcli とは何か? {#what} > **結論**: nmcli は NetworkManager をコマンドラインから操作する公式 CLI。GUI なしで接続設定を作成・変更・永続化できる。 `nmcli`(NetworkManager Command Line Interface)は、多くのディストリビューションで標準のネットワーク管理デーモン **NetworkManager** を操作するための公式コマンドだ。GUI のネットワーク設定パネルでできることは、ほぼすべて `nmcli` で実行できる。 nmcli の操作対象は「オブジェクト」で分類される。よく使うのは次の 5 つ。 | オブジェクト | 短縮 | 役割 | | ------------ | ---- | ---------------------------------- | | `connection` | `c` | 接続プロファイル(設定の保存単位) | | `device` | `d` | 物理 / 仮想ネットワークデバイス | | `general` | `g` | NetworkManager 全体の状態 | | `radio` | `r` | Wi-Fi / WWAN の電波 ON/OFF | | `networking` | `n` | ネットワーク機能全体の ON/OFF | ::: tip オブジェクト名は **先頭が一意なら省略可能**。`nmcli connection show` は `nmcli c s` と書ける。本記事では分かりやすさのため省略しない形で記載する。 ::: ## connection と device は何が違う? {#connection-vs-device} > **結論**: device は実在するネットワーク機器、connection は機器に適用する設定プロファイル。設定の永続化は connection 側に記録される。 ここが nmcli 理解の最大のポイント。 - **device(デバイス)**: `eth0` / `enp3s0` / `wlan0` のような実際のインターフェース。今この瞬間の状態(接続中か、IP は何か)を表す - **connection(接続プロファイル)**: 「このデバイスをこう設定する」という **設定の保存ファイル**。`/etc/NetworkManager/system-connections/` 配下に保存され、再起動しても残る 1 つのデバイスに対し複数のプロファイル(自宅用・社内用など)を用意し、`up` で切り替える、という使い方ができる。 ::: warning `ip addr add` で直接 IP を振っても **再起動で消える**。永続化したいなら必ず connection プロファイルを編集すること。これが「設定したのに消えた」事故の典型原因。 ::: ## ネットワークの状態を確認するには? {#status} > **結論**: 全体は `nmcli general status`、デバイスは `nmcli device status`、プロファイルは `nmcli connection show` で確認する。 まずは現状把握から。引数なしの `nmcli` は全デバイスの詳細を一覧表示する。 ```bash # NetworkManager 全体の状態 nmcli general status # デバイス一覧(状態・接続中プロファイル) nmcli device status # 接続プロファイル一覧 nmcli connection show ``` ```output DEVICE TYPE STATE CONNECTION enp3s0 ethernet connected Wired connection 1 wlan0 wifi connected MyHome-WiFi lo loopback unmanaged -- ``` 特定の接続やデバイスの詳細を見るには名前を付ける。 ```bash # プロファイルの全設定を表示 nmcli connection show "Wired connection 1" # デバイスの詳細(IP・DNS・ルートなど) nmcli device show enp3s0 ``` ::: tip `-f` で表示フィールドを絞れる。`nmcli -f DEVICE,STATE device status` のように指定すると、スクリプトでの解析が楽になる。 ::: ## Wi-Fi に接続するには? {#wifi} > **結論**: `nmcli device wifi list` で SSID を確認し、`nmcli device wifi connect password <パスワード>` で接続する。 Wi-Fi はスキャンして接続する 2 ステップ。 ```bash # 周辺の Wi-Fi をスキャンして一覧表示 nmcli device wifi list # SSID とパスワードを指定して接続 nmcli device wifi connect "MyHome-WiFi" password "your-password" ``` 接続に成功すると、同名の connection プロファイルが自動生成され、次回以降は自動接続される。 ```bash # 電波そのものを ON/OFF(機内モード相当) nmcli radio wifi off nmcli radio wifi on ``` ::: warning コマンド履歴にパスワードが残るのを避けたい場合は、`--ask` を付けて `password` を省略すると対話的に入力を求められる。共有サーバでは履歴対策を意識すること。 ::: ## 固定 IP を設定するには? {#static-ip} > **結論**: `ipv4.method manual` に切り替え、`ipv4.addresses` / `ipv4.gateway` / `ipv4.dns` を設定し、`connection up` で反映する。 サーバ運用で最も使う操作。DHCP(自動取得)から固定 IP へ切り替える流れを示す。 ```bash # プロファイル名を確認(以降 "Wired connection 1" と仮定) nmcli connection show # 固定 IP・ゲートウェイ・DNS をまとめて設定 nmcli connection modify "Wired connection 1" \ ipv4.method manual \ ipv4.addresses 192.168.1.100/24 \ ipv4.gateway 192.168.1.1 \ ipv4.dns "8.8.8.8 1.1.1.1" # 設定を反映(プロファイルを再適用) nmcli connection up "Wired connection 1" ``` ::: tip - `ipv4.addresses` は `/24` のように **プレフィックス長を必ず付ける** - DNS を複数指定するときは `"8.8.8.8 1.1.1.1"` のようにスペース区切りで引用符に入れる - `modify` した時点ではまだ反映されない。`up` で初めて適用される ::: DHCP(自動取得)に戻すときは `method` を `auto` にする。 ```bash nmcli connection modify "Wired connection 1" ipv4.method auto nmcli connection up "Wired connection 1" ``` 新しいプロファイルをゼロから作る場合は `connection add` を使う。 ```bash nmcli connection add type ethernet con-name office-static ifname enp3s0 \ ipv4.method manual ipv4.addresses 10.0.0.50/24 ipv4.gateway 10.0.0.1 ``` ## 接続の有効化・無効化はどうする? {#updown} > **結論**: `nmcli connection up <名前>` で有効化、`down` で無効化。プロファイルの削除は `connection delete`。 ```bash # プロファイルを有効化(接続) nmcli connection up office-static # プロファイルを無効化(切断、設定は残る) nmcli connection down office-static # プロファイルを完全に削除 nmcli connection delete office-static ``` `down` は接続を切るだけでプロファイルは残るのに対し、`delete` は設定ファイルごと消す。混同しないこと。 ```bash # デバイスごと切断したい場合 nmcli device disconnect enp3s0 # ネットワーク機能全体を OFF/ON nmcli networking off nmcli networking on ``` ## よくあるトラブルと対処 {#troubleshooting} > **結論**: 「device が unmanaged」「設定が反映されない」「接続できない」の 3 パターンに切り分けると原因に早く到達できる。 ### デバイスが unmanaged と表示される {#unmanaged} `nmcli device status` で `unmanaged` の場合、そのデバイスは NetworkManager の管理外。`/etc/netplan/` や `/etc/network/interfaces` など別の仕組みが管理している可能性が高い。 ```bash # NetworkManager に管理させる nmcli device set enp3s0 managed yes ``` ### modify したのに反映されない {#not-applied} `nmcli connection modify` は **プロファイルを書き換えるだけ**。実機への反映には `up` が必須。 ```bash nmcli connection up "Wired connection 1" ``` ### 設定値のキーが分からない {#keys} 設定可能なプロパティは膨大。現在値を一覧して目的のキーを探す。 ```bash # ipv4 関連のプロパティと現在値を表示 nmcli connection show "Wired connection 1" | grep ipv4 ``` ::: tip 変更が不安な操作は、先に `nmcli connection show <名前>` で現在値を控えておくとロールバックしやすい。サーバではリモート切断に備え、コンソールアクセスを確保してから IP 変更すること。 ::: ## まとめ / 次に読む {#next} `nmcli` は NetworkManager 環境のネットワーク設定を、GUI なしで永続的に管理できる標準ツール。`device`(実機)と `connection`(プロファイル)の分離さえ押さえれば、状態確認・Wi-Fi 接続・固定 IP 設定まで一貫した型で扱える。 - [/etc/hosts と /etc/resolv.conf - 名前解決の優先順位](/articles/tutorials/etc-hosts-resolv) - [ファイアウォール設定入門 - ufwとfirewalldの基礎](/articles/tutorials/firewall-basics) - [network 障害対応の実践 - ping/traceroute/digを使った切り分け](/articles/tutorials/network-troubleshooting-practical) # nohup/disown/screen - ターミナル切断後もプロセスを残す Source: https://penguin-gym-linux.com/articles/tutorials/nohup-disown-screen ## この記事で解決できること {#intro} - SSH 切断・ターミナル終了後もコマンドを **継続させる 3 つの方法** が分かる - `nohup` / `disown` / `screen` の **正しい使い分け基準** が身につく - 「うっかり nohup を付け忘れた」ときの **事後対応** ができる ::: tip **結論(実務の型)** | 状況 | 手段 | | ---------------------- | ---------------------- | | 最初から継続させる | `nohup cmd &` | | 実行後に切り離す | `disown %1` | | セッションを丸ごと保持 | `screen` または `tmux` | ::: ## SIGHUP とは何か? {#sighup} プロセスがターミナル切断後に死ぬ直接の原因は、シェルが配下のプロセスに送る **SIGHUP(Signal 1: Hangup)** にある。 ターミナルを閉じる、SSH 接続を切断する、いずれの場合もシェルは終了前に自分の管理するプロセスグループへ SIGHUP を送信する。デフォルトのシグナルハンドラは終了なので、バックグラウンドで走っているコマンドも道連れになる。 ```bash $ sleep 300 & [1] 12345 $ exit # シェル終了 → sleep に SIGHUP → 12345 が終了 ``` 以下で紹介する 3 つの手段は、それぞれ異なるレイヤーでこの SIGHUP 配送を遮断する。 ## nohup — 実行前に使う {#nohup} `nohup`("no hangup" の略)は、コマンドを **SIGHUP を無視する状態** で起動する。 ```bash $ nohup long-job.sh & ``` - `&` は任意だが、バックグラウンド実行とセットにするのが慣例 - STDOUT / STDERR は **`nohup.out`**(カレントディレクトリ)へ自動リダイレクト - ログファイルを明示する場合: ```bash $ nohup ./deploy.sh > /var/log/deploy.log 2>&1 & ``` ::: tip 出力が不要なら `/dev/null` へ捨てる。`nohup.out` が無制限に肥大化するのを防げる。 ```bash $ nohup ./job.sh > /dev/null 2>&1 & ``` ::: ::: warning `nohup` は SIGHUP をブロックするだけであり、プロセスをデーモン化するわけではない。シェル終了後もプロセスは生存するが、親プロセスは `init`(PID 1)に付け替わる。 ::: ## disown — 実行後に使う {#disown} `disown` は、すでに走っているジョブを **シェルの job table から削除**し、SIGHUP を配送しないようにする。`nohup` を付け忘れて実行してしまったときの事後対応に使う。 ```bash # 実行中のジョブを確認 $ jobs [1]+ Running long-job.sh & # ジョブ番号を指定して切り離す $ disown %1 # すべてのジョブを切り離す $ disown -a ``` ::: tip **`disown -h`(SIGHUP だけ除外)** job table から完全に消さず、SIGHUP の配送だけを止める。`jobs` での確認は引き続き可能。 ```bash $ disown -h %1 ``` ::: ### nohup を付け忘れたときの手順 ```bash # 1. フォアグラウンドで動いているなら Ctrl+Z で一時停止 → bg で再開 $ Ctrl+Z [1]+ Stopped ./deploy.sh $ bg %1 [1]+ ./deploy.sh & # 2. disown で SIGHUP を切る $ disown %1 # 3. ターミナルを閉じても OK ``` ## screen — セッションを永続化する {#screen} `screen` はターミナルセッション全体を仮想化する **ターミナルマルチプレクサ**。核心機能は **デタッチ→再接続** で、別端末や別 SSH からセッションへ戻れる。 ```bash # 名前付きセッションを作成して接続 $ screen -S mysession # セッション内で作業する # (Ctrl+A D でデタッチ — screen は裏で生き続ける) # 別の端末 / SSH から再接続 $ screen -r mysession # セッション一覧を確認 $ screen -ls ``` ### よく使う screen キーバインド | 操作 | キー | | -------------- | ------------------------- | | デタッチ | `Ctrl+A D` | | ウィンドウ作成 | `Ctrl+A C` | | 次のウィンドウ | `Ctrl+A N` | | 前のウィンドウ | `Ctrl+A P` | | セッション終了 | 最後のウィンドウで `exit` | ::: tip **screen vs tmux** 機能・設定の柔軟性では `tmux` が優れる。サーバに `tmux` が入っていれば積極的に使うべき。詳細は [tmux 入門](/articles/tutorials/tmux-basics) を参照。サーバ環境で `tmux` が使えない、あるいはシンプルさを優先するなら `screen` で十分。 ::: ## 3 つの使い分けまとめ {#compare} | 手段 | タイミング | 特徴 | | ----------------- | ---------- | --------------------------------------------- | | `nohup cmd &` | 実行前 | シンプル・標準搭載・ログが `nohup.out` に残る | | `disown` | 実行後 | 付け忘れ対策・ログリダイレクトは自分で管理 | | `screen` / `tmux` | どちらでも | デタッチ・再接続・複数ウィンドウ管理が可能 | ::: warning **避けるべきこと** - `nohup` なしのバックグラウンドジョブのままターミナルを閉じる → SIGHUP で強制終了 - `screen` セッション内で `Ctrl+D` を連打して「閉じた気」になる(実際はセッション終了)→ デタッチは `Ctrl+A D` - ログ出力先を指定せず `nohup.out` を溜め続ける → ディスク消費に注意 ::: ## 次に読む {#next} - [ジョブ制御入門 - jobs/fg/bg/Ctrl+Z](/articles/tutorials/job-control-basics) - [ps・top・killの使い方 - プロセス管理入門](/articles/tutorials/process-management-basics) - [tmux 入門 - ターミナル多重化の基本](/articles/tutorials/tmux-basics) # openssl コマンド入門 - 証明書・ハッシュ・暗号化の実務操作 Source: https://penguin-gym-linux.com/articles/tutorials/openssl-basics ## この記事で解決できること {#intro} - `openssl` で **自己署名証明書 / CSR** を作る型が分かる - ファイルの **SHA-256 ハッシュ** と **AES 暗号化** を即実行できる - 公開サーバの **証明書の有効期限** をワンライナーで確認できる ::: tip **結論(実務でよく使う 5 つ)** - バージョン確認 → `openssl version` - ハッシュ → `openssl dgst -sha256 file` - 証明書作成 → `openssl req -x509 -newkey rsa:2048 ...` - 証明書確認 → `openssl s_client -connect host:443` - 暗号化 → `openssl enc -aes-256-cbc -pbkdf2 ...` ::: ::: warning **前提(対象環境)** - OS:Ubuntu / 一般的な Linux - OpenSSL 1.1.1 以降(`openssl version` で確認) - 古い 1.0.x では `-pbkdf2` が使えないため注意 ::: ## openssl コマンドとは? {#what} > **結論**: openssl は TLS/SSL と暗号化機能を CLI から扱う万能ツール。証明書・ハッシュ・暗号化・乱数生成を 1 コマンドで完結できる。 `openssl` は OpenSSL ライブラリのフロントエンドで、サブコマンド方式で動く。`openssl <サブコマンド> <オプション>` の形が基本。 ```bash $ openssl version ``` ```output OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022) ``` 主なサブコマンドは次の通り。 | サブコマンド | 役割 | | ------------ | -------------------- | | `dgst` | ハッシュ計算 | | `enc` | 共通鍵暗号化 | | `genpkey` | 秘密鍵生成 | | `req` | CSR / 自己署名証明書 | | `x509` | 証明書の表示・変換 | | `s_client` | TLS 接続デバッグ | | `rand` | 乱数生成 | ## どうやってハッシュを計算するのか? {#hash} > **結論**: `openssl dgst -sha256 file` でファイルの SHA-256 を計算する。配布物の改ざん検知や整合性確認に使う。 ### ファイルのハッシュ ```bash $ openssl dgst -sha256 ubuntu.iso ``` ```output SHA256(ubuntu.iso)= 9bc6b8f6...(64桁の16進数) ``` `sha1` / `sha512` などアルゴリズムを差し替えれば他のダイジェストも取れる。 ### 文字列のハッシュ ```bash $ echo -n "hello" | openssl dgst -sha256 ``` ::: tip `echo -n` の `-n` を忘れると末尾の改行までハッシュ対象になり、値が変わる。文字列ハッシュでは必ず `-n` を付ける。 ::: ## 証明書はどう作るのか? {#cert} > **結論**: 開発・検証用なら `openssl req -x509` で秘密鍵と自己署名証明書を一度に作れる。本番は CSR を作って認証局に署名してもらう。 ### 自己署名証明書(開発・検証用) ```bash $ openssl req -x509 -newkey rsa:2048 \ -keyout key.pem -out cert.pem \ -days 365 -nodes \ -subj "/CN=localhost" ``` オプションの意味: - `-x509`:CSR ではなく自己署名証明書を出力 - `-newkey rsa:2048`:2048bit の RSA 鍵を新規生成 - `-keyout` / `-out`:秘密鍵 / 証明書の出力先 - `-days 365`:有効期間 - `-nodes`:秘密鍵をパスフレーズなしで保存("no DES") - `-subj`:対話入力を省略して Subject を指定 ::: warning `-nodes` はパスフレーズ保護を外す。鍵ファイルのパーミッションは `chmod 600 key.pem` で必ず本人のみに制限する。 ::: ### 本番向け:CSR を作る 認証局に署名してもらう場合は、秘密鍵と CSR(証明書署名要求)を作る。 ```bash $ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out key.pem $ openssl req -new -key key.pem -out request.csr -subj "/CN=example.com" ``` 生成した CSR の中身は次で確認できる。 ```bash $ openssl req -in request.csr -noout -text ``` ## サーバ証明書の有効期限はどう確認するのか? {#expiry} > **結論**: `openssl s_client` で公開サーバに接続し、`x509 -noout -dates` に渡すと有効期限をワンライナーで取得できる。 ### ローカルの証明書を見る ```bash $ openssl x509 -in cert.pem -noout -text ``` 有効期限だけ見たいなら `-dates`: ```bash $ openssl x509 -in cert.pem -noout -dates ``` ```output notBefore=Jun 5 00:00:00 2026 GMT notAfter=Jun 5 00:00:00 2027 GMT ``` ### 公開サーバの証明書を確認する ```bash $ echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \ | openssl x509 -noout -dates ``` ::: tip `-servername` は SNI(Server Name Indication)を指定する。同一 IP で複数ドメインを配信する環境では付けないと別の証明書が返る。 ::: ## ファイルを暗号化するには? {#encrypt} > **結論**: `openssl enc -aes-256-cbc -pbkdf2` で共通鍵(パスワード)暗号化できる。復号は同じ指定に `-d` を足すだけ。 ### 暗号化 ```bash $ openssl enc -aes-256-cbc -pbkdf2 -salt -in secret.txt -out secret.enc ``` 実行するとパスワードを聞かれる。オプションの意味: - `-aes-256-cbc`:AES-256(CBC モード) - `-pbkdf2`:鍵導出を PBKDF2 にする(必須級) - `-salt`:ソルトを付与(デフォルト有効、明示推奨) ### 復号 ```bash $ openssl enc -aes-256-cbc -pbkdf2 -d -in secret.enc -out secret.txt ``` ::: danger `-pbkdf2` を付けないと OpenSSL の旧来の弱い鍵導出(単一 MD5)が使われ、総当たりに弱くなる。暗号化・復号の両方で必ず同じオプションを揃えること。 ::: ## 安全なパスワードや鍵を生成するには? {#rand} > **結論**: `openssl rand` で暗号学的に安全な乱数を生成する。`-base64` でパスワード、`-hex` でトークンとして使える。 ```bash $ openssl rand -base64 24 ``` ```output Xa9b2C... (32文字程度のランダム文字列) ``` ```bash $ openssl rand -hex 32 ``` 16 進 64 桁のトークンが得られる。API キーやセッションシークレットの生成に使える。 ## まとめ:コピペ用テンプレート {#summary} > **結論**: 用途別の openssl ワンライナーを手元に置けば、証明書・ハッシュ・暗号化の作業を迷わず実行できる。 ::: tip **コピペ用:openssl 実務テンプレ** ```bash # バージョン確認 openssl version # SHA-256 ハッシュ openssl dgst -sha256 file # 自己署名証明書(開発用、1年) openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost" # 公開サーバ証明書の有効期限 echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates # AES-256 暗号化 / 復号 openssl enc -aes-256-cbc -pbkdf2 -salt -in in.txt -out out.enc openssl enc -aes-256-cbc -pbkdf2 -d -in out.enc -out in.txt # 安全なパスワード生成 openssl rand -base64 24 ``` ::: ::: warning **やってはいけないこと** - `-pbkdf2` 無しで `enc` を使う(弱い鍵導出) - 秘密鍵を `chmod 600` せず放置する - `-nodes` の鍵を本番サーバに置きっぱなしにする ::: ## 次に読む {#next} - [ファイル転送の基本:scp / rsync の使い分け](/articles/tutorials/scp-rsync-basics) - [SSH 鍵認証セットアップ](/articles/tutorials/ssh-key-setup) - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) # package 管理トラブルシューティング - 依存関係エラーの解決法 Source: https://penguin-gym-linux.com/articles/tutorials/package-troubleshooting ## 依存関係エラーとは? {#what} 依存関係エラーは「パッケージ A のインストールにはパッケージ B が必要だが、B が存在しない・バージョンが合わない」状態で発生する。原因の大半は①リポジトリの不整合、②途中で中断されたインストール、③サードパーティリポジトリの混在のいずれかだ。 ::: tip **解決の基本方針** 1. まず `apt update` / `dnf makecache` でリポジトリ情報を最新化する 2. `apt --fix-broken install` / `dpkg --configure -a` で自動修復を試みる 3. それでも解決しない場合のみ、個別対処に入る ::: ## apt の定番エラーと解決法 {#apt-errors} ### E: Unmet dependencies ``` E: Unmet dependencies. Try 'apt --fix-broken install' with no packages (or specify a solution). ``` 依存パッケージが不足している。**この順で試す:** ```bash sudo apt --fix-broken install ``` ```output Reading package lists... Done Building dependency tree Correcting dependencies... Done The following packages will be installed: libfoo1 ... ``` 欠損した依存関係を自動補完してインストールを完了させる。多くのケースでこれだけで解決する。 解決しない場合: ```bash sudo dpkg --configure -a sudo apt-get install -f ``` ### E: Unable to locate package ``` E: Unable to locate package foo ``` リポジトリにパッケージが見当たらない。 ```bash sudo apt update sudo apt install パッケージ名 ``` ::: tip `apt update` を実行してからインストールすれば、パッケージ一覧の古さが原因のエラーは大半が解消する。 ::: Universe リポジトリが必要な場合(Ubuntu): ```bash sudo add-apt-repository universe sudo apt update sudo apt install パッケージ名 ``` ### The following packages have been kept back ``` The following packages have been kept back: foo ``` 依存関係の変更を伴うアップグレードが保留されている。 ```bash # 個別パッケージを指定してインストール sudo apt install foo # または保留パッケージをまとめてアップグレード sudo apt full-upgrade ``` ## dpkg の破損状態からの復旧 {#dpkg-fix} `dpkg` が途中で中断されると(Ctrl+C、電源断など)、次回操作時に以下のエラーが出る。 ```output dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem. ``` **復旧手順:** ```bash sudo dpkg --configure -a ``` ```output Setting up libssl1.1:amd64 (1.1.1f-1ubuntu2.20) ... Processing triggers for libc-bin (2.31-13+deb11u7) ... ``` 特定パッケージが原因の場合は個別に指定: ```bash sudo dpkg --configure パッケージ名 ``` `--configure` でも解消しないパッケージは強制削除して再インストール: ```bash sudo dpkg --remove --force-remove-reinstreq パッケージ名 sudo apt install パッケージ名 ``` ::: danger `dpkg --force-all` はすべての依存チェックをスキップする最終手段。システムが不安定になるリスクがあるため、上記手順で解決しない場合のみ検討する。 ::: ## yum / dnf の依存エラー解決 {#yum-dnf} ### Transaction Check Error(ファイル競合) ``` Error: Transaction check error: file /path/to/file conflicts with file from package foo-1.0 ``` ファイルが既存パッケージと競合している。 ```bash # 競合ファイルの所有パッケージを確認 rpm -qf /path/to/file ``` ```output foo-1.0-1.el9.x86_64 ``` ```bash # 競合パッケージを削除してから再インストール sudo dnf remove 競合パッケージ名 sudo dnf install 目的パッケージ名 ``` ### Dependency problems(バージョン不一致) ``` Error: Problem: package foo-2.0 requires bar >= 2.0, but none of the providers can be installed ``` ```bash # 利用可能なバージョンを確認 dnf list --available bar # distro-sync でパッケージをリポジトリに整合させる sudo dnf distro-sync ``` ::: tip `dnf distro-sync` はインストール済みパッケージを現在のリポジトリの最新バージョンに同期させる。`yum` 環境なら `yum distro-sync` でも同じことができる。 ::: EPEL などのサードパーティリポジトリが競合源の場合: ```bash # 問題リポジトリを一時的に無効化してインストール sudo dnf install パッケージ名 --disablerepo=epel ``` ## リポジトリ問題のトラブルシューティング {#repo} ### GPG キーエラー ``` The following signatures were invalid: EXPKEYSIG XXXXXXXXXXXXXXXX ``` リポジトリの GPG 署名キーが期限切れ。 ```bash # Ubuntu / Debian — GPG キーを更新 curl -fsSL https://リポジトリのキーURL | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/repo.gpg sudo apt update # CentOS / Fedora — RPM キーをインポート sudo rpm --import https://リポジトリのキーURL ``` ### ロックファイルの競合 ``` E: Could not get lock /var/lib/dpkg/lock-frontend ``` 別の apt プロセスが実行中か、前回の実行が異常終了した状態。 ```bash # apt / dpkg を使用中のプロセスを確認 ps aux | grep -E "apt|dpkg" ``` **プロセスが存在する場合**: 終了するまで待つ。`sudo kill -9 PID` は最終手段。 **プロセスが存在しない場合**(前回の異常終了後): ```bash sudo rm /var/lib/dpkg/lock-frontend sudo rm /var/lib/dpkg/lock sudo dpkg --configure -a ``` ::: danger ロックファイルを削除するのは、`ps aux` で apt/dpkg プロセスが存在しないことを確認した後のみ。稼働中プロセスがあるまま削除するとパッケージデータベースが破損する。 ::: `yum` / `dnf` のロック: ```bash # yum ロック sudo rm /var/run/yum.pid # dnf ロック sudo rm /var/cache/dnf/*.lock ``` ## 診断フロー {#flow} 依存関係エラーが出たとき、次の順で試す。 | ステップ | コマンド | 効果 | | ----------------- | ------------------------------ | ---------------------- | | 1. キャッシュ更新 | `apt update` / `dnf makecache` | リポジトリ情報を最新化 | | 2. 自動修復 | `apt --fix-broken install` | 欠損依存を補完 | | 3. dpkg 完了 | `dpkg --configure -a` | 未設定パッケージを完了 | | 4. 強制修復 | `apt-get install -f` | 依存関係を強制解決 | | 5. 同期 | `dnf distro-sync` | リポジトリに整合 | ::: warning `--force` 系オプションはステップ 1〜4 で解決しない場合のみ検討する。 ::: ## 次に読む {#next} - [パッケージ管理入門 - apt/yumの基本操作と使い分け](/articles/tutorials/apt-yum-basics) - [dnfパッケージ管理 - Fedora/RHEL系の最新ツール](/articles/tutorials/dnf-package-management) - [Linuxエラーメッセージ辞典](/articles/tutorials/linux-error-messages-dictionary) # bash parameter expansion チートシート — ${var:-x} を読み解く Source: https://penguin-gym-linux.com/articles/tutorials/parameter-expansion ## パラメータ展開とは? {#intro} `${var}` のような構文が **bash parameter expansion(パラメータ展開)**。単なる変数の取り出しにとどまらず、デフォルト値の指定・文字列の加工・パターンマッチによる削除や置換まで幅広い操作ができる。`sed` や `awk` を呼び出さずシェル内で完結するため、スクリプトのパフォーマンス改善にも役立つ。 ::: tip **全構文チートシート** | 構文 | 動作 | | ----------------- | --------------------------------------------------- | | `${var}` | 変数の値を展開(曖昧さ回避のため `{}` で囲む) | | `${var:-val}` | 未設定または空文字なら `val` を返す(代入しない) | | `${var:=val}` | 未設定または空文字なら `val` を代入して返す | | `${var:+val}` | セット済みかつ非空なら `val` を返す | | `${var:?msg}` | 未設定または空文字なら `msg` を stderr に出して終了 | | `${#var}` | 変数の文字列長を返す | | `${var:N}` | offset N 文字目から末尾まで取得 | | `${var:N:L}` | offset N 文字目から長さ L を取得 | | `${var#pat}` | 先頭から最短マッチを削除 | | `${var##pat}` | 先頭から最長マッチを削除 | | `${var%pat}` | 末尾から最短マッチを削除 | | `${var%%pat}` | 末尾から最長マッチを削除 | | `${var/pat/rep}` | 最初のマッチを `rep` に置換 | | `${var//pat/rep}` | 全マッチを `rep` に置換 | | `${var/#pat/rep}` | 先頭一致のみ置換 | | `${var/%pat/rep}` | 末尾一致のみ置換 | | `${var^}` | 先頭の文字を大文字に変換(Bash 4+) | | `${var^^}` | 全文字を大文字に変換(Bash 4+) | | `${var,}` | 先頭の文字を小文字に変換(Bash 4+) | | `${var,,}` | 全文字を小文字に変換(Bash 4+) | ::: ## デフォルト値パターン(`:-` / `:=` / `:+` / `:?`){#defaults} 「未設定のとき」「セット済みのとき」で分岐する構文群。スクリプトの引数処理・環境変数の検証に頻出する。 ### `${var:-val}` — 未設定/空なら val を返す {#colon-minus} 最も使用頻度が高い。`var` が未設定か空文字のとき、`val` を返す。`var` への代入は発生しない。 ```bash name=${1:-"world"} echo "Hello, ${name}!" # 引数なし → Hello, world! # 引数 Alice → Hello, Alice! ``` ```bash LOG_DIR=${LOG_DIR:-/var/log/myapp} echo "ログ出力先: ${LOG_DIR}" ``` ### `${var:=val}` — 未設定/空なら val を代入 {#colon-equal} `:-` との違いは **変数に代入が発生する**点。以降のコードでも `var` は `val` になる。 ```bash : ${TMPDIR:=/tmp} echo "${TMPDIR}" # /tmp(未設定だった場合) ``` ::: warning 位置パラメータ(`$1` 等)に `:=` は使えない。通常の変数にのみ有効。 ::: ### `${var:+val}` — セット済みかつ非空なら val {#colon-plus} `:-` の逆の動作。`var` がセット済みで非空のときだけ `val` を返す。 ```bash VERBOSE=1 msg="処理完了${VERBOSE:+ (詳細モード)}" echo "$msg" # 処理完了 (詳細モード) unset VERBOSE msg="処理完了${VERBOSE:+ (詳細モード)}" echo "$msg" # 処理完了 ``` ### `${var:?msg}` — 未設定/空なら即時エラー {#colon-question} 必須変数の存在チェックに使う。未設定なら `msg` を stderr に出力してスクリプトを終了。 ```bash #!/bin/bash set -e DB_HOST=${DB_HOST:?"DB_HOST が設定されていません"} ``` ::: tip `set -u` でも未設定変数のエラーを検出できるが、`:?` はカスタムメッセージを出せる点で実用的。 ::: ## 文字列長と部分文字列抽出 {#substr} ### `${#var}` — 文字列長 {#length} ```bash str="Hello, World" echo ${#str} # 12 ``` ### `${var:N:L}` — 部分文字列 {#substring} offset `N` から長さ `L` の文字列を取り出す。`L` を省略すると末尾まで。 ```bash str="2026-06-02" echo ${str:0:4} # 2026(年) echo ${str:5:2} # 06 (月) echo ${str:8} # 02 (日) ``` ::: warning マイナスのオフセット(末尾から)は `${var: -2}` のように**スペースを入れる**か `${var:(-2)}` と書く。`${var:-2}` は「未設定なら 2」の `:−` 展開に解釈される。 ::: ```bash str="Hello" echo ${str: -3} # llo(末尾3文字) ``` ## パターン削除(`#` / `##` / `%` / `%%`){#pattern-remove} ファイルパスの加工やファイル名から拡張子を取り除くときに多用する。`basename`・`dirname` の代替として外部プロセスを起動しない点が利点。 ### 先頭から削除(`#` / `##`){#prefix-remove} - `${var#pat}` — 先頭から**最短**マッチを削除 - `${var##pat}` — 先頭から**最長**マッチを削除 ```bash path="/home/user/docs/report.txt" echo ${path#*/} # home/user/docs/report.txt(先頭の / 一つを削除) echo ${path##*/} # report.txt (最後の / より前をすべて削除) ``` ファイル名取得(`basename` 代替): ```bash file="/home/user/docs/report.tar.gz" echo ${file##*/} # report.tar.gz ``` ### 末尾から削除(`%` / `%%`){#suffix-remove} - `${var%pat}` — 末尾から**最短**マッチを削除 - `${var%%pat}` — 末尾から**最長**マッチを削除 ```bash file="report.tar.gz" echo ${file%.*} # report.tar(最後の .xxx を削除) echo ${file%%.*} # report (最初の . 以降をすべて削除) ``` ディレクトリ取得(`dirname` 代替): ```bash path="/home/user/docs/report.txt" echo ${path%/*} # /home/user/docs ``` ::: tip パターンには glob が使える。`${var#*_}` は最初の `_` まで削除、`${var##*_}` は最後の `_` まで削除。 ::: ## パターン置換(`/` / `//`){#replace} ### `${var/pat/rep}` — 最初のマッチを置換 {#replace-first} ```bash msg="foo bar foo" echo ${msg/foo/baz} # baz bar foo ``` ### `${var//pat/rep}` — 全マッチを置換 {#replace-all} ```bash msg="foo bar foo" echo ${msg//foo/baz} # baz bar baz ``` ### 先頭・末尾一致に絞った置換 {#replace-anchored} ```bash str="foo-bar-foo" echo ${str/#foo/baz} # baz-bar-foo(先頭一致のみ) echo ${str/%foo/baz} # foo-bar-baz(末尾一致のみ) ``` ::: warning パターンは glob として扱われる。正規表現は使えない(正規表現には `[[ $str =~ pattern ]]` を使う)。 ::: ## 大文字・小文字変換(Bash 4+){#case} Bash 4.0 以降で使用可能。macOS のデフォルト bash(3.x)では動かないため、`brew install bash` または zsh で代替する。 | 構文 | 動作 | 例 | | ---------- | -------------- | ----------------- | | `${var^}` | 先頭を大文字 | `hello` → `Hello` | | `${var^^}` | 全文字を大文字 | `hello` → `HELLO` | | `${var,}` | 先頭を小文字 | `HELLO` → `hELLO` | | `${var,,}` | 全文字を小文字 | `HELLO` → `hello` | ```bash name="alice" echo ${name^} # Alice echo ${name^^} # ALICE ``` ## 実務ユースケース集 {#usecases} ### ファイル拡張子の取得・変換 ```bash file="image.png" ext=${file##*.} # png base=${file%.*} # image new="${base}.webp" # image.webp ``` ### スクリプト引数のデフォルト値 ```bash #!/bin/bash ENV=${1:-production} PORT=${2:-8080} echo "起動: env=${ENV} port=${PORT}" ``` ### 必須環境変数チェック ```bash #!/bin/bash : ${API_KEY:?"API_KEY が未設定です。export API_KEY=... で設定してください"} : ${DB_URL:?"DB_URL が未設定です"} ``` ### URL からドメイン部分を取り出す ```bash url="https://example.com/path/to/page" no_proto=${url#*://} # example.com/path/to/page domain=${no_proto%%/*} # example.com ``` ### ログレベルを小文字に統一 ```bash level="WARNING" echo ${level,,} # warning ``` ### スペースを `_` に置換してファイル名を安全にする ```bash filename="my report 2026.txt" safe=${filename// /_} # my_report_2026.txt ``` ## 次に読む {#next} - [シェルスクリプトの書き方入門](/articles/tutorials/shell-scripting-basics) - [ヒアドキュメント入門 — 複数行文字列を扱う](/articles/tutorials/heredoc-basics) - [.bashrc と .profile の読み込み順](/articles/tutorials/bashrc-profile-order) # passwd / chage 入門 - パスワードと有効期限を管理する Source: https://penguin-gym-linux.com/articles/tutorials/passwd-chage-aging ## この記事で解決できること {#intro} - `passwd` と `chage` の **役割の違い** が分かる - パスワード変更・**アカウントロック**・状態確認を迷わず実行できる - パスワードの **有効期限(パスワードエイジング)** をポリシーどおりに設定できる - `/etc/shadow` を読んで **現状を即把握** できる ::: tip **結論(実務の型)** - **パスワードの値そのもの**(変更・ロック・削除)→ `passwd` - **期限・経過日数のルール**(最長/最短/警告/失効)→ `chage` - どちらも書き込み先は同じ `/etc/shadow`。他人のアカウント操作は **root(sudo)が必要** ::: ::: warning **前提(対象環境)** - OS:Ubuntu / 一般的な Linux(shadow-utils) - 他ユーザーの操作は `sudo` 前提 - ローカルアカウント(`/etc/shadow` 管理)が対象。LDAP / AD 連携環境は対象外 ::: ## passwd と chage は何が違うのか? {#diff} > **結論**: `passwd` はパスワードの「値」を、`chage` はパスワードの「期限ルール」を操作する。対象はどちらも同じ `/etc/shadow` の別フィールド。 `/etc/shadow` の 1 行はコロン区切りの 9 フィールドで構成される。両コマンドが触る場所を分けて理解すると混乱しない。 ```output alice:$6$xxxx...:19876:7:90:7:30:20089: │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └ (8) アカウント失効日(1970-01-01からの日数) │ │ │ │ │ │ └ (7) 失効後の猶予日数(inactive) │ │ │ │ │ └ (6) 期限切れ警告日数(warn) │ │ │ │ └ (5) 最長有効日数(max) │ │ │ └ (4) 変更後の最短経過日数(min) │ │ └ (3) 最終パスワード変更日(1970-01-01からの日数) │ └ (2) ハッシュ化パスワード └ (1) ユーザー名 ``` - `passwd` → 第 2 フィールド(ハッシュ)を書き換える - `chage` → 第 3〜8 フィールド(経過日数・期限)を書き換える ::: tip 日付フィールドは「1970-01-01 からの経過日数」で保存される。人が読む形式は `chage -l` が変換してくれる(後述)。 ::: ## パスワードを変更するには? {#passwd-change} > **結論**: 自分のパスワードは `passwd` だけで変更する。他人のパスワードは `sudo passwd ユーザー名` で root 権限が必要。 ### 自分のパスワード ```bash $ passwd ``` 現在のパスワードを 1 回、新しいパスワードを 2 回入力する。入力中は画面に何も表示されない(仕様)。 ### 他人のパスワード(管理者) ```bash $ sudo passwd alice ``` 現在のパスワードを聞かれず、新しいパスワードを 2 回入力するだけで設定できる。 ::: warning 新規ユーザーに初期パスワードを設定したら、次のログイン時に本人へ強制変更させるのが定石。`passwd --expire alice`(後述の `chage -d 0` と同義)で実現する。 ::: ## アカウントをロック・状態確認するには? {#lock-status} > **結論**: `passwd -l` でロック、`passwd -u` で解除、`passwd -S` で状態を 1 行で確認する。ロックはハッシュ先頭に `!` を付けてパスワード認証を無効化する。 ### 状態確認 ```bash $ sudo passwd -S alice ``` ```output alice P 06/05/2026 0 99999 7 -1 ``` 2 列目の意味: - `P`:使用可能なパスワードあり - `L`:ロック中 - `NP`:パスワード未設定 ### ロックと解除 ```bash # パスワード認証を無効化(退職者・休眠アカウント等) $ sudo passwd -l alice # 解除 $ sudo passwd -u alice ``` ::: warning `passwd -l` はパスワード認証を止めるだけ。**SSH 鍵認証は止まらない**。完全に締め出すにはアカウント失効(`chage -E` または `usermod -L` + シェル無効化)を併用する。 ::: ## パスワードの有効期限はどう確認するのか? {#chage-list} > **結論**: `chage -l ユーザー名` で、最終変更日・次回失効日・各種ポリシーを人が読める形式で一覧表示する。 ```bash $ sudo chage -l alice ``` ```output Last password change : Jun 05, 2026 Password expires : Sep 03, 2026 Password inactive : Oct 03, 2026 Account expires : never Minimum number of days between password change : 7 Maximum number of days between password change : 90 Number of days of warning before password expires : 7 ``` - **Password expires**:このパスワードが使えなくなる日(最終変更日 + max) - **Password inactive**:失効後さらに猶予が切れてアカウントが使えなくなる日(+ inactive) - **Account expires**:アカウント自体が無効になる日(`-E` で設定) ## パスワードの有効期限はどう設定するのか? {#chage-set} > **結論**: `chage -M` で最長日数、`-m` で最短日数、`-W` で警告日数、`-I` で失効猶予を設定する。複数オプションは 1 コマンドにまとめられる。 ### 主なオプション | オプション | 意味 | 例 | | ---------- | ------------------------------------ | --------------- | | `-M` | 最長有効日数(これを超えると失効) | `-M 90` | | `-m` | 変更後に再変更できるまでの最短日数 | `-m 7` | | `-W` | 失効前の警告日数 | `-W 7` | | `-I` | 失効後アカウント無効化までの猶予日数 | `-I 30` | | `-E` | アカウント失効日(YYYY-MM-DD) | `-E 2026-12-31` | | `-d` | 最終変更日の上書き | `-d 0` | ### まとめて設定 ```bash $ sudo chage -M 90 -m 7 -W 7 -I 30 alice ``` 「90 日で失効、変更後 7 日は再変更不可、7 日前から警告、失効後 30 日でアカウント無効」というポリシーになる。 ### 次回ログイン時に強制変更させる ```bash $ sudo chage -d 0 alice ``` 最終変更日を「エポック当日」に戻すことで「すぐ期限切れ」扱いとなり、次回ログイン時にパスワード変更を強制できる。 ::: tip 対話形式で 1 項目ずつ設定したい場合は引数なしの `sudo chage alice` を実行する。現在値が `[ ]` 内に表示され、Enter で据え置きできる。 ::: ## アカウントの失効日を設定するには? {#account-expire} > **結論**: `chage -E 日付` でアカウント自体の失効日を設定する。期限契約のユーザーや一時アカウントの自動無効化に使う。 ```bash # 2026-12-31 でアカウントを失効させる $ sudo chage -E 2026-12-31 alice # 失効を解除(無期限に戻す) $ sudo chage -E -1 alice ``` `-E` はパスワード失効(`-M`)とは独立している。**契約満了で確実にログインを止めたい**場合は `-M`(パスワード期限)ではなく `-E`(アカウント期限)を使う。 ## よくある事故と切り分け {#troubleshoot} > **結論**: 「変更できない」「即ログインできなくなった」の多くは、min 日数・`-d 0`・失効日設定の誤りが原因。`chage -l` で現状を必ず確認する。 ### すぐにパスワードを変えられない ```output You must wait longer to change your password ``` `-m`(最短日数)が効いている。管理者なら `sudo passwd` で即変更できる。 ### 設定直後にログインできなくなった `chage -d 0` と `-M`/`-I` の組み合わせで「即失効 + 猶予 0」になっていないか確認する。 ```bash $ sudo chage -l alice ``` ### root のパスワードは期限管理しない システムアカウントや `root` にエイジングを掛けると締め出し事故につながる。ポリシー適用は **対話ログインする一般ユーザーに限定** する。 ::: warning **やってはいけないこと** - `root` やサービスアカウントへ安易にエイジング適用 - `chage -E` と `passwd -l` を混同(前者はアカウント、後者はパスワード) - 設定後に `chage -l` で確認せず放置 ::: ## まとめと次に読む {#next} - [user・group 管理の実践](/articles/tutorials/user-group-practical) - [sudo / su の使い分け - 安全な権限昇格の基本](/articles/tutorials/sudo-su-switching) - [SSH 鍵認証セットアップ](/articles/tutorials/ssh-key-setup) # chmod・chown・sudoの使い分け - Linux権限管理の応用 Source: https://penguin-gym-linux.com/articles/tutorials/permissions-advanced 権限管理の基礎を終えたら、次は**「状況に応じて使い分ける判断力」**を身につけましょう。chmod・chown・sudo はどれも便利なコマンドです。ただし、**使う順番と判断基準**を間違えると事故につながります。 **用語の整理**(この記事で使う言葉を先に定義します) - **パーミッション(権限)**: だれがそのファイルを読めるか・書けるか・実行できるかの設定です。「アクセス権」とも呼びます。この記事では「権限」で統一します - **owner / group / other**: 権限を与える相手の 3 分類です。順に「所有者本人」「所有グループに属する人」「それ以外の全員」を指します。`chmod` では `u` / `g` / `o` と書きます - **r / w / x**: 読み取り(read)・書き込み(write)・実行(execute)の権限です。ディレクトリでは `x` の意味が変わります(後述) - **記号表記 / 数値表記**: `chmod u+w` のような書き方が記号表記です。`chmod 644` のような書き方が数値表記です。「シンボリックモード」「8 進数表記」も同じものを指します - **umask**: 新しく作るファイルから権限を差し引く設定値です - **root**: 何でもできる管理者ユーザーです。**sudo** は「このコマンドだけ root として実行する」という指示です ## 先に結論(判断の型) {#conclusion} 権限トラブルに遭遇したら、**この順番を崩さない**。 1. `ls -l` で **所有者・グループ・権限** を確認 2. 自分が **owner / group / other** のどれかを判断 3. 修正方法を選ぶ - 権限を足す → `chmod` - 所有者を変える → `chown` - 一時的に突破 → `sudo`(最後の手段) ::: warning **「とりあえず sudo」は事故の入口**です。原因を理解せずに突破すると、後で取り返しがつかなくなることがあります。 具体的には、`sudo` で作ったファイルが root 所有になります。次からは自分では編集できません。詳細は[後述](#sudo-spiral)します。 ::: ## chmod を"判断付き"で使う {#chmod} > **結論**: chmodは記号表記を基本とし数値表記は644や755の意味をすぐ説明できる場合のみ使う。 ### まずは記号表記で考える(安全第一) {#chmod-symbolic} ```bash $ chmod u+w report.txt ``` ```output -rw-r--r-- 1 user user 2048 report.txt ``` - 所有者(u)に書き込み権限(w)を追加します - 変更するのは指定した 1 点だけです。**影響範囲が限定的**なので事故りにくい書き方です **なぜ安全か** - 変更対象(u/g/o)が明示される - 既存権限を壊しにくい ### 危険な例:数値表記を雑に使う {#chmod-numeric-danger} ```bash $ chmod 777 report.txt ``` **問題点** - 全員(other を含む)に読み取り・書き込み・実行の権限を与えます - 同じサーバに入れる人なら誰でも中身を書き換えられます - 意図しない改変や、置き換えられたスクリプトの実行につながります ::: tip **数値表記は「意味を説明できるときだけ」使う**。`644`や`755`の意味を即答できないなら、記号表記を優先しよう。 ::: ### なぜ chmod 777 は危険なのか? {#chmod-777-detail} **777 = 誰でも何でもできる** - **r(読み取り)+ w(書き込み)+ x(実行)**を全員に付与 - 共有サーバーでは他ユーザーが悪意あるコードを書き込める **実際に起きた事故例** 本番サーバーで `/var/www` を 777 にしたケース: - 攻撃者がPHPファイルを改ざん - Webシェル(遠隔操作ツール)を設置された - サーバー全体が乗っ取られる事態に **安全な代替** - ディレクトリ: `755`(所有者のみ書き込み、他は読み取り・実行) - ファイル: `644`(所有者のみ書き込み、他は読み取りのみ) - Web サーバに書き込ませたいディレクトリだけ、所有グループを合わせて `chmod g+w` で許可する **それでも `777` を試したくなったら** `777` は「権限の問題を消す」のではなく「権限の確認をやめる」設定です。まず `ls -l` と `id` で、自分が owner / group / other のどれなのかを確認してください。必要な相手に必要な 1 ビットだけ足すのが正しい直し方です。 ### umask:新規ファイルのデフォルト権限を決める {#umask} **なぜ新規ファイルは 644 になるのか?** `umask` が「引き算」しているからです。`umask` は新規ファイルに与える権限から、指定した値を差し引く設定です。 ```bash $ umask ``` ```output 0022 ``` **考え方(引き算ではなくマスク)** umask は指定した権限ビットを**取り除く**設定です。算術的な引き算ではありません。`022` のときはたまたま引き算と同じ結果になりますが、他の値では一致しません。 | umask | ファイル(元 666) | ディレクトリ(元 777) | | ----- | ------------------ | ---------------------- | | 022 | 644 | 755 | | 002 | 664 | 775 | | 027 | 640 | 750 | | 077 | 600 | 700 | `umask 077` を「666 - 077」と計算すると桁借りが起きて成立しません。桁ごとに引くのではなく、指定したビットを落とす操作だと理解してください。 ![元の権限 666 から umask 022 が指定するビットを落として 644 になる過程を、owner / group / other の位置を示して3段で並べた図](/images/diagrams/umask-mask.svg){.article-diagram} {.diagram-figure} 図1: `umask 022` が落とすのは group と other の `w` だけです。残った権限がそのまま新規ファイルの 644 になります。上下を引き算しているのではなく、指定された位置の権限を消しているだけです(ファイルの場合。ディレクトリは 777 が起点で 755 になります)。 {.diagram-caption} **実務での使いどころ** - 共有ディレクトリでグループ書き込みを許可したい → `umask 002` - セキュリティ強化で他者の読み取りを禁止したい → `umask 077` ::: tip **変更は一時的**:umaskはシェルセッション内でのみ有効。永続化は `~/.bashrc` に記述。 ::: ## ディレクトリ権限の落とし穴(応用で一番事故る) {#directory} > **結論**: ディレクトリのx権限がないとcd不可でありls -lで現状確認し最小限の権限を追加する。 ```output dr--r--r-- 2 user user 4096 logs/ ``` ### 症状 {#directory-symptom} - `ls logs` はできます(中身の名前は読めます) - `ls -l logs` は名前だけ出て、サイズや日付が `?` になります(中のファイルの情報を取りに行けないため) - `cd logs` はできません(Permission denied になります) ### 理由 {#directory-reason} - ディレクトリの `x` は「中に入る権利」です。ファイルの `x`(実行権限)とは意味が違います - ディレクトリの `r` は「中のファイル名を一覧する権利」です。`r` だけでは `cd` も、中のファイルを開くこともできません ### 対応 {#directory-fix} ```bash $ chmod u+x logs ``` ::: tip **なぜこの対応が正しいか** - 必要最小限の権限追加 - 他ユーザーへの影響を広げない ::: ## chown:所有者変更は"最終判断" {#chown} > **結論**: chownは所有者が明確にズレている場合のみ使い-Rオプションはサービス停止を招くため慎重に。 ### 正しい使いどころ {#chown-correct} ```bash $ sudo chown user:user app.log ``` ファイルの所有者が**明確にズレている場合のみ**使用します。「権限エラーが出たから」という理由だけで所有者を変えないでください。 ### 実際に起きがちな事故 {#chown-disaster} ```bash $ sudo chown -R user:user /var/www ``` **何が起きるか** - Web サーバは `www-data` などの専用ユーザーで動いています - そのユーザーがファイルを読めなくなります - 結果として Web サーバが応答しなくなります **実行前に必ずやること** 1. `ls -ld 対象ディレクトリ` で現在の所有者を控える 2. `-R` を外して 1 ファイルだけで試す 3. `find /var/www ! -user www-data | head` のように、変更対象を先に一覧する ::: warning **「なぜ所有者を変える必要があるか」を言語化できないなら実行しない** ::: ### chown -R で詰んだ話(復旧方法付き) {#chown-r-recovery} **実際の事故** `/var/www` を全部 `chown -R myuser` した結果: - Apache/Nginx は `www-data` ユーザで動作 - 設定ファイルやログにアクセスできなくなった - Webサービスが全停止 **復旧方法** ```bash # Webコンテンツ領域を復旧 $ sudo chown -R www-data:www-data /var/www/html # 所有者を確認 $ ls -la /var/www/html/ ``` **教訓** - `-R`(再帰的)は影響範囲が広い - 実行前に `ls -la` で対象を確認 - サービスディレクトリは特に慎重に ## sudo は魔法ではない {#sudo} > **結論**: sudoは一時的な突破手段であり根本対応はchmodかchownで行い安易な連用は詰みの入口。 ```bash $ sudo rm important.txt ``` - 実行できることと、正しいことは別です - `sudo` は原因を消しません。原因を見えなくするだけです ### 安全な考え方 {#sudo-mindset} - `sudo` は一時的な突破手段です - 恒久対応は `chmod` か `chown` で行います - `sudo` を打つ前に「なぜ自分の権限では足りないのか」を 1 行で説明できるか確かめてください ::: danger **`sudo rm` は取り消せない** `sudo rm` にはゴミ箱がありません。消したファイルはバックアップからしか戻せません。特に `sudo rm -rf` は、パスを 1 文字打ち間違えただけで無関係なディレクトリを消します。 **安全なやり方** - 先に `ls` で対象を確認する。`rm` に渡す予定のパスをそのまま `ls` に渡して、出てきたものが消したいものと一致するか見る - 一括削除の前に `find ... -print` で対象一覧を出し、意図どおりなら `-delete` に置き換える - 消す代わりに `mv` で退避する。問題がないと確認できてから削除する ::: ### sudo 連打で後戻り不能になった話 {#sudo-spiral} **よくあるパターン** 1. `Permission denied` が出る 2. 「とりあえず sudo」で実行 3. 作成されたファイルが root 所有に 4. 次から自分で編集できない 5. 「また sudo」で対応… 6. 気づいたら root 所有ファイルだらけ ![sudo でファイルを作ると root 所有になり、次の編集が Permission denied になって再び sudo に戻る循環を示した図](/images/diagrams/sudo-owner-spiral.svg){.article-diagram} {.diagram-figure} 図2: `sudo` が作ったファイルは root 所有になります。次に自分で編集しようとすると弾かれ、そのユーザーがまた `sudo` を打つ。この輪が「とりあえず sudo」を再生産します。 {.diagram-caption} **実例** ```bash # 新規ファイルを sudo で作ると… $ sudo vim new-config.yaml # root 所有のファイルができる $ ls -l new-config.yaml -rw-r--r-- 1 root root 1024 new-config.yaml # 次から自分では編集できない $ vim new-config.yaml # Permission denied... ``` 既存の自分のファイルを `sudo vim` で編集した場合、所有者は変わりません。vim は書き戻すときに元の所有者を保ちます。root 所有のファイルが増えるのは、`sudo` を付けたコマンドが**ファイルを新しく作った**ときです。`sudo touch`、`sudo cp`、`sudo コマンド > ファイル` のリダイレクトも同じ理由で root 所有のファイルを作ります。 **正しい対応** - `sudo` を使う前に「なぜ権限がないか」を確認します - システムファイルの編集には `sudoedit`(`sudo -e`)を使います。編集後もファイルの所有者が変わりません - 作業後に `ls -l` で所有者を確認します。root 所有になっていたら `sudo chown $USER ファイル名` で戻します ### 代替案:所有者を変えない設計 {#sudo-alternative} **そもそも権限トラブルを起こさない方法** **1. 実行ユーザーを揃える** アプリケーションの出力先を、実行ユーザーが書き込めるディレクトリに変更する。 **2. グループ権限で解決する** ```bash # 開発者グループを作成 $ sudo groupadd developers # ユーザーをグループに追加 $ sudo usermod -a -G developers user # ディレクトリのグループを変更 $ sudo chgrp developers /path/to/project $ sudo chmod g+w /path/to/project ``` `usermod` でグループを追加しても、いま開いているセッションには反映されません。一度ログアウトして入り直してください。反映されたかどうかは `groups` コマンドで確認できます。 **3. アプリ側の設定を変更する** ログ出力先や一時ファイルの保存先を変更します。実行ユーザーが書き込める場所に寄せるほど、権限トラブルは起きにくくなります。 ## エラーメッセージ起点の切り分け(具体例) {#error-cases} > **結論**: Permission deniedはls -lで所有者と権限を確認し原因を特定してから対処する。 ### ケース1:Permission denied {#case-permission-denied} ```bash $ echo test > report.txt ``` ```output Permission denied ``` **確認手順** ```bash $ ls -l report.txt ``` ```output -r--r--r-- 1 user user 2048 report.txt ``` **判断** - 所有者が自分の場合 → `chmod u+w` で解決します - 所有者が違う場合 → まず所属グループでの解決を検討します。それでも足りないときだけ `chown` や `sudo` を検討します ### ケース2:Operation not permitted {#case-operation-not-permitted} ```bash $ chown user:user system.conf ``` ```output Operation not permitted ``` **原因** - 所有者の変更は root 権限が必要な操作です。一般ユーザーは自分のファイルであっても他人へ譲渡できません **判断** - 本当に所有者変更が必要か? - sudo を使う理由を説明できるか? ### 事故例:chmod -R の暴発 {#chmod-r-disaster} **最悪のパターン** ```bash # カレントディレクトリ全体を777に… $ chmod -R 777 . ``` **何が起きるか** - カレントディレクトリ配下すべてに影響します。隠しディレクトリ(`.git` や `.ssh`)も対象です - サブディレクトリのファイルが全員書き込み可能になります - `~/.ssh` が含まれていた場合、SSH は権限が緩すぎる鍵を拒否するため、鍵認証でログインできなくなります **防止策** - `-R` を使う前に、対象を必ず `ls -la` で確認します - まず `-R` なしで単一ファイル・単一ディレクトリに実行します - 意図どおりか確認してから範囲を広げます - ファイルとディレクトリで必要な権限は違います。まとめて変えず、`find . -type f -exec chmod 644 {} +` と `find . -type d -exec chmod 755 {} +` のように分けて指定します ## 実践課題(10分) {#exercise} > **結論**: 権限エラーを実際に体験しls -lで原因を読みとりどのコマンドで直すかを判断する。 以下のコマンドを実行して、権限エラーを体験してみましょう。作業用のディレクトリを新しく作るので、既存のファイルには影響しません。 ```bash $ mkdir perm-adv $ cd perm-adv $ touch test.txt $ chmod u-w test.txt $ echo test > test.txt ``` ### 確認ポイント {#exercise-checks} 1. `Permission denied` が出たか? 2. `ls -l` を見て理由を説明できるか? 3. どのコマンドで修正すべきか判断できるか? ::: tip **回答例** - `ls -l test.txt` → `-r--r--r--`(書き込み権限がない) - 自分が所有者なので `chmod u+w test.txt` で解決 ::: ## 次に読む {#next} > **結論**: 次は仮想ターミナルで実際に権限操作を安全に練習してLPIC-1の理解をさらに深める。 この記事で学んだ「判断の型」を、実際のターミナルで試してみましょう。Penguin Gym Linux の仮想環境なら、実機を壊す心配なく権限操作を練習できます。 - [LPIC-1 学習ハブ(全12記事と出題範囲マップ)](/lpic1) - [chmod・chownの使い方](/articles/tutorials/permissions-basics) - [Permission deniedの解決方法](/articles/tutorials/permissions-practical) - [ハードリンクとシンボリックリンク](/articles/lpic/hard-symbolic-links) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # chmod・chownの使い方 - Linux権限管理の基本とPermission denied対策 Source: https://penguin-gym-linux.com/articles/tutorials/permissions-basics この記事でできること: - `ls -l` の出力を読んで、原因を判断できる。 - chmod と chown を使い分けて、正しい直し方を選べる。 - 「とりあえず sudo」から卒業できる。 **想定読者**:Ubuntu でサーバを触りはじめた新人。 **前提**:一部の操作では sudo が必要です。 **用語の整理**:パーミッション(permission)とは、「だれがそのファイルを読めるか・書けるか・実行できるか」の設定のことです。日本語では「権限」「アクセス権」とも呼びます。この記事では「権限」で統一します。 **root(ルート)**とは、何でもできる管理者のユーザー名です。**sudo(スードゥ)**は「このコマンドだけ root として実行する」という指示です。**グループ**とは、複数のユーザーをまとめた集まりのことです。 ::: tip **先に結論(判断の型)** 1. `ls -l` で所有者と権限を見ます。 2. 自分が owner / group / other のどれなのかを判断します。 3. chmod か chown か sudo かを選びます。 ::: ## 判断フロー(早見表) {#decision-flow} > **結論**: Permission denied が出たら、まず `ls -l` で所有者と権限を見る。そのうえで chmod・chown・sudo のどれを使うかを選ぶ。 Permission denied が出たときは、次の表を上から順にチェックしてください。 | 症状 | 確認コマンド | 原因 | 対処 | | ------------------------------------------ | ----------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | ファイルに書き込めない | `ls -l ファイル名` | w権限がない | `chmod u+w ファイル名` | | スクリプトが実行できない | `ls -l スクリプト名` | x権限がない | `chmod u+x スクリプト名` | | ディレクトリに入れない(自分が所有者) | `ls -ld ディレクトリ名` | x権限がない | `chmod u+x ディレクトリ名` | | ディレクトリに入れない(他人 / root 所有) | `ls -ld ディレクトリ名` | 自分が owner でも group でもない | 所属グループを追加する(`sudo usermod -aG グループ名 $USER` 後に再ログイン)。閲覧だけなら `sudo ls ディレクトリ名` | | 他ユーザーのファイルを編集したい | `ls -l ファイル名` | 所有者が異なる | まず `sudo -u 所有者名 編集コマンド` を検討する。所有者を変える理由を説明できるときだけ `sudo chown $USER ファイル名`($USER は現在のユーザー名に自動展開) | | システムファイルを編集したい | `ls -l /etc/ファイル名` | root所有のファイル | `sudoedit /etc/ファイル名`(sudo vim より安全 — 編集中のファイルの所有者が変わらない) | `ls -ld` の `-d` は、ディレクトリ自身の情報を表示するオプションです。付けないと中身の一覧が出ます。 ::: warning **重要な確認手順** 1. `whoami` で自分のユーザー名を確認します。 2. `groups` で自分の所属グループを確認します。 3. `ls -l` で所有者・グループ・権限を確認します。 4. 自分が owner / group / other のどれなのかを判断します。 ::: ## まずは ls -l を読めるようになる(最重要) {#ls-l} > **結論**: 権限トラブルの9割は `ls -l` で解決の方向が見える。まずここを読めるようにするのが最優先だ。 権限トラブルの **9割は ls -l で解決の方向が見える**。ここを読めるようになるだけで、「なんとなく sudo」から卒業できます。 ```bash $ ls -l sample.txt ``` ```output -rw-r--r-- 1 user user 1234 Dec 17 12:00 sample.txt ``` ::: tip **見る順番(この順で固定)** 1. 先頭の `-` か `d` を見ます。ファイルかディレクトリかが分かります。 2. 権限 `rw-r--r--` を見ます。 3. 所有者 `user` を見ます。 4. グループ `user` を見ます。 ::: **なぜこの順番か**:権限エラーの原因は「誰が」「何を」できるかの組み合わせです。この順番で見れば、原因を論理的に切り分けられます。 ## rwx の意味を「判断基準」として理解する {#rwx} > **結論**: owner・group・other のどの立場かで、できることが変わる。そこを判断の基準にする。 `r` は読み取り、`w` は書き込み、`x` は実行を表します。この3文字が3回くり返され、owner・group・other の順に並びます。 例:`rw-r--r--` | 対象 | 権限 | できること | | ----------------- | ---- | ------------------ | | owner(所有者) | rw- | 読み書きOK、実行NG | | group(グループ) | r-- | 読みのみ | | other(その他) | r-- | 読みのみ | ![user が owner か、group に属するか、それ以外かで適用される rwx が分かれる判定の流れ図](/images/diagrams/permission-check-flow.svg){.article-diagram} {.diagram-figure} 図1: Linux は owner → group → other の順に判定し、最初に当てはまった1組の rwx **だけ**を適用します。owner に該当した時点で、group や other の権限は見ません。図が示すのは「どの1組が適用されるか」であり、その中身が許可かどうかは別の話です(適用された組が `---` なら何もできません)。 {.diagram-caption} ::: tip **判断例** - 自分が owner なら書けます。 - group や other の立場なら、読めるだけで書けません。 **自分がどの立場か**を確認するには `whoami` と `groups` を使います。 ::: ## chmod:まずは記号表記で考える(事故防止) {#chmod} > **結論**: 記号表記の `u+w` や `u+x` を基本にする。意味を説明できない数値表記は後回しでよい。 ### 基本 {#chmod-basic} ```bash $ chmod u+w sample.txt ``` chmod(change mode)は、ファイルの権限を変えるコマンドです。 **意味**: - `u`(所有者)に対して、 - `w`(書き込み)を、 - 追加します。 **なぜ記号表記が安全か**:「何を変えたか」が明確に分かるからです。`chmod 644` と書くより `chmod u+w` の方が意図が伝わります。レビューでも事故に気づきやすくなります。 ::: danger **よくある事故** ```bash $ chmod 777 sample.txt ``` **問題点**: - 意味を理解しないまま実行しがちです。 - 必要のないところまで権限を広げてしまいます。 数値表記は後回しで大丈夫です。まず記号表記を使ってください。 ::: **失敗するとどうなるか**:`chmod` はファイルの中身を書きかえません。ただし「1つ前に戻す」コマンドはありません。実行の前に `ls -l` の出力を控えておき、その状態に合わせて戻してください。 **安全に試す方法**:練習は `~/perm-test` のような自分専用のディレクトリで行ってください。システムのファイルには影響しません。 ### なぜ chmod 777 は危険なのか? {#chmod-777-danger} `777` は「誰でも読み書き実行できる」という意味です。 **実際に起きた事故例** `-R` は、そのディレクトリの中身すべてに同じ設定を適用するオプションです。本番Webサーバで `/var/www/html` を `chmod -R 777` にした結果、次のことが起きました。 - 攻撃者が PHP ファイルをアップロードしました。 - その PHP が実行され、サーバを乗っ取られました。 - 顧客データが流出し、サービスが停止しました。 **なぜ攻撃が成功したか** `777` は other(誰でも)に `w`(書き込み)と `x`(実行)を許可します。そのため Web サーバ経由でファイルを置かれ、そのまま実行されてしまいました。 **安全な代替設定** **数値表記の読み方**:r=4、w=2、x=1 の合計です。たとえば 755 は rwx(7) + r-x(5) + r-x(5) を表します。所有者は全権限、グループとその他は読み取りと実行だけになります。 | 対象 | 推奨値 | 権限の意味 | 理由 | | ------------ | ------ | ---------- | -------------------- | | ディレクトリ | 755 | rwxr-xr-x | 所有者のみ書き込み可 | | ファイル | 644 | rw-r--r-- | 所有者のみ書き込み可 | | 秘密鍵等 | 600 | rw------- | 所有者のみアクセス可 | ## ディレクトリの実行権限に注意 {#directory-x} > **結論**: ディレクトリに `x` がないと `cd` できない。`ls -ld` で確認し、`u+x` を最小限だけ足す。 ```output drwxr-xr-x 2 user user 4096 Dec 17 12:10 mydir ``` ディレクトリの `x` は「中に入れるかどうか」を表します。`cd` できるかどうか、と言いかえられます。`r` だけでは `cd` できません。ファイル一覧は見えますが、中には入れません。 ::: warning **詰まり例** `ls` はできるのに `cd` できないことがあります。この場合はディレクトリに `x` 権限がない可能性が大きいです。 **修正**:`chmod u+x mydir` を実行します。 ::: ## chown:所有者変更は慎重に {#chown} > **結論**: 所有者の変更は理由を説明できるときだけ行う。`-R` はサービス停止を招くので特に慎重に。 ### 基本 {#chown-basic} ```bash $ sudo chown user:user sample.txt ``` chown(change owner)は、ファイルの持ち主を変えるコマンドです。 **なぜ sudo が必要か**:所有者の変更はシステム管理の操作だからです。一般ユーザーが他人のファイルを勝手に自分のものにできたら危険です。 ::: danger **よくある事故** - `/var/www` 配下をまとめて `chown -R myuser` にすると、Webサーバが動かなくなります。 - root 所有にしてしまい、自分では戻せなくなります。 **考え方**:「なぜ所有者を変える必要があるのか」を必ず先に考えてください。 ::: **失敗するとどうなるか**:`chown` には取り消しコマンドがありません。元に戻すには、変更前の所有者名を自分で覚えておく必要があります。 **安全に試す方法**:実行の前に `ls -l` の出力を控えておいてください。所有者名が残っていれば、同じ `chown` で元に戻せます。 ### chown -R で詰んだ話(復旧方法付き) {#chown-r-disaster} **事故の経緯** ```bash $ sudo chown -R myuser:myuser /var/www/html ``` 「自分のファイルを編集したい」と思って実行した結果、次のことが起きました。 - Apache や Nginx は `www-data` ユーザーで動作しています。 - 所有者が変わったため、Webサーバがファイルを読めなくなりました。 - サイトが 403 Forbidden で表示されなくなりました。 **復旧方法** ```bash $ sudo chown -R www-data:www-data /var/www/html ``` **正しいアプローチ** 自分が編集したいだけなら、所有者を変える必要はありません。次のどちらかを選びます。 - 自分を `www-data` グループに追加します。 - または `sudo -u www-data vim file.php` で編集します。 ## sudo は魔法ではない {#sudo} > **結論**: sudo は根本原因を隠してしまう。先に `ls -l` で切り分け、理由を説明できるときだけ使う。 sudo は root として実行するだけのコマンドです。権限設計の問題をそのまま隠してしまうことがあります。 ::: danger **NGパターン** ```bash $ sudo chmod 777 ... ``` 「Permission denied が出たから sudo」「それでもダメだから 777」。これは**最悪のパターン**です。原因を理解しないまま、セキュリティホールを作っています。 ::: ::: tip **推奨** - まず `ls -l` で原因を切り分けます。 - 必要な場合だけ sudo を使います。 - sudo が必要な理由を説明できる状態で使います。 ::: ### sudo の乱用で起きた実際の事故 {#sudo-abuse} **事故パターン1:ファイルが編集できなくなる** ```bash $ sudo vim config.yaml ``` この後、普通に `vim config.yaml` で編集しようとすると、次のメッセージが出ます。 ```output E45: 'readonly' option is set (add ! to override) ``` **原因**:sudo で開いたときに root 所有になったためです。または .swp ファイルが root で作られたためです。 **解決策** - `sudo chown $USER:$USER config.yaml` で所有者を戻します。 - または最初から `sudoedit config.yaml`(sudo -e)を使います。 **事故パターン2:ホームディレクトリが root 所有に** ```bash $ sudo chown -R root:root ~ ``` この結果、ログインできなくなります。.bashrc が読めなくなり、SSH鍵も使えなくなります。 **復旧**:別のroot権限セッションから `chown -R user:user /home/user` を実行します。 ## よくある Permission denied の切り分け {#troubleshooting} > **結論**: `ls -l` で所有者と権限を確認する。自分が owner・group・other のどれかを特定してから対処する。 ### ケース1:ファイルに書き込めない {#case-write} `>>` はファイルの末尾に文字を書き足す記号です。つまり次のコマンドは書き込み操作です。 ```bash $ echo "test" >> /etc/hosts ``` ```output -bash: /etc/hosts: Permission denied ``` **診断** ```bash $ ls -l /etc/hosts ``` ```output -rw-r--r-- 1 root root 221 Dec 17 10:00 /etc/hosts ``` **原因** - 所有者は `root` です。 - 自分は other の立場なので `r--`、つまり読みのみです。 **ありがちな勘違い** 「sudo すればいい」という理解は、半分正解で半分不正解です。`/etc/hosts` はシステムファイルなので、意図的に root のみ書き込み可にしています。sudo で編集すること自体は正しい対処です。ただし「なぜ保護されているか」を理解したうえで使ってください。 **修正** ```bash $ sudoedit /etc/hosts ``` 追記だけなら次のコマンドでもできます。 ```bash $ echo "test" | sudo tee -a /etc/hosts ``` **なぜ `sudo echo "test" >> /etc/hosts` は失敗するか**:`>>` を処理するのはシェルであり、sudo の外側で動きます。root 権限が及ぶのは `echo` だけなので、書き込みは一般ユーザー権限のまま拒否されます。 ### ケース2:スクリプトが実行できない {#case-execute} ```bash $ ./deploy.sh ``` ```output -bash: ./deploy.sh: Permission denied ``` **診断** ```bash $ ls -l deploy.sh ``` ```output -rw-r--r-- 1 user user 1234 Dec 17 11:00 deploy.sh ``` **原因** - 権限が `rw-r--r--` なので、実行権限の `x` がありません。 - 自分が所有者なのに実行できない状態です。 **修正** ```bash $ chmod u+x deploy.sh $ ./deploy.sh ``` **なぜこの方法が安全か**:`u+x` は所有者にだけ実行権限を付けます。他のユーザーには影響しないため、必要最小限の変更で済みます。 ### ケース3:ディレクトリに入れない {#case-directory} ```bash $ cd /var/log/nginx ``` ```output -bash: cd: /var/log/nginx: Permission denied ``` **診断** ```bash $ ls -ld /var/log/nginx ``` ```output drwxr-x--- 2 www-data adm 4096 Dec 17 10:00 /var/log/nginx ``` **原因** - 権限が `rwxr-x---` なので、other には何も許可されていません。 - 自分は `www-data` でも `adm` でもありません。 **修正オプション** 1. `sudo cd` は使えません。cd はシェル組み込みだからです。中身を見るには `sudo ls /var/log/nginx` を使います。 2. 自分を `adm` グループに追加します。`sudo usermod -aG adm $USER` を実行し、再ログインします。グループ情報はログイン時に読み込まれるためです。 ## 権限問題診断チェックリスト {#checklist} > **結論**: `whoami` と `groups` で自分の立場を確認する。次に `ls -l` で権限を見て、対処方法を選ぶ。 Permission denied が発生したときは、次の順に確認してください。 **Step 1: 自分の立場を確認** - `whoami` で自分のユーザー名を確認しました。 - `groups` で所属グループを確認しました。 **Step 2: ファイル/ディレクトリの状態を確認** - `ls -l ファイル名` で権限と所有者を確認しました。 - 自分が owner / group / other のどれかを判断しました。 - 必要な権限(r/w/x)が自分の立場に付いているかを確認しました。 **Step 3: 対処方法を選択** - 権限が足りないときは `chmod` で権限を追加します。 - 所有者が異なるときは `chown` で所有者を変更します(要sudo)。 - システムファイルのときは `sudoedit` または `sudo` を使います。 **Step 4: 変更後の確認** - 再度 `ls -l` で変更が反映されたことを確認しました。 - 目的の操作が成功することを確認しました。 - 過剰な権限(777等)を付けていないことを確認しました。 ## 実践課題(5分) {#practice} > **結論**: `chmod u-w` でわざと権限を外す。権限エラーを体験し、修正コマンドを自力で判断する。 この課題は自分専用の練習ディレクトリで行います。システムのファイルは触らないため、失敗しても環境は壊れません。 ```bash $ mkdir ~/perm-test $ cd ~/perm-test $ touch a.txt $ ls -l $ chmod u-w a.txt $ echo "test" > a.txt ``` **期待結果**: - Permission denied が出ます。 - `ls -l` を見て「自分が owner だが w がない」と説明できます。 **復旧**: ```bash $ chmod u+w a.txt $ echo "test" > a.txt $ cat a.txt ``` ```output test ``` ## 次に読む {#next} > **結論**: 判断の型が身についたら、仮想ターミナルで権限操作を試して LPIC-1 の理解を深める。 この記事で紹介した「判断の型」が身についたら、Penguin Gym Linuxの実践課題で手を動かして定着させましょう。 **コマンド早見表** | 目的 | コマンド | 例 | | -------------------- | ------------ | ------------------------------- | | 権限・所有者確認 | `ls -l` | `ls -l sample.txt` | | 自分のユーザー名確認 | `whoami` | `whoami` | | 所属グループ確認 | `groups` | `groups` | | 書き込み権限を追加 | `chmod u+w` | `chmod u+w sample.txt` | | 実行権限を追加 | `chmod u+x` | `chmod u+x script.sh` | | 所有者を変更 | `sudo chown` | `sudo chown user:user file.txt` | | システムファイル編集 | `sudoedit` | `sudoedit /etc/hosts` | ::: tip **覚えておくべき3つのポイント** 1. **まず ls -l**:権限トラブルの9割はこれで原因が分かります。 2. **記号表記を使う**:`chmod u+w` は意図が明確で安全です。 3. **sudo は最後の手段**:原因を理解してから使います。 ::: - [LPIC-1 学習ハブ(全12記事と出題範囲マップ)](/lpic1) - [chmod・chown・sudoの使い分け](/articles/tutorials/permissions-advanced) - [Permission deniedの解決方法](/articles/tutorials/permissions-practical) - [ハードリンクとシンボリックリンク](/articles/lpic/hard-symbolic-links) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # Permission deniedの解決方法 - Linux権限管理の実践 Source: https://penguin-gym-linux.com/articles/tutorials/permissions-practical 「Permission denied」—このエラーを見て、すぐに `sudo` を打っていませんか?それは**根本解決ではなく、問題の先送り**です。この記事では、実務で頻出するケースを通じて**「正しい切り分け」と「恒久対応」**の型を身につけます。 ## 先に結論(実践での判断の型) {#conclusion} `Permission denied` を見たら、**この3ステップで切り分け**る。 1. **何をしようとしたか**を明確にする(読み取り / 書き込み / 実行) 2. `ls -l` で**対象の権限と所有者**を確認 3. 自分が **owner / group / other** のどれかを判断し、**最小限の修正**を選ぶ ::: warning **「とりあえず sudo」「とりあえず chmod 777」は禁句**。原因を特定してから最小限の修正を行うのが鉄則です。 ::: ### 実践での判断フロー {#practical-flow} ```bash # Step 1: エラーが出た操作を再現 $ echo "log" >> /var/log/app.log Permission denied # Step 2: 対象を確認 $ ls -l /var/log/app.log -rw-r----- 1 app-user app-group 1234 /var/log/app.log # Step 3: 自分が誰かを確認 $ id uid=1000(developer) gid=1000(developer) groups=1000(developer) # 結論: グループに入れば解決 $ sudo usermod -a -G app-group developer ``` ## ケース1:ログファイルに書き込めない {#case1} > **結論**: ログファイルへの書き込み失敗はls -lで所有者・グループを確認し、グループ追加か出力先変更で対処する。 ### 症状 {#case1-symptom} ```bash $ echo "test log" >> /var/log/myapp.log ``` ```output bash: /var/log/myapp.log: Permission denied ``` ### 診断手順 {#case1-diagnosis} ```bash # ファイルの権限を確認 $ ls -l /var/log/myapp.log -rw-r----- 1 root adm 2048 /var/log/myapp.log # 自分の所属グループを確認 $ groups developer ``` ### 判断 {#case1-analysis} - ファイルは `root:adm` 所有 - グループ `adm` には書き込み権限がない(`r--`) - 自分は `adm` にも入っていない ### 解決策(状況に応じて選択) {#case1-solutions} **A: アプリケーションの設定を変える(推奨)** ログの出力先を、自分に権限のある場所に変更する。 ```bash # アプリの設定ファイルでログ出力先を変更 LOG_PATH=/home/developer/logs/myapp.log ``` **B: グループに追加される** ```bash # admグループに追加(要再ログイン) $ sudo usermod -a -G adm developer ``` **C: 所有者を変更する(最終手段)** ```bash # 所有者を変更 $ sudo chown developer:developer /var/log/myapp.log ``` ::: tip **ベストプラクティス**: アプリケーションログは `/var/log/` 直下より、専用ディレクトリ(例: `/var/log/myapp/`)を作成し、適切な所有者で管理する方が安全。 ::: ## ケース2:ディレクトリ配下にファイルを作れない {#case2} > **結論**: 新規ファイル作成の失敗はファイルではなくディレクトリのls -ldで書き込み権限がないことを確認する。 ### 症状 {#case2-symptom} ```bash $ touch /var/www/html/newfile.html ``` ```output touch: cannot touch '/var/www/html/newfile.html': Permission denied ``` ### 診断手順(ここが重要) {#case2-diagnosis} ```bash # ファイルは存在しない → ディレクトリの権限が問題 $ ls -ld /var/www/html/ drwxr-xr-x 2 www-data www-data 4096 /var/www/html/ ``` ### 判断 {#case2-analysis} - ディレクトリは `www-data:www-data` 所有 - other には `r-x`(読み取りと実行のみ、書き込み不可) - **ファイル作成にはディレクトリへの書き込み権限(w)が必要** ### 解決策(状況に応じて選択) {#case2-solutions} **A: www-dataグループに追加(推奨)** ```bash # グループに追加 $ sudo usermod -a -G www-data developer # グループに書き込み権限を付与 $ sudo chmod g+w /var/www/html/ ``` **B: 開発用ディレクトリを別に作る** ```bash # 開発用ディレクトリを自分の所有で作成 $ sudo mkdir /var/www/html/dev $ sudo chown developer:developer /var/www/html/dev ``` ::: warning **絶対にやってはいけないこと**: `chmod 777 /var/www/html/`。セキュリティリスクが跳ね上がります。 ::: ### 事故例:chmod -R の暴発 {#case2-disaster} **実際に起きた事故** ```bash # 本番サーバーでうっかり実行 $ sudo chmod -R 777 /var/www/ ``` **何が起きたか** - 全ファイルが誰でも書き込み可能に - 悪意あるスクリプトが設置された - サーバー乗っ取りの踏み台に **教訓** - `-R` を使う前に、影響範囲を必ず確認 - まず単一ファイル/ディレクトリで試す - 本番サーバーでは特に慎重に ## ケース3:スクリプトが実行できない {#case3} > **結論**: スクリプトのPermission deniedはls -lで実行権限xがないと分かり、chmod u+xで付与する。 ### 症状 {#case3-symptom} ```bash $ ./deploy.sh ``` ```output bash: ./deploy.sh: Permission denied ``` ### 診断手順 {#case3-diagnosis} ```bash $ ls -l deploy.sh -rw-r--r-- 1 developer developer 512 deploy.sh ``` ### 判断 {#case3-analysis} - 所有者は自分 - **実行権限(x)がない**(`rw-r--r--`) ### 解決策 {#case3-solution} ```bash # 所有者に実行権限を付与 $ chmod u+x deploy.sh # 確認 $ ls -l deploy.sh -rwxr--r-- 1 developer developer 512 deploy.sh # 実行 $ ./deploy.sh ``` ::: tip **代替手段:bash で直接実行** 実行権限がなくても、インタプリタを明示すれば実行可能です。 ```bash $ bash deploy.sh ``` ただし、これは一時的な回避策。本番運用では適切な権限設定が推奨されます。 ::: ## sudo を使う前に考える {#sudo} > **結論**: sudoはシステム設定変更時にのみ使い、開発作業や自分のファイル操作では原因を調べて対処する。 `sudo` は強力なツールですが、**「何でも解決できる魔法」ではありません**。 ### sudo を使うべきとき {#sudo-when-use} - システム設定の変更(`/etc/` 配下など) - パッケージのインストール - サービスの起動・停止 ### sudo を使うべきでないとき {#sudo-when-not-use} - 自分の作業ファイルの編集 - 開発中のアプリケーションの実行 - 「とりあえず動かしたい」という理由だけ ### 事故例:root 所有ファイルの増殖 {#sudo-root-proliferation} **よくあるパターン** ```bash # 開発中にPermission denied $ npm install Permission denied # とりあえずsudo $ sudo npm install # node_modules が root 所有に... $ ls -ld node_modules/ drwxr-xr-x 500 root root 20480 node_modules/ # 次から普通に npm install できない! ``` **なぜ起きるか** - `sudo` で実行すると、作成されるファイルは `root` 所有になる - 一度 root 所有になると、通常ユーザーでは上書きできない - また `sudo` を使う → root 所有が増える... の悪循環 **修復方法** ```bash # 所有者を自分に戻す $ sudo chown -R $(whoami) node_modules/ # 以後は sudo なしで実行 ``` **予防策** - 開発作業は原則 `sudo` なしで行う - `Permission denied` が出たら原因を調べる - npm は `--prefix` でローカルインストール先を指定できる ### 代替案:そもそも権限トラブルを起こさない設計 {#sudo-alternative-design} **1. ホームディレクトリで作業する** `/home/user/projects/` 配下なら権限問題は起きにくい。 **2. 開発用グループを活用する** ```bash # 開発チーム用グループ $ sudo groupadd devteam $ sudo usermod -a -G devteam alice $ sudo usermod -a -G devteam bob # プロジェクトディレクトリをグループで管理 $ sudo chgrp devteam /var/www/project $ sudo chmod g+w /var/www/project $ sudo chmod g+s /var/www/project # 新規ファイルも自動的にdevteam所有 ``` **3. 環境変数でパスを変える** ログ、キャッシュ、一時ファイルの出力先を、権限のある場所に設定する。 ## 実践課題(10分) {#exercise} > **結論**: 書き込み権限なし・ディレクトリ作成拒否・スクリプト実行の3課題で診断から解決までの流れを体験できる。 以下のシナリオを順番に試して、「診断 → 判断 → 解決」の流れを体験しましょう。 ### 課題1: 書き込み権限のないファイルへの追記 {#exercise-1} ```bash $ mkdir perm-practice && cd perm-practice $ touch readonly.txt $ chmod u-w readonly.txt $ echo "test" >> readonly.txt ``` **確認ポイント** 1. `Permission denied` が出たか? 2. `ls -l` で原因を特定できるか? 3. どのコマンドで解決するか判断できるか? ### 課題2: ディレクトリへのファイル作成 {#exercise-2} ```bash $ mkdir restricted $ chmod u-w restricted $ touch restricted/newfile.txt ``` **確認ポイント** 1. エラーの原因は「ファイル」ではなく「ディレクトリ」だと理解できるか? 2. `ls -ld` でディレクトリの権限を確認したか? ### 課題3: スクリプトの実行 {#exercise-3} ```bash $ echo '#!/bin/bash' > test.sh $ echo 'echo "Hello!"' >> test.sh $ ./test.sh ``` **確認ポイント** 1. 実行権限がないことを特定できるか? 2. `chmod u+x` で解決できるか? 3. `bash test.sh` という代替手段を知っているか? ::: tip **解答の考え方** - 課題1: `chmod u+w readonly.txt` - 課題2: `chmod u+w restricted`(ディレクトリの権限) - 課題3: `chmod u+x test.sh` または `bash test.sh` ::: ## 次に読む {#next} Permission denied を見たら「sudo」ではなく「なぜ?」と考える習慣が、本当の実践力です。Penguin Gym Linux の仮想環境で、安全に権限トラブルを体験してみましょう。 - [LPIC-1 学習ハブ(全12記事と出題範囲マップ)](/lpic1) - [chmod・chownの使い方](/articles/tutorials/permissions-basics) - [chmod・chown・sudoの使い分け](/articles/tutorials/permissions-advanced) - [ハードリンクとシンボリックリンク](/articles/lpic/hard-symbolic-links) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # パイプとリダイレクト入門 - データの流れを理解する Source: https://penguin-gym-linux.com/articles/tutorials/pipe-redirect-basics ## この記事で学べること {#intro} - **「データの流れ」** という考え方が分かります。 - `|`(パイプ)と `>` `>>`(リダイレクト)の違いが分かります。 - 「標準出力」「標準エラー出力」を使い分けられます。 - `grep` や `sort` を組み合わせて **小さなコマンドを連結** できるようになります。 ::: tip **結論(先に覚えるべき型)** - 画面に出る結果を **ファイルに保存** したいときは `>` か `>>` を使います。 - 結果を **次のコマンドに渡す** ときは `|` を使います。 - **エラーだけ** を別扱いにしたいときは `2>` を使います。 ::: ::: warning **先に知っておくこと**:この記事で唯一注意が必要なのは `>` です。`>` は保存先のファイルを先に空にします。中身のあるファイルに `>` を使うと、その内容は消えます。 **失敗するとどうなるか**:消えた内容はゴミ箱に行きません。元には戻せません。 **安全に試す方法**:練習では `~/practice` のような新しいディレクトリを作り、そこで新しい名前のファイルに書き出してください。追記したいだけなら、最初から `>>` を使えば中身は消えません。本サイトの仮想ターミナルは学習用なので、あなたのパソコンは壊れません。安心して試してください。 ::: ## 1. まずはここから:データはどこから来てどこへ行く? {#dataflow} > **結論**: Linux コマンドは標準入力・標準出力・標準エラーの 3 つの口を持ち、行き先を付け替えられる。 ::: dialogue @lina: 先輩、`ls` を実行すると画面にファイル名が出ますよね。あれってどこから出てるんですか? @linny: いい質問だね。Linuxのコマンドは **「入力 → 処理 → 出力」** の3つの口を持っているんだ。図にするとこんな感じ。 @linny: 入力(標準入力 / stdin)→ コマンド → 出力(標準出力 / stdout)+ エラー(標準エラー出力 / stderr) @lina: 入力と出力で口が分かれてるんですね。 @linny: そう。そして大事なのは、**この口は付け替えできる** ということ。画面に出してるものを、ファイルに切り替えたり、別のコマンドに渡したり。それが今日のテーマだよ。 ::: ::: tip **言葉の整理**:標準入力・標準出力・標準エラー出力は、英語では stdin・stdout・stderr と書きます。読み方は「エスティーディーイン」などですが、記事や本では英語表記のまま出てくることが多いです。どちらも同じものを指します。 ::: ::: highlight **3 つの口の名前** | 名前 | 略称 | 番号 | デフォルトの行き先 | | ---------- | ------ | ---- | ------------------ | | 標準入力 | stdin | `0` | キーボード | | 標準出力 | stdout | `1` | 画面 | | 標準エラー | stderr | `2` | 画面 | ::: ## 2. リダイレクト:出力をファイルに保存する {#redirect} > **結論**: `>` は出力をファイルに上書き保存し、`>>` は追記する。大事なファイルには `>>` を使う。 ### 2-1. `>` で上書き保存 ```bash $ ls > files.txt ``` ::: dialogue @lina: 何も画面に出ません...失敗ですか? @linny: 大丈夫。`>` で **画面ではなくファイルに行き先を切り替えた** から、画面には出ないんだ。中身を見てごらん。 @linny: 中身を見るのは `cat` コマンド。ファイルの中身を画面に出すコマンドだよ。 ::: ```bash $ cat files.txt ``` ```output Documents Downloads files.txt Pictures ``` ### 2-2. `>>` で追記 ```bash $ echo "1行目" > log.txt $ echo "2行目" >> log.txt $ cat log.txt ``` ```output 1行目 2行目 ``` ::: warning `>` は **既存の中身を消して上書き** する。大事なファイルに `>` を使うと内容が消える。追記したいときは必ず `>>` を使うこと。 ::: ![中身が A のファイルに B を書き込んだ結果の比較図。cmd > file では A が B に置き換わり、cmd >> file では A の下に B が並ぶ](/images/diagrams/redirect-overwrite-append.svg){.article-diagram} {.diagram-figure} 図1: 左が書き込む前のファイル、右が書き込んだ後の同じファイル。上下は別々のケースだ。`>` は元の A が消えて B だけになり、`>>` は A の後ろに B が並ぶ。 {.diagram-caption} ### 2-3. `<` で入力をファイルから取る(応用) `wc` は数を数えるコマンドです。`-l` を付けると行数を数えます。 ```bash $ wc -l < log.txt ``` ```output 2 ``` ::: dialogue @lina: `wc -l log.txt` と何が違うんですか? @linny: 結果はほぼ同じだけど、**ファイル名がコマンドに渡らない** のが違い。`<` は「キーボードの代わりにファイルを差し込む」イメージ。最初は `>` と `>>` だけ覚えればOK。 ::: ## 3. パイプ:コマンドをつなぐ {#pipe} > **結論**: `|` は左のコマンドの出力を右の入力に渡す。小さなコマンドを連結して目的を達成する。 ### 3-1. 基本形 `|`(縦棒、パイプ)は **「左のコマンドの出力を、右のコマンドの入力にする」** 記号。 ```bash $ ls | wc -l ``` ```output 12 ``` ::: dialogue @lina: `ls` の結果を `wc -l` に渡してファイル数を数えてるんですね! @linny: その通り。これがパイプの威力。**1 つのコマンドで全部やる必要はなくて、小さなコマンドを連結して目的を達成する** のがLinuxの流儀だよ。 ::: ![cmd1 の stdout がパイプ記号を通って cmd2 の stdin につながる流れ図](/images/diagrams/pipe-flow.svg){.article-diagram} {.diagram-figure} 図2: `|` は左のコマンドの出力(stdout)を、右のコマンドの入力(stdin)に差し込む。画面には出ず、そのまま次のコマンドへ渡る。エラー(stderr)はこの管を通らず、画面にそのまま出る。 {.diagram-caption} ### 3-2. よく使う組み合わせ ```bash # ファイル一覧から「.txt」を含むものだけ抽出 $ ls | grep .txt # プロセス一覧から nginx を含む行を抽出 $ ps aux | grep nginx # ログを新しい順に表示 $ cat access.log | sort -r | head -n 10 ``` ::: highlight **パイプは何個でもつなげる** ```bash $ コマンド1 | コマンド2 | コマンド3 | ... ``` 各段で「絞り込む」「並び替える」「整形する」と役割を分担させるのがコツ。 ::: ## 4. リダイレクトとパイプの違い {#difference} > **結論**: `>` は出力先がファイル、`|` は出力先が次のコマンドの入力。保存なら `>`、加工なら `|`。 ::: dialogue @lina: 結果をどこかに送るのは同じに見えるんですけど、`>` と `|` って何が違うんですか? @linny: 行き先が違うんだ。図にするね。 ::: ::: highlight **`>` と `|` の違い** ``` cmd > file → 出力先が【ファイル】 cmd | cmd2 → 出力先が【次のコマンドの入力】 ``` - `>` は保存したいときに使います。 - `|` はもう一段加工したいときに使います。 ::: ```bash # パターンA: ls の結果をファイルに保存 $ ls > list.txt # パターンB: ls の結果を grep で絞り込む $ ls | grep ".log" # パターンC: 組み合わせ。grep で絞った結果をファイルに保存 $ ls | grep ".log" > log-files.txt ``` ::: tip `|` でつないでから最後に `>` でファイル化、というのが実務で一番多いパターン。 ::: ## 5. 標準エラー出力(stderr)の扱い {#stderr} > **結論**: エラーは stderr(番号 2)から出る。`2>` で別ファイルに分け、`2>&1` で標準出力にまとめる。 ### 5-1. エラーは別の口から出ている ```bash $ ls /not-exist > out.txt ``` ```output ls: '/not-exist' にアクセスできません: そのようなファイルやディレクトリはありません ``` ::: dialogue @lina: あれ? `> out.txt` でファイルに出したつもりなのに、エラーが画面に出てます... @linny: それは **エラーは標準エラー出力(stderr)から出ていて、`>` は標準出力(stdout)しか拾わない** から。エラーを拾うには番号 `2` を指定するんだ。 @lina: あ、そういうことか!出口が2つあって、`>` は片方しか拾わないんですね。エラーを取りたいときは `2>` を使えばいいんですね。 ::: ### 5-2. エラーを別ファイルに分ける ```bash $ ls /not-exist 2> error.log ``` ```output (画面には何も出ない) ``` ```bash $ cat error.log ``` ```output ls: '/not-exist' にアクセスできません: そのようなファイルやディレクトリはありません ``` ### 5-3. 通常出力とエラーをまとめて1ファイルに ```bash $ コマンド > all.log 2>&1 ``` ::: highlight **`2>&1` の意味** 「**標準エラー(2)を、標準出力(1)と同じ場所に流す**」という指示。 ログ収集で頻出する書き方なので、形で覚えてしまうのがおすすめ。 ::: ::: warning 書く順番に注意。`2>&1 > all.log` ではなく `> all.log 2>&1` の順序が正しい。逆にすると stderr がリダイレクト前の場所(画面)に流れてしまう。 ::: ## 6. よくある初心者のつまずき {#pitfalls} > **結論**: 自分自身へのリダイレクトはファイルを空にする。`tee` を使えばパイプ途中の内容も保存できる。 ### 6-1. `>` を使ったらファイルが空になった ```bash $ cat important.txt > important.txt # NG: ファイルが空になる ``` ::: warning `>` は **コマンド実行前にファイルを空にする** 動きをする。`cat important.txt` が読む前に中身が消えている。 **自分自身に向けてリダイレクトしない** のが鉄則。 ::: ### 6-2. `|` の前後にスペースが要る? 要らない? どちらでも動く。ただし **読みやすさのため前後にスペースを入れる** のが慣習。 ```bash $ ls|grep txt # OK だが見づらい $ ls | grep txt # 推奨 ``` ### 6-3. パイプの途中の結果が見たい ```bash $ ls | tee list.txt | wc -l ``` `tee` は **流れている内容をファイルに記録しつつ、そのまま次に渡す** コマンド。デバッグや「途中経過も保存しておきたい」ときに便利。 ## 7. ミニ課題:実際にやってみよう {#exercise} > **結論**: 保存・件数カウント・絞り込み保存の 3 問で、リダイレクトとパイプの基本を手で確かめる。 ::: dialogue @lina: 知識は入りました! 実際に手を動かしてみたいです。 @linny: いいね、3問用意したよ。ターミナルで試してみて。 ::: **課題1**: 自分のホームディレクトリのファイル一覧を `home-files.txt` に保存しよう。 :::details ヒント1(方向づけ)を見る 一覧を出すコマンドは知っていますね。その結果の行き先を、画面からファイルに付け替えます。 ::: :::details ヒント2(コマンド名)を見る 使うのは `ls` です。行き先を変える記号は `>` です。ホームディレクトリは `~` で表します。 ::: :::details 答えを見る ```bash $ ls ~ > home-files.txt $ cat home-files.txt ``` ```output Documents Downloads Pictures home-files.txt ``` `>` はコマンドを動かす前にファイルを作ります。そのため `home-files.txt` 自身も一覧に入ります。中身はお使いの環境で変わります。 ::: **課題2**: `/etc` 以下のファイル数を数えて画面に表示しよう(パイプを使う)。 :::details ヒント1(方向づけ)を見る 一覧を出すコマンドの結果を、行数を数えるコマンドに渡します。ファイルには保存しません。 ::: :::details ヒント2(コマンド名)を見る 使うのは `ls` と `wc` です。行数を数えるオプションは `-l`、2つをつなぐ記号は `|` です。 ::: :::details 答えを見る ```bash $ ls /etc | wc -l ``` ```output 220 ``` 表示される数はお使いの環境で変わります。数字が1つだけ出れば成功です。 ::: **課題3**: `/etc` の中で `.conf` で終わるファイルだけ取り出して、`conf-list.txt` に保存しよう。 :::details ヒント1(方向づけ)を見る 一覧を出し、そこから条件に合う行だけを残し、最後にファイルへ保存します。3段構えです。 ::: :::details ヒント2(コマンド名)を見る 使うのは `ls` と `grep` です。つなぐのは `|`、保存するのは `>` です。「`.conf` で終わる」は `"\.conf$"` と書きます。 ::: :::details 答えを見る ```bash $ ls /etc | grep "\.conf$" > conf-list.txt $ cat conf-list.txt ``` ```output adduser.conf ca-certificates.conf debconf.conf nsswitch.conf resolv.conf ``` `grep "\.conf$"` の `$` は「行末」を意味する正規表現です。そのため「`.conf` で終わる行」だけが残ります。並ぶファイル名はお使いの環境で変わります。 ::: ## 8. 振り返り {#review} > **結論**: 行き先がファイルなら `>`、次のコマンドなら `|`。エラーは別の口から出る。この3点を言葉にして確認する。 ::: dialogue @lina: 保存したいときは `>`、次のコマンドに渡したいときは `|` なんですね。 @linny: その通り。`>` は行き先がファイル、`|` は行き先が次のコマンド。ここさえ押さえれば迷わないよ。 @lina: エラーが画面に残ったのは、エラーだけ別の口から出ていたからでしたね。 @linny: うん。エラーも一緒に記録したいときは `> ファイル名 2>&1` と書けばいいよ。 ::: ## 9. 今日の3行まとめ {#summary} > **結論**: リダイレクト・パイプ・エラー出力の3点を3行で整理して定着させる。 1. `>` は上書き保存、`>>` は追記。中身を消したくないときは `>>` を使う 2. `|` は左のコマンドの結果を右のコマンドに渡す 3. エラーは別の口(2番)から出る。`2>` で分け、`2>&1` でまとめる ## 10. コピペ用テンプレート {#templates} > **結論**: 保存・追記・絞り込み・ログ収集・途中保存のよく使う型をまとめて手元に置いておく。 ::: tip **よく使う型をまとめておく** ```bash # 結果をファイルに保存(上書き) コマンド > out.txt # 結果をファイルに追記 コマンド >> out.txt # 結果を絞り込む コマンド | grep キーワード # 結果を並び替えて先頭10件 コマンド | sort | head -n 10 # 通常出力とエラーをまとめて記録 コマンド > all.log 2>&1 # エラーだけ別ファイルに コマンド 2> error.log # 途中経過もファイルに残しつつ次へ コマンド | tee progress.txt | 次のコマンド ``` ::: ## 次に読む {#next} - [LPIC-1 学習ハブ(全12記事と出題範囲マップ)](/lpic1) - [コマンドライン基礎](/articles/lpic/command-line-basics) - [テキストストリームフィルタ](/articles/lpic/text-stream-filters) - [正規表現とgrep応用](/articles/lpic/regular-expressions) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # printf コマンド入門 - echo より正確な書式付き出力 Source: https://penguin-gym-linux.com/articles/tutorials/printf-formatting ## printf でできること {#intro} `echo` で文字を表示するのには慣れたけれど、「桁をそろえたい」「小数点以下を2桁にしたい」と思ったことはないだろうか。そんなときに使うのが `printf` コマンドだ。 ::: tip **結論(先に要点)** - `printf` は **書式(フォーマット)を指定して** 文字を出力するコマンド - `echo` と違い、**改行は自動で付かない**(`\n` を自分で書く) - `%s`(文字列)・`%d`(整数)・`%f`(小数)に値を流し込む - `%5d` や `%-10s` で **桁そろえ**、`%.2f` で **小数の桁数** を指定できる ::: ## この記事でわかること - `printf` と `echo` の違いと使い分け - `printf` の基本の形(書式文字列 + 値) - `%s` `%d` `%f` など主要な書式指定子の使い方 - 桁そろえ・ゼロ埋め・小数桁数の指定方法 - `\n` `\t` などエスケープシーケンスの使い方 - 初心者がつまずきやすいポイント ## 1. printf と echo は何が違う? {#vs-echo} > **結論**: `echo` は手軽だが改行や `\n` の扱いが環境でばらつく。`printf` は書式を厳密に指定でき、出力をきれいにそろえられる。 ::: dialogue @lina: 文字を表示するなら `echo` でいいんですよね? `printf` って何が違うんですか? @linny: いい質問だね。`echo` は手軽なんだけど、「数字をそろえて表示」みたいな細かい制御が苦手なんだ。あと、環境によって挙動が変わる弱点もあるよ。 @lina: 環境によって変わるって、どういうことですか? @linny: たとえば `echo -e "a\tb"` でタブを出したいとき、シェルや OS によっては `-e` が効かずにそのまま `-e a\tb` と表示されることがある。`printf` ならそういうブレがないんだ。 ::: ```bash # echo は環境によって -e の扱いが変わる echo -e "名前\t点数" # printf なら書式どおりに必ず出力される printf '名前\t点数\n' ``` ```output 名前 点数 ``` ::: tip 細かい書式制御や、スクリプトで安定した出力が欲しいときは `printf` を選ぶと安心。ちょっと表示するだけなら `echo` で十分。 ::: ## 2. printf の基本の形は? {#basic} > **結論**: `printf '書式' 値...` の形。書式の中の `%s` などに、後ろの値が順番に入る。改行は `\n` を自分で書く。 ::: dialogue @lina: `printf` はどう書けばいいんですか? @linny: 「書式文字列」と「流し込む値」の2つを並べるだけだよ。まずは文字列を表す `%s` から見てみよう。 @lina: `%s` の `s` は string(文字列)の s ですね。 @linny: そのとおり。`%s` の場所に、後ろに書いた値がそのまま入るんだ。 ::: ```bash printf '%s\n' ペンギン ``` ```output ペンギン ``` 書式文字列の `%s` が「ここに値が入るよ」という目印になっている。後ろの `ペンギン` がその位置に流し込まれ、最後の `\n` で改行している。 ::: warning `printf` は **改行を自動で付けない**。`\n` を書き忘れると、次のプロンプトが同じ行にくっついて出る。最初のうちは必ず付けると覚えておこう。 ::: 値は複数並べられる。`%s` を必要なだけ書く。 ```bash printf '%s は %s が好き\n' リナ Linux ``` ```output リナ は Linux が好き ``` ## 3. 書式指定子(%s %d %f)の使い方は? {#specifiers} > **結論**: `%s` は文字列、`%d` は整数、`%f` は小数。値の種類に合わせて使い分ける。 ::: dialogue @lina: `%s` 以外にもあるんですか? @linny: よく使うのは3つ。文字列の `%s`、整数の `%d`、小数の `%f` だよ。表にまとめるね。 ::: | 指定子 | 意味 | 例の入力 | 出力 | | ------ | ------------------ | -------- | ---------- | | `%s` | 文字列 | `abc` | `abc` | | `%d` | 整数(10進) | `42` | `42` | | `%f` | 小数(既定6桁) | `3.14` | `3.140000` | | `%x` | 16進数 | `255` | `ff` | | `%%` | `%` という文字自身 | (なし) | `%` | ```bash printf '%s の値段は %d 円、税込 %f 円\n' りんご 100 110 ``` ```output りんご の値段は 100 円、税込 110.000000 円 ``` ::: dialogue @lina: あれ、`%f` だと `110.000000` って後ろにゼロがいっぱい付きました… @linny: `%f` は何も指定しないと小数点以下6桁で出るんだ。桁数を変える方法は次の章で教えるよ。 @lina: `%` そのものを出したいときは `%%` なんですね。 @linny: そう。`%` は特別な記号だから、文字として出したいときは2つ重ねるのがルールだよ。 ::: ::: warning `%d` に小数や文字を渡すとエラーや 0 になる。たとえば `printf '%d\n' abc` は「数値ではない」と警告が出る。値の種類と指定子は合わせること。 ::: ## 4. 桁そろえと精度はどう指定する? {#width} > **結論**: `%` と指定子の間に数字を書くと幅(桁)、`.` のあとの数字で小数桁数を指定できる。`-` で左寄せ、`0` でゼロ埋め。 ::: dialogue @lina: さっきの `110.000000`、`110.00` にしたいです。 @linny: `%.2f` と書けば小数点以下2桁になるよ。「`.` のあとの数字 = 小数の桁数」と覚えよう。 ::: ```bash printf '%.2f\n' 110 ``` ```output 110.00 ``` 幅(最低何文字分とるか)も指定できる。数字を表のように右そろえにしたいときに便利だ。 ```bash printf '%5d\n' 1 printf '%5d\n' 42 printf '%5d\n' 1000 ``` ```output 1 42 1000 ``` `%5d` は「最低5文字分の幅をとって右そろえ」という意味。桁数が違う数字でも右端がそろう。 主な修飾の組み合わせを覚えておこう。 | 書き方 | 意味 | 例(出力) | | ------- | -------------------- | ------------ | | `%5d` | 幅5・右寄せ | `␣␣␣42` | | `%-5d` | 幅5・左寄せ | `42␣␣␣` | | `%05d` | 幅5・ゼロ埋め | `00042` | | `%.2f` | 小数2桁 | `3.14` | | `%8.2f` | 幅8・小数2桁・右寄せ | `␣␣␣␣3.14` | | `%-10s` | 幅10・左寄せ文字列 | `abc␣␣␣␣␣␣␣` | (`␣` は半角スペースのこと) ::: dialogue @lina: `%-10s` の `-` は「左寄せ」なんですね。表のラベルをそろえるのに使えそう! @linny: まさにその用途にぴったり。商品名を左寄せ、値段を右寄せにすると、レシートみたいにきれいに並ぶよ。 ::: ```bash printf '%-10s %5d 円\n' りんご 100 printf '%-10s %5d 円\n' バナナ 80 printf '%-10s %5d 円\n' メロン 1200 ``` ```output りんご 100 円 バナナ 80 円 メロン 1200 円 ``` ::: tip 日本語(全角文字)は幅計算がバイト数基準(UTF-8 では全角1文字が3バイトなど)のため、見た目が完全にはそろわないことがある。半角英数字なら桁そろえはきれいに決まる。 ::: ## 5. よく使うエスケープシーケンスは? {#escape} > **結論**: `\n` は改行、`\t` はタブ、`\\` はバックスラッシュそのもの。書式文字列の中で特別な意味を持つ。 ::: dialogue @lina: `\n` は改行でしたよね。ほかにもあるんですか? @linny: タブの `\t` がよく使われるよ。列を区切って表っぽく見せるのに便利。代表的なものを並べるね。 ::: | 書き方 | 意味 | | ------ | ------------------------- | | `\n` | 改行 | | `\t` | タブ | | `\\` | バックスラッシュ `\` 自身 | | `\r` | 行頭に戻る(復帰) | ```bash printf '名前\t年齢\n' printf 'リナ\t17\n' ``` ```output 名前 年齢 リナ 17 ``` ::: warning バックスラッシュ自体を出したいときは `\\` と2つ書く。`\n` のように1つだと「改行の指示」と解釈されてしまう。 ::: ## 6. 書式が引数より少ないとどうなる? {#reuse} > **結論**: 値が書式指定子より多いと、`printf` は書式を先頭から繰り返し使う。これを使うとリスト出力が一行で書ける。 ::: dialogue @lina: `printf '%s\n' a b c` みたいに、`%s` が1つなのに値を3つ書いたらエラーになりますか? @linny: ならないよ。`printf` は値が余っていると、書式をもう一度先頭から使い直すんだ。だから3行に分かれて出る。 ::: ```bash printf '%s\n' りんご バナナ メロン ``` ```output りんご バナナ メロン ``` `%s\n` が値の数だけ繰り返されるので、3つの値がそれぞれ改行付きで出力される。リストをきれいに縦に並べたいときの定番テクニックだ。 ::: tip 逆に、値が足りないと文字列は空、数値は 0 として扱われる。たとえば `printf '%s と %s\n' りんご` は「りんご と (空)」になる。 ::: ## 7. よくあるつまずきは? {#pitfalls} > **結論**: 改行忘れ・`%` の二重化忘れ・型と指定子の不一致の3つが定番。落ち着いて書式文字列を見直そう。 ::: dialogue @lina: 自分でも書けそうな気がしてきました!でも失敗しそうで不安です… @linny: 大丈夫。つまずくポイントは決まっているから、先に知っておけば怖くないよ。 ::: ::: warning **つまずき1: 改行を忘れる** `printf '%s' ペンギン` だと改行されず、次のプロンプトがくっつく。末尾に `\n` を付ける。 ::: ::: warning **つまずき2: `%` をそのまま書く** `printf '50% 完了\n'` は `%␣` を書式指定子と誤解する。`%` の文字は `printf '50%% 完了\n'` と二重に。 ::: ::: warning **つまずき3: 型と指定子が合っていない** `%d` に文字列、`%s` のつもりで数値…という取り違え。出ない・0 になる・警告が出る原因になる。 ::: :::details ミニ課題(クリックで答え) 「商品名を左寄せ幅8、価格を右寄せ幅6、末尾に「円」と改行」を満たす `printf` を書いてみよう。 答えの一例: ```bash printf '%-8s %6d 円\n' コーヒー 350 ``` ```output コーヒー 350 円 ``` ::: ## 次に読む {#next} - [date コマンドで日付を整形する](/articles/tutorials/date-formatting) - [シェルスクリプト入門](/articles/tutorials/shell-scripting-basics) - [column / pr で表を整形する](/articles/tutorials/column-pr-fmt) # ps・top・killの使い方 - Linuxプロセス管理入門 Source: https://penguin-gym-linux.com/articles/tutorials/process-management-basics Linuxを触っていると、次のような場面に必ず出会います。 - コマンドが返ってこない。 - CPU使用率が急に上がった。 - どのプロセスが動いているのか分からない。 この記事では、**プロセス管理の基本操作を「暗記」ではなく「判断できる」状態**になることを目標にします。 **用語の整理**:プロセス(process)とは、いま動いているプログラム1つ分のことです。「タスク」「ジョブ」と呼ばれることもありますが、この記事では「プロセス」で統一します。 ## 先に結論:プロセス管理の判断の型 {#judgment-pattern} ::: tip **プロセスで困ったら、次の順で確認** 1. いま何が動いているかを見ます。 2. 負荷や状態を確認します。 3. 必要な場合だけ、穏やかに止めます。 **いきなり `kill` しないことが、事故を防ぐ一番のポイントです。** ::: ## プロセスとは何か(最低限) {#what-is-process} > **結論**: プロセスは実行中のプログラムそのものだ。PID は実行のたびに変わるので、止める前に必ず確認する。 プロセスとは、**実行中のプログラムそのもの**です。同じコマンドでも、実行されるたびに別のプロセスとして扱われます。 ::: tip **覚えておくべきポイント** - 各プロセスには **PID(プロセスID)** という番号が割り当てられます。 - PIDは実行のたびに変わります。 この「PIDが変わる」という性質を理解していないと、誤った操作につながります。 ::: ## ps:まず全体を把握する {#ps} > **結論**: `ps aux` で PID・CPU・コマンド名を確認する。ここがプロセス管理の出発点になる。 ### 基本形 {#ps-basic} ```bash $ ps aux ``` ```output USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.1 169084 1208 ? Ss 10:00 0:01 /sbin/init user 2345 80.2 5.1 512000 42000 ? R 10:15 2:34 python app.py ``` **`aux` の意味**:`a` は自分以外のユーザーのプロセスも表示します。`u` は実行ユーザー名と CPU・メモリ使用率を並べた形式で表示します。`x` は端末に結びついていないプロセスも表示します。3つ合わせて「動いているものを全部、詳しく表示」になります。この書き方ではハイフンを付けません。 ::: tip **見るポイント** - **PID**:操作の対象になる番号です。 - **%CPU / %MEM**:負荷の目安です。 - **COMMAND**:何が動いているかを表します。 ::: `ps` は状態を表示するだけのコマンドです。プロセスを止めたり変更したりはしません。何度実行しても安全です。 ## top:いま重い原因をリアルタイムで見る {#top} > **結論**: `top` で load average と CPU 占有率をリアルタイムに見る。原因候補を絞ってから判断する。 ### 実行 {#top-run} ```bash $ top ``` ::: tip **よく見る項目** - 画面上部の **load average** を見ます。これは「待たされている処理の多さ」の目安です。 - プロセス一覧の **%CPU** を見ます。 **判断例** - load average が高いときは、CPUが混雑しています。目安は CPU のコア数です。コア数は `nproc` で確認できます。この値を超えていたら混雑と判断します。 - 特定プロセスの %CPU が高いときは、そのプロセスが原因の候補です。 top は **q キーで終了**できます。 ::: ## よくある詰まり①:CPU100%=即 kill してしまう {#pitfall-cpu} > **結論**: CPU100% でも即 `kill -9` は禁物だ。後処理が飛んでデータが壊れることがあるので、まず様子を見る。 ::: warning **よくある誤解** ```bash $ kill -9 2345 ``` この操作には、次の可能性があります。 - 後処理が走りません。 - データ破損や不整合を起こします。 **CPUが高い理由が一時的な処理**であることも多いため、まずは様子を見る判断が重要です。 ::: ## kill:安全な止め方を理解する {#kill} > **結論**: まずは穏やかな `kill PID`(SIGTERM)を送る。応答がないときだけ `kill -9`(SIGKILL)を使う。 ### 基本(穏やかに止める) {#kill-basic} ```bash $ kill 2345 ``` これは **SIGTERM** を送ります。SIGTERM は「終了してください」という依頼の合図です。プロセスは保存などの後処理をしてから終了できます。 ### 強制終了(最終手段) {#kill-force} ```bash $ kill -9 2345 ``` - どうしても止まらない場合だけ使います。 - **常用はしません。** **失敗するとどうなるか**:`kill -9` は SIGKILL を送ります。これはアプリの「終了」ボタンと違い、後処理をする時間を与えません。書きかけのファイルが壊れることがあります。また、PIDを間違えると別のプロセスを止めてしまいます。 **安全に試す方法**:練習では、自分で起動したプロセスだけを対象にしてください。次の3ステップで安全に試せます。 ```bash $ sleep 300 & # 1. 練習用プロセスを起動(& は「画面を占有せず裏で動かす」記号) [1] 3456 # 表示された数字が PID $ ps aux | grep sleep # 2. COMMAND が sleep であることを確認 $ kill 3456 # 3. 自分で起動したプロセスだけを止める ``` `sleep` は指定した秒数だけ待つコマンドです。止めてもシステムには影響しません。本サイトの仮想ターミナルは学習用なので、あなたのパソコンは壊れません。 ## よくある詰まり②:別のプロセスを止めてしまう {#pitfall-wrong-pid} > **結論**: `grep` は自分自身も一覧に出す。PIDの見間違いを防ぐため、必ず COMMAND 名を確認してから止める。 ::: warning **注意が必要なパターン** ```bash $ ps aux | grep python $ kill 1234 ``` - grep 自身が一覧に表示されます。 - そのため PID を見間違えます。 **対策** - COMMAND を必ず確認します。 - PIDだけで判断しません。 ::: ## トラブルシューティング {#troubleshooting} > **結論**: 症状ごとに「原因 → 確認 → 対処」の順で切り分ける。いきなり強制終了に飛ばない。 ### 症状:コマンドが返ってこない {#symptom-no-response} **原因**:実行中のプロセスがまだ処理を続けています。 **確認**:別のターミナルを開き、次のコマンドで状態を見ます。 ```bash $ ps aux | grep コマンド名 ``` **対処**: 1. 実行中の画面で `Ctrl + C` を押して中断します。 2. 効かない場合だけ、別のターミナルから `kill PID` を送ります。 ### 症状:kill しても終了しない {#symptom-kill-fails} **原因**:プロセスが SIGTERM を受け取れない状態にあります。ディスクなどの入出力を待っている場合が代表例です。 **確認**: ```bash $ ps -p 2345 -o pid,stat,comm ``` STAT 列が `D` なら、入出力の待ちで割り込みができない状態です。この状態では SIGKILL でも止まりません。 **対処**:STAT 列の値で分かれます。 1. STAT が `D` 以外(`S` や `R` など)の場合:数十秒待ってから再確認し、それでも残るときだけ `kill -9 2345` を実行します。 2. STAT が `D` の場合:`kill -9` を送っても止まりません。入出力が終わるまで待ちます。数分たっても `D` のままなら、ディスクやネットワークストレージ側の障害を疑います。 ## プロセスが増え続けるときの考え方 {#process-increase} > **結論**: 増え続けるときは、いきなり止めない。自動起動・cron・親プロセスを先に確認する。 ::: tip **確認すべきポイント** - 自動起動していませんか。 - 定期実行(cronなど)ではありませんか。 - 親プロセスは何ですか。 いきなり止めるのではなく、**なぜ増えているか**を考えることで再発を防げます。 ::: ## 実践例:安全に確認して止める手順 {#practice} > **結論**: ps → top → kill の順を守る。状況把握・判断・操作の流れが崩れないので、誤操作を防げる。 ### 推奨手順 {#practice-steps} ```bash $ ps aux # 1. 全体把握 $ top # 2. 負荷確認 $ kill 2345 # 3. 穏やかに終了 ``` **注意**:`2345` はこの記事の例です。そのまま打たないでください。手順1の `ps aux` で表示された、自分が止めたいプロセスの PID に置きかえてください。 ::: tip **この順番を守るだけで防げること** - 誤操作を防げます。 - 不要な強制終了を防げます。 **なぜこの手順が安全なのか** - 状況把握 → 判断 → 操作、の順になります。 - PIDの取り違えを防げます。 - 一時的な負荷を誤って止めにくくなります。 **プロセス管理は「急がない」ことが安全につながります。** ::: ## 作業完了チェックリスト {#checklist} > **結論**: 把握・判断・操作・確認の4段階を順に埋める。埋まっていない段階があれば、そこへ戻る。 **Step 1: 状況把握** - `ps aux` で対象プロセスの PID と COMMAND を確認しました。 - `top` で %CPU と load average を確認しました。 **Step 2: 判断** - 一時的な処理ではないことを確認しました。 - 止めてよいプロセスであることを COMMAND 名で確認しました。 **Step 3: 操作** - まず `kill PID`(SIGTERM)を送りました。 - 数十秒待っても終わらない場合だけ `kill -9 PID` を使いました。 **Step 4: 確認** - 再度 `ps aux` で対象プロセスが消えたことを確認しました。 - ほかのプロセスを止めていないことを確認しました。 ## まとめ {#summary} > **結論**: 場面ごとに使うコマンドは4つだけだ。この対応表を覚えれば判断に迷わない。 | 場面 | コマンド | 目的 | | ------------ | ------------- | ---------------------------------------- | | 全体把握 | `ps aux` | PID と COMMAND を確認する | | 負荷確認 | `top` | load average と %CPU をその場で見る | | 穏やかに終了 | `kill PID` | SIGTERM を送り、後処理をさせて終わらせる | | 最終手段 | `kill -9 PID` | SIGKILL で強制的に終わらせる | ## 次に読む {#next} > **結論**: 次は LPIC-1 学習ハブやプロセス管理の実践編で、kill・top・ps の応用パターンを身につける。 - [LPIC-1 学習ハブ(全12記事と出題範囲マップ)](/lpic1) - [Linuxプロセス管理の実践](/articles/tutorials/process-management-practical) - [プロセス優先度の制御](/articles/lpic/process-priorities-nice) - [chmod・chownの使い方](/articles/tutorials/permissions-basics) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # Linuxプロセス管理の実践 - ジョブコントロールとpkill・nice Source: https://penguin-gym-linux.com/articles/tutorials/process-management-practical 基礎編で学んだ「見る → 確認する → 止める」の判断の型を前提に、この実践編では次を学ぶ。 - **止める以外の選択肢**(バックグラウンド、nice) - **複数プロセスをまとめて操作**(pkill、killall) - **エラーへの対処**(Operation not permitted など) ## 先に結論:実践の判断の型 {#judgment-pattern} ### プロセスが邪魔なとき、まず考える順番 {#judgment-list} プロセスが邪魔なとき、いきなり `kill` に手を伸ばす前に次の順番で考えると良い。 1. **バックグラウンドに逃がせないか?**(端末を使いたいだけなら) 2. **優先度を下げられないか?**(CPUが重いだけなら) 3. **止める必要があるなら、まずTERMから** **「止める」は選択肢の一つに過ぎない。** ## 止めずに逃がす:バックグラウンド実行 {#background} > **結論**: コマンド末尾に&を付けてバックグラウンド実行し、SSH切断後も継続させたい場合はnohupを組み合わせる。 ### 最初からバックグラウンドで起動 {#bg-start} ```bash $ long_command & ``` コマンドの最後に `&` を付けると、バックグラウンドで実行される。 ### SSH切断後も継続させたい場合 {#nohup} ```bash $ nohup long_command & ``` `nohup` を付けると、ログアウト後もプロセスが継続する。標準出力は自動的に `nohup.out` に書き込まれる。出力先を明示したい場合は次のように指定する。 ```bash $ nohup long_command > output.log 2>&1 & ``` ## ジョブコントロール:Ctrl+Z / bg / fg {#job-control} > **結論**: Ctrl+Zで一時停止してbgでバックグラウンドに逃がせば端末を取り戻せ、fgで元のコマンドに戻れる。 「実行中のコマンドを一旦止めて、端末を使いたい」という場面で使う。 ### 基本の流れ {#job-flow} ```bash # 1. 実行中のコマンドを一時停止 Ctrl+Z # 2. バックグラウンドで再開 $ bg # 3. または、フォアグラウンドに戻す $ fg ``` ### 複数ジョブがある場合 {#multiple-jobs} ```bash # ジョブ一覧を確認 $ jobs [1]- Stopped vim file.txt [2]+ Running ./script.sh & # ジョブ番号を指定して操作 $ fg %1 # vim をフォアグラウンドに ``` ### 判断ポイント {#key-decision} - **Ctrl+Z** = 一時停止(プロセスは生きている) - **Ctrl+C** = 終了を試みる(SIGINT送信) 「端末を取り戻したい」だけなら **Ctrl+Z → bg** で止めずに済む。 ## 複数プロセス操作:pkill / killall {#pkill-killall} > **結論**: pkillは部分一致・killallは完全一致で名前からプロセスを終了でき、事前にpgrepで対象を確認する。 同じ名前のプロセスが複数ある場合、PIDを一つずつ指定するのは大変だ。 ### pkill:名前でまとめて操作 {#pkill} ```bash # python という名前のプロセスすべてに SIGTERM $ pkill python # 強制終了(最終手段) $ pkill -9 python ``` ### killall:同様に名前で操作 {#killall} ```bash $ killall python ``` ::: warning **注意:名前の一致範囲** - **pkill**:部分一致(python → python3 も対象) - **killall**:完全一致 意図しないプロセスを止めないよう、**事前に `pgrep` で確認**することを推奨する。 ```bash # 対象を事前確認 $ pgrep -l python 1234 python3 5678 python ``` ::: ## 止めずに落ち着かせる:nice / renice {#nice-renice} > **結論**: nice -n 10で低優先度起動、reniceで実行中のプロセスの優先度を変更し他の処理を優先させられる。 CPUが重いプロセスを「止める」のではなく、**優先度を下げて他の処理を優先させる**選択肢だ。 ### nice:起動時に優先度を指定 {#nice} ```bash # 低優先度で実行(数値が大きいほど低優先度) $ nice -n 10 ./heavy_script.sh ``` ### renice:実行中のプロセスの優先度を変更 {#renice} ```bash # PID 1234 の優先度を下げる $ renice +10 -p 1234 ``` ### nice値の範囲 {#nice-range} | 値 | 意味 | | --- | -------------------------- | | -20 | 最高優先度(root権限必要) | | 0 | デフォルト | | +19 | 最低優先度 | **数値が小さいほど優先度が高い**という点に注意。 nice/renice が有効な場面: - バックアップスクリプトを裏で実行中、他の作業を優先したい - ビルド処理が重いが、止めたくない - 夜間バッチの優先度を最初から低くしておきたい 止めるとやり直しになる処理では、優先度変更が有効だ。 ## エラー対処:Operation not permitted {#errors} > **結論**: Operation not permittedは他ユーザーやrootのプロセスを操作しようとしており、所有者確認後にsudoを使う。 killやpkillで「Operation not permitted」が出る場合の対処。 ### 原因と対策 {#cause-solution} ```bash $ kill 1234 bash: kill: (1234) - Operation not permitted ``` ### このエラーが出る主な原因 {#error-causes} 1. **他のユーザーのプロセス**を止めようとしている 2. **システムプロセス**(rootが起動)を止めようとしている ### 対処法 {#solution} ```bash # まず、プロセスの所有者を確認 $ ps aux | grep 1234 # 自分のプロセスでなければ sudo が必要 $ sudo kill 1234 ``` ::: warning **sudo を使う前に** root権限で止めるということは、**システムに影響を与える可能性がある**ということだ。「なぜ自分のプロセスじゃないのか」を確認してから実行すること。 ::: ## 実務シナリオ:よくある場面と対応 {#scenarios} > **結論**: vim一時停止・Python暴走・ビルド優先度低下・SSH切断継続の4シナリオで実際の対処手順を確認できる。 ### シナリオ1:vimを開いたまま端末を使いたい {#scenario1} ```bash Ctrl+Z # vim を一時停止 $ bg # バックグラウンドへ(vim は停止状態のまま) $ fg # 作業後、vim に戻る ``` ### シナリオ2:pythonプロセスが複数暴走 {#scenario2} ```bash # 1. 対象を確認 $ pgrep -l python 1234 python3 5678 python3 # 2. まとめて SIGTERM $ pkill python # 3. 数秒待ってから確認し、残っていれば強制終了 $ pgrep python && pkill -9 python ``` ::: warning `pkill -9` は即座にプロセスを強制終了し、データ保存・クリーンアップ処理を行う機会を与えない。SIGTERM(デフォルト)を送った後は数秒待ち、プロセスが残っている場合にのみ使用すること。 ::: ### シナリオ3:ビルドが重いが止めたくない {#scenario3} ```bash # ビルドプロセスのPIDを確認 $ pgrep -l make 1234 make # 優先度を下げる $ renice +15 -p 1234 ``` ### シナリオ4:SSH切断後も実行を続けたい {#scenario4} ```bash # 最初から nohup で起動 $ nohup ./long_script.sh > log.txt 2>&1 & # または、実行中のジョブを切り離す $ disown %1 ``` ## まとめ {#summary} ### プロセスが邪魔なとき {#summary-list} 1. 端末を使いたいだけ → **Ctrl+Z → bg** 2. CPUが重いだけ → **renice で優先度を下げる** 3. 止める必要がある → **kill(SIGTERM)** 4. それでも止まらない → **kill -9(最終手段)** **「止める」は最後の選択肢だ。** ## 次に読む {#next} - [LPIC-1 学習ハブ(全12記事と出題範囲マップ)](/lpic1) - [ps・top・killの使い方](/articles/tutorials/process-management-basics) - [プロセス優先度の制御](/articles/lpic/process-priorities-nice) - [シェル環境変数の設定](/articles/lpic/shell-environment) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # pv 入門 - パイプの進捗をプログレスバーで見る Source: https://penguin-gym-linux.com/articles/tutorials/pv-progress-viewer ## pv とは何か? {#intro} > **結論**: `pv`(Pipe Viewer)はパイプを流れるデータ量を計測し、プログレスバー・経過時間・転送速度・ETA をリアルタイム表示するツール。`dd` や `cp` で「進んでいるのか分からない」不安を解消する。 `dd`・`gzip`・`tar`・`mysql` などにデータを流し込むと、完了まで何の出力もなく無言で固まったように見える。`pv` はパイプの途中に挟むだけで、いま何バイト処理し、残り何秒で終わるかを可視化する。 ```bash $ pv bigfile.iso | dd of=/dev/sdb 1.2GiB 0:00:42 [29.0MiB/s] [=========> ] 48% ETA 0:00:45 ``` 表示される 6 項目はそれぞれ次を意味する。 - `1.2GiB`: ここまでに転送したバイト数 - `0:00:42`: 経過時間 - `[29.0MiB/s]`: 現在の転送速度 - `[====>]`: プログレスバー - `48%`: 進捗率 - `ETA 0:00:45`: 完了までの予測時間 ::: tip **この記事で身につくこと** - パイプの進捗をプログレスバーで見る基本形 - サイズ不明なパイプでも ETA を出す `-s` の使い方 - 速度制限・行数計測・実行中プロセス監視といった実務テクニック ::: ## pv はどうインストールするのか? {#install} > **結論**: `pv` は標準では入っていないことが多い。Debian/Ubuntu は `apt`、RHEL 系は `dnf`、macOS は `brew` で導入する。 ```bash # Debian / Ubuntu $ sudo apt install pv # RHEL / Rocky / AlmaLinux / Fedora $ sudo dnf install pv # macOS (Homebrew) $ brew install pv ``` 導入確認: ```bash $ pv --version pv 1.6.20 ``` ::: warning 多くの最小構成サーバーには `pv` が入っていない。`command not found` が出たら上記でインストールする。本番サーバーに勝手に追加できない場合は、後述の `-d`(実行中プロセス監視)で代替できることがある。 ::: ## 基本的な使い方は? {#basic} > **結論**: 引数にファイルを渡すと `pv` がサイズを自動把握し、進捗率と ETA まで表示する。パイプの「途中」に置くのが基本の型。 ### ファイルを渡す(サイズ自動検出) ファイル名を直接渡すと、`pv` はそのサイズを `stat` で取得するため、進捗率と ETA が出る。 ```bash $ pv access.log | grep "404" > errors.txt ``` `cat` の代わりに `pv` を使う、と覚えればよい。 ### パイプの途中に挟む すでにパイプがある場合は、計測したい箇所に挟み込む。 ```bash $ gzip -dc backup.sql.gz | pv | mysql mydb ``` この場合 `pv` は標準入力から読むためサイズが分からず、進捗率と ETA は出ない(転送量・経過時間・速度のみ表示)。ETA まで出したいときは次の `-s` を使う。 ## サイズが分からないパイプで ETA を出すには? {#size} > **結論**: 標準入力経由でサイズ不明なときは `-s` で想定サイズを明示する。`du` や圧縮前サイズと組み合わせると ETA が出せる。 `-s`(`--size`)に総バイト数を渡すと、`pv` はそれを 100% として進捗率と ETA を計算する。`k`/`m`/`g` の接尾辞が使える。 ```bash $ pv -s 2g backup.sql.gz | gunzip | mysql mydb ``` ディレクトリを `tar` で固める場合は、`du` で実サイズを求めて渡すのが定番。 ```bash $ tar -cf - mydir | pv -s "$(du -sb mydir | cut -f1)" > mydir.tar ``` ::: tip `du -sb` はバイト単位の合計サイズを返す(`-b` = bytes)。`cut -f1` でサイズ列だけ取り出して `-s` に渡している。圧縮するとサイズは縮むため、`gzip` を通す場合 ETA は目安として捉える。 ::: ## 表示項目をカスタマイズするには? {#format} > **結論**: 表示要素はフラグで選べる。`-p` 進捗バー / `-t` 経過時間 / `-e` ETA / `-r` 速度 / `-b` バイト数 / `-N` 名前付け。何も付けなければ全部入り。 オプションを 1 つでも指定すると、指定した要素だけが表示される。 | フラグ | 表示内容 | | ------ | --------------- | | `-p` | プログレスバー | | `-t` | 経過時間 | | `-e` | ETA(残り時間) | | `-r` | 現在の転送速度 | | `-a` | 平均転送速度 | | `-b` | 転送バイト数 | | `-N` | 名前ラベル付与 | 複数の `pv` を 1 行に並べるときは `-N` で名前を付けると区別しやすい。 ```bash $ pv -N "読み込み" -cb source.dat | gzip | pv -N "圧縮後" -cb > out.gz ``` `-n`(`--numeric`)を使うと進捗率を「数値のみ」で出力するため、シェルスクリプトや `dialog --gauge` への入力に向く。 ```bash $ pv -n bigfile 2>&1 >/dev/null | while read p; do echo "進捗 $p%"; done ``` ## 転送速度を制限するには? {#ratelimit} > **結論**: `-L`(`--rate-limit`)で帯域を上限指定できる。本番ディスクやネットワークへの負荷を抑えたいコピーに有効。 ```bash $ pv -L 10m bigfile.iso > /mnt/backup/bigfile.iso ``` この例では転送速度を毎秒 10MiB に制限する。バックアップ中にディスク I/O や回線を食いつぶしてサービスへ影響を与えたくない場面で使う。 ::: warning `-L` の値は 1 秒あたりのバイト数。`10m` は 10MiB/s であり 10Mbps(ビット毎秒)ではない。ネットワーク帯域の単位と混同しやすいので注意。 ::: ## 行数ベースで進捗を見るには? {#lines} > **結論**: `-l`(`--line-mode`)でバイト数ではなく行数をカウントする。ログ処理やレコード件数の進捗把握に向く。 ```bash $ pv -l -s 1000000 access.log | grep "POST" > posts.log ``` `-s` に総行数を渡せば「100 万行中いま何行目か」を進捗率で表示できる。行数が事前に分かるバッチ処理やインポートで便利。 ## 複数段のパイプを個別に監視するには? {#multi} > **結論**: パイプの複数箇所に `pv` を置く場合は `-c`(`--cursor`)を併用する。`-c` がないと表示が衝突して崩れる。 ```bash $ pv -cN "入力" raw.dat | gzip | pv -cN "出力" > raw.gz ``` `-c` は端末のカーソル位置を制御し、複数の `pv` が別々の行に進捗を描くようにする。`-N` の名前ラベルと組み合わせて、どの段の進捗かを明示する。 ## 実行中のプロセスの進捗を見るには? {#watchfd} > **結論**: すでに走っている `cp` や `dd` の進捗は `pv -d PID` で後付け監視できる。`pv` を最初から挟み忘れたときの救済策。 `-d`(`--watchfd`)に PID を渡すと、そのプロセスが開いている全ファイルディスクリプタの読み書き進捗を表示する。 ```bash # 別端末で動いている dd の PID を調べる $ pgrep -a dd 4821 dd if=/dev/sda of=backup.img bs=1M # その進捗を後から覗く $ pv -d 4821 ``` 特定のファイルディスクリプタだけを見たい場合は `PID:FD` 形式で指定する。 ```bash $ pv -d 4821:1 ``` ::: tip `pv -d` は対象プロセスの `/proc//fd` と `/proc//fdinfo` を読んで進捗を推定する。Linux 限定の機能で、長時間の `cp`・`dd`・`gzip` を「挟み忘れた」ときの定番リカバリ手段。 ::: ## よくあるトラブルと対処は? {#troubleshooting} > **結論**: 進捗率が出ない・ETA が表示されないの大半は「サイズ不明」が原因。`-s` で補うか、ファイル引数を直接渡す形に変える。 ### 進捗率(%)と ETA が出ない 標準入力から読んでいてサイズが分からないため。ファイルを直接 `pv file` で渡すか、`-s` で想定サイズを与える。 ### `command not found: pv` 未インストール。[インストール手順](#install)を参照。本番に追加できない場合は `pv -d PID` での外部監視を検討する。 ### バーが一瞬で 100% になる データ量が小さいか、`-s` に指定したサイズが実データより小さい。`-s` の値が正しいか確認する。 ### `bash: /usr/bin/pv: Argument list too long` これは `pv` 自体ではなくシェルの展開上限。`pv` でファイルを大量展開せず、`find ... | pv -l | xargs` のように行ストリームで渡す。 ## まとめ:次に読む {#next} `pv` はパイプに 1 つ挟むだけで、無言だった処理が「あと何秒で終わるか」を語り出す。`dd` でのイメージ書き込み、`tar`/`gzip` でのバックアップ、`mysql` へのインポートなど、長時間ジョブの体感が一変する。まずは `cat` を `pv` に置き換えるところから始めるとよい。 - [dd でディスクイメージを扱う](/articles/tutorials/dd-command-basics) - [パイプとリダイレクトの基礎](/articles/tutorials/pipe-redirect-basics) - [watch でコマンド出力を定点観測する](/articles/tutorials/watch-command-basics) # read コマンド入門 - シェルスクリプトで入力を受け取る Source: https://penguin-gym-linux.com/articles/tutorials/read-command-input 「スクリプトの途中で、ユーザーに名前やパスワードを入力してもらいたい」——そんなときに使うのが `read` コマンドです。この記事では、ライニー先輩とリナの会話を通じて、`read` でキーボード入力やファイルを受け取る方法を一緒に学んでいきましょう。 ## この記事でわかること {#what-you-will-learn} - `read` コマンドが何をするコマンドなのかがわかる - プロンプト表示(`-p`)・パスワード非表示(`-s`)・タイムアウト(`-t`)の使い方を習得できる - `while read` でファイルを1行ずつ処理する定番パターンが書ける - 初心者がハマりやすい「パイプの落とし穴」「`-r` を付ける理由」を理解できる ## 1. read コマンドって何? {#intro} > **結論**: read は標準入力から1行を読み取って変数に入れる組み込みコマンドで、スクリプトをユーザーと対話させるための入り口になる。 :::dialogue @lina: ライニー先輩、シェルスクリプトを書いてたら「ここでユーザーに入力してもらいたい」って場面が出てきたんです。こういうのってどうやるんですか? @linny: それなら `read` コマンドの出番だよ。`read` は**キーボードから打たれた1行を読み取って、変数に入れてくれる**コマンドなんだ。 ::: :::tip **read コマンドとは** `read` は、**標準入力(キーボードなど)から1行を読み取り、変数に格納する**シェルの組み込みコマンドです。ユーザーに何かを入力してもらい、その内容をスクリプトの中で使いたいときに利用します。 ::: :::dialogue @lina: 「組み込みコマンド」って、`ls` とか `cat` とは違うんですか? @linny: いい質問だね。`ls` や `cat` は `/bin/ls` のように**実行ファイルが存在する**コマンドなんだけど、`read` は**シェル自身が持っている機能**なんだ。だから `which read` で探しても出てこないことが多いよ。`type read` で確認してみると `read is a shell builtin` って表示されるんだ。 ::: ## 2. いちばん基本の使い方 {#basic} > **結論**: read 変数名 と書くと入力待ちになり、Enter を押すまでの1行がその変数に入る。 :::dialogue @lina: さっそく使ってみたいです。いちばんシンプルな形を教えてください。 @linny: じゃあ、こう打ってみて。Enter を押すとカーソルが止まって入力待ちになるよ。 ::: ```bash read name ``` :::dialogue @lina: 打ったら、画面が止まりました。ここで `リナ` って入力して Enter を押すと...? @linny: その「リナ」という文字列が `name` という変数に入るんだ。中身を確認してみよう。 ::: ```bash read name リナ echo "こんにちは、$name さん" ``` ``` こんにちは、リナ さん ``` :::tip **変数の中身を見るには `$` を付ける** `read name` で入れた値は、`$name` のように**頭に `$`** を付けて取り出します。`echo "$name"` のように**ダブルクォートで囲む**のが安全な書き方です(値に空白が含まれても正しく扱えます)。 ::: ## 3. プロンプトを表示する(-p) {#prompt} > **結論**: read -p "メッセージ" 変数名 と書くと、入力欄の前に案内文を表示できる。 :::dialogue @lina: でも、ただカーソルが止まるだけだと、何を入力すればいいのか分からないですよね? @linny: そのとおり。だから普通は `-p` オプションで**入力を促すメッセージ(プロンプト)**を一緒に表示するんだ。 ::: ```bash read -p "お名前を入力してください: " name echo "ようこそ、$name さん" ``` ``` お名前を入力してください: リナ ようこそ、リナ さん ``` :::dialogue @lina: なるほど! これなら何を入力すればいいか一目で分かりますね。 @linny: そう。`-p` の後ろに書いた文字列がそのまま表示されるよ。`: ` のように最後に空白を入れておくと、入力位置が見やすくなるからおすすめだよ。 ::: ## 4. 複数の値を一度に受け取る {#multiple} > **結論**: read に変数名を複数並べると、空白で区切られた入力をそれぞれの変数に振り分けられる。 :::dialogue @lina: 名前と年齢みたいに、いくつかまとめて入力してもらうことはできますか? @linny: できるよ。`read` の後ろに**変数名を空白で区切って並べる**んだ。入力も空白で区切ると、それぞれに振り分けられるよ。 ::: ```bash read -p "姓 名 を入力: " sei mei echo "姓: $sei / 名: $mei" ``` ``` 姓 名 を入力: 山田 リナ 姓: 山田 / 名: リナ ``` :::warning **変数より入力が多いと、最後の変数にまとめて入る** `read a b` のように変数が2つなのに `1 2 3 4` と入力すると、`a` には `1`、`b` には残り全部(`2 3 4`)が入ります。逆に入力が少ないと、余った変数は空のままになります。 ::: ## 5. パスワードを隠して入力する(-s) {#secret} > **結論**: read -s は入力した文字を画面に表示しないので、パスワードなど見られたくない入力に使う。 :::dialogue @lina: パスワードを入力してもらうとき、画面に丸見えだと困りますよね...? @linny: そこで `-s`(silent)オプションだよ。**入力した文字が画面に表示されなくなる**んだ。パスワードや秘密の値を受け取るときに使うよ。 ::: ```bash read -s -p "パスワード: " pass echo echo "入力された文字数: ${#pass}" ``` ``` パスワード: 入力された文字数: 8 ``` :::dialogue @lina: 入力しても画面に何も出ませんでした。でも、ちゃんと受け取れてるんですね。 @linny: そう。`-s` は文字を表示しないだけで、中身はちゃんと変数に入っているんだ。`-s` を使うと Enter を押しても改行が表示されないから、直後に `echo` で改行を1つ入れておくと見た目がきれいになるよ。`${#pass}` は**変数の文字数**を表す書き方だよ。 ::: ## 6. 入力を時間制限つきにする(-t) {#timeout} > **結論**: read -t 秒数 を使うと、指定した秒数以内に入力がないと read が自動で終了する。 :::dialogue @lina: もし誰も入力しなかったら、スクリプトはずっと止まったままになっちゃいますよね? @linny: それを防ぐのが `-t`(timeout)だよ。**指定した秒数だけ待って、入力がなければ次に進む**んだ。 ::: ```bash if read -t 5 -p "5秒以内に入力してね: " answer; then echo "入力されました: $answer" else echo echo "時間切れです" fi ``` :::tip **read の成功・失敗を if で判定できる** `read` は、正常に1行読めれば「成功」、タイムアウトや入力の終わり(後述)になると「失敗」を返します。これを `if read ...; then` の形で受け取ると、入力があったかどうかで処理を分けられます。 ::: ## 7. ファイルを1行ずつ読む(while read) {#while-read} > **結論**: while read line; do ... done < ファイル で、ファイルを1行ずつ変数に取り込んで処理できる。 :::dialogue @lina: キーボードだけじゃなくて、ファイルの中身を1行ずつ処理したいときもありますよね? @linny: それには `while read` の組み合わせがいちばんだよ。これは**シェルスクリプトの超定番パターン**だから、形ごと覚えておくと一生使えるよ。 ::: ```bash while read line; do echo "読んだ行: $line" done < list.txt ``` :::dialogue @lina: `< list.txt` の部分は何ですか? @linny: これは**リダイレクト**といって、「`list.txt` の中身を `read` に流し込んでね」という意味だよ。`read` は1行読むたびに `line` に入れて、行がなくなるまでループを繰り返すんだ。ファイルの最後まで読むと `read` が「失敗」を返すから、自然にループが終わるんだよ。 ::: :::tip **より安全な型: while IFS= read -r line** 実務では `while IFS= read -r line; do ... done < file` という形をよく見ます。`IFS=` は行頭・行末の空白が削られるのを防ぎ、`-r` はバックスラッシュ(`\`)をそのまま読み込むための指定です。理由は次の章で説明します。 ::: ## 8. 初心者がハマる2つの落とし穴 {#pitfalls} > **結論**: パイプで渡した while read は別プロセスで動くため変数が消える点と、-r なしだとバックスラッシュが消える点に注意する。 :::dialogue @lina: 先輩、`while read` を使ったら、ループの中で数えた数がループの後で 0 に戻ってて...バグですか? @linny: それは「あるある」だね。バグじゃなくて**パイプの仕組み**が原因なんだ。順番に見ていこう。 ::: ### 落とし穴1: パイプの中の変数は外に残らない ```bash count=0 cat list.txt | while read line; do count=$((count + 1)) done echo "$count" # ← 0 になってしまう ``` :::dialogue @lina: なんで 0 なんですか? ちゃんと数えてるはずなのに... @linny: `cat ... | while ...` のようにパイプでつなぐと、`while` の部分が**別のプロセス(サブシェル)**で動くんだ。その中で `count` を増やしても、それは「分身」の中だけの話。元の `count` には反映されないんだよ。 ::: :::tip **解決策: パイプではなくリダイレクトを使う** ```bash count=0 while read line; do count=$((count + 1)) done < list.txt echo "$count" # ← 正しく数えられる ``` `< list.txt` のリダイレクトなら、`while` は同じプロセスの中で動くので、`count` の変更がちゃんと残ります。 ::: ### 落とし穴2: -r を付けないとバックスラッシュが消える :::dialogue @lina: もう1つの `-r` って、なんで付けるんですか? @linny: `-r` を付けないと、`read` は**バックスラッシュ(`\`)を特別扱い**して消してしまうんだ。例えばファイルのパス `C:\Users` を読むと `C:Users` になっちゃう。だから「入力された文字をそのまま読みたい」ときは `-r` を付けるのが鉄則だよ。 ::: ```bash # -r なし: \ が消えてしまう read path <<< 'C:\Users\rina'; echo "$path" # → C:Usersrina # -r あり: そのまま読める read -r path <<< 'C:\Users\rina'; echo "$path" # → C:\Users\rina ``` :::warning **迷ったら `-r` を付ける** 特別な理由がない限り、`read` には `-r` を付けるのが安全です。シェルスクリプトのチェックツール(ShellCheck など)も、`-r` なしの `read` を警告します。 ::: ## 9. ミニ課題で復習しよう {#exercise} :::dialogue @lina: いろいろ学んだので、自分でも書いてみたいです! @linny: いいね。じゃあ簡単な課題を出すよ。**名前を尋ねて、あいさつを返すスクリプト**を書いてみよう。 ::: :::details 課題: あいさつスクリプトを書いてみよう(クリックで解答例) **お題**: 「お名前は?」と尋ね、入力された名前を使って「こんにちは、〇〇さん!」と表示するスクリプトを書いてください。 **解答例**: ```bash #!/bin/bash read -p "お名前は? " name echo "こんにちは、${name}さん!" ``` `-p` でプロンプトを出し、`$name` で受け取った値を表示しています。`${name}` のように `{}` で囲むと、変数名の区切りがはっきりして安全です。 ::: :::dialogue @lina: できました! `read -p` でプロンプトを出して、`$name` で表示するんですね。 @linny: その調子だよ。`read` は短いコマンドだけど、`-p`・`-s`・`-t`・`while read` の4つを押さえれば、たいていの「入力を受け取る場面」に対応できる。あとは実際のスクリプトでどんどん使って慣れていこう。 ::: ## まとめ {#summary} :::tip **read コマンドの要点** - `read 変数名` で標準入力の1行を変数に格納する - `-p "メッセージ"` で入力を促すプロンプトを表示 - `-s` で入力を画面に表示しない(パスワード向け) - `-t 秒数` で入力待ちにタイムアウトを設定 - `while read line; do ... done < file` でファイルを1行ずつ処理 - パイプではなくリダイレクト(`< file`)を使い、`-r` を付けるのが安全な型 ::: `read` をマスターすると、スクリプトが「自動で流れるだけのもの」から「ユーザーと対話できるもの」に進化します。次は[シェルスクリプト入門](/articles/tutorials/shell-scripting-basics)で、受け取った入力を条件分岐やループと組み合わせる方法を学んでみましょう。 # realpath / readlink 入門 - 絶対パスとシンボリックリンクを解決する Source: https://penguin-gym-linux.com/articles/tutorials/realpath-readlink ## この記事で分かること {#intro} - `realpath` と `readlink` の **違いと使い分け** が分かる - シンボリックリンクや `..` を含むパスを **絶対パスに解決** できる - `-f` / `-e` / `-m` の **存在チェック 3 段階** を理解できる - スクリプトで **事故らないパス解決** が書けるようになる ::: tip **結論(先に要点)** - **パスを絶対パスにしたい** → `realpath path` - **シンボリックリンクの「中身」を見たい** → `readlink link` - **リンクも `..` も全部たどって 1 本の実体パスにしたい** → `realpath path` または `readlink -f path` - スクリプトで使うなら `readlink`(素)ではなく `realpath` か `readlink -f` を使う ::: ::: warning **前提(対象環境)** - GNU coreutils(Ubuntu / Debian / RHEL 系など一般的な Linux) - `realpath` は coreutils 8.15 以降で標準同梱 - 本記事の挙動は coreutils 9.4 で確認 ::: ## realpath と readlink の違いは? {#diff} > **結論**: `readlink` はシンボリックリンクの「中身(リンク先文字列)」を読むコマンド。`realpath` はパス全体を実体の絶対パスに解決するコマンド。`readlink -f` を付けると realpath とほぼ同じ働きになる。 両者は名前も用途も近いが、**素の状態での役割が違う**。 | 観点 | `readlink`(オプションなし) | `realpath`(オプションなし) | | ---------------------------- | ---------------------------- | ---------------------------- | | 主目的 | リンク先の文字列を読む | 実体の絶対パスを求める | | 対象がシンボリックリンク以外 | **何も出力せず失敗** | そのまま絶対パスを出力 | | 相対リンク先 | 格納された相対文字列のまま | 解決して絶対パスにする | | `..` の解決 | しない | する | ポイントは **「素の `readlink` は symlink 専用」** という点。通常ファイルやディレクトリに対して素の `readlink` を使うと無言で失敗する(後述の落とし穴)。一方 `realpath` はどんなパスでも絶対パスを返す。 ## readlink の基本的な使い方は? {#readlink} > **結論**: 引数なしの `readlink` はリンク先文字列をそのまま表示する。`-f` を付けるとリンクを再帰的にたどり、最終的な絶対パスを返す。 ### リンクの「中身」を表示する(オプションなし) `readlink` は、シンボリックリンクに**格納されている文字列**をそのまま表示する。多くのシステムで `/bin` は `usr/bin` への相対リンクになっている。 ```bash $ readlink /bin ``` ```output usr/bin ``` 格納値が相対パス(`usr/bin`)なので、出力も相対のまま。`..` も解決されない。「リンクが何を指しているか」を確認したいときに使う。 ### -f / -e / -m で完全に解決する `-f`(`--canonicalize`)を付けると、途中のシンボリックリンクをすべてたどり、`..` も処理した**絶対パス**を返す。 ```bash $ readlink -f /bin ``` ```output /usr/bin ``` `-f` 系には存在チェックの強さが異なる 3 つのモードがある。 | オプション | 別名 | 存在要件 | | ---------- | ------------------------- | ---------------------------------------- | | `-f` | `--canonicalize` | **最後の要素以外**が存在すればよい | | `-e` | `--canonicalize-existing` | **すべての要素**が存在しなければならない | | `-m` | `--canonicalize-missing` | **存在不問**(全部なくてもよい) | ```bash # 親は存在し、最後のファイルだけ無い → -f は成功、-e は失敗 $ readlink -f /etc/does-not-exist.conf /etc/does-not-exist.conf $ readlink -e /etc/does-not-exist.conf # 何も出力せず exit 1 ``` 「これから作るファイルの最終パスを先に求めたい」なら `-f` か `-m`、「実在を保証したい」なら `-e` を使う。 ::: warning `readlink` でリンク先を表示したいとき、`-n`(`--no-newline`)を付けると末尾の改行を抑制できる。コマンド置換 `$(...)` は末尾改行を自動で除くため通常は不要だが、`printf` で連結するときなどに役立つ。 ::: ## realpath はどう使うのか? {#realpath} > **結論**: `realpath path` はシンボリックリンク・`..`・`.` をすべて解決した絶対パスを返す。デフォルトは「最後の要素以外が存在すればよい」挙動で、`readlink -f` と一致する。 ### 基本: リンクと相対パスを 1 本の実体パスにする ```bash $ realpath /bin ``` ```output /usr/bin ``` シンボリックリンクでないパスでもそのまま絶対パス化できるのが `readlink` との大きな違い。 ```bash $ cd /var/log $ realpath ../tmp ``` ```output /var/tmp ``` `..` や `.`、重複スラッシュも正規化される。 ### 存在チェックを制御する(-e / -m) `realpath` の存在チェックは `readlink` と同じ 3 段階。 - **デフォルト**: 最後の要素以外が存在すればよい(親までは実在が必要) - `-e`(`--canonicalize-existing`): すべての要素が存在しなければならない - `-m`(`--canonicalize-missing`): どの要素も存在しなくてよい ```bash # 親ディレクトリが存在しない → デフォルトでも失敗 $ realpath /no-such-dir/file.txt realpath: /no-such-dir/file.txt: No such file or directory # -m なら存在しないパスでも正規化して返す $ realpath -m /no-such-dir/file.txt /no-such-dir/file.txt ``` ### --relative-to で相対パスに変換する `realpath` は絶対パス化だけでなく、**基準ディレクトリからの相対パス**も計算できる。 ```bash $ realpath --relative-to=/home /home/alice/work/report.txt ``` ```output alice/work/report.txt ``` スクリプトで「設定ディレクトリからの相対位置」を出したいときなどに便利。`--relative-base=DIR` を使うと、対象が `DIR` 配下のときだけ相対化し、外なら絶対パスのままにできる。 ### -s でリンクを展開しない `-s`(`--strip` / `--no-symlinks`)を付けると、シンボリックリンクを**解決せず**に `.` や `..` だけを正規化する。リンク構造を保ったままパスを整えたいときに使う。 ```bash $ realpath -s /bin/../bin ``` ```output /bin ``` ## よくある落とし穴は? {#pitfalls} > **結論**: 最大の罠は「素の `readlink` は通常ファイルに対して無言で失敗する」こと。スクリプトでパス解決するなら `realpath` か `readlink -f` を使う。 ### 1. 通常ファイルに素の readlink を使うと空になる `readlink`(オプションなし)はシンボリックリンク以外に対して**何も出力せず exit 1** で終わる。 ```bash $ readlink /etc/hostname # 出力なし、exit 1(/etc/hostname は通常ファイル) ``` スクリプトでこう書くと、対象が symlink でないときに変数が空になり、思わぬ事故につながる。 ```bash # 危険: $f が symlink でないと p が空になる p=$(readlink "$f") # 安全: symlink でも通常ファイルでも絶対パスが入る p=$(realpath "$f") ``` ### 2. -f と -e の取り違え 「まだ存在しない出力先パスを解決したい」のに `-e` を使うと失敗する。これから作るパスは `-f`(最後の要素は無くてよい)か `-m`(全部無くてよい)を選ぶ。 ### 3. 末尾スラッシュとシンボリックリンク シンボリックリンクの末尾に `/` を付けると、リンク自身ではなく**リンク先のディレクトリ**を指す。意図せず解決結果が変わることがあるため、リンクそのものを調べたいときは末尾 `/` を付けない。 ::: tip **使い分けの目安** - リンクが指す「文字列」を 1 段だけ見たい → `readlink link` - 実体の絶対パスが欲しい(スクリプト含む)→ `realpath path` か `readlink -f path` - 実在を保証したい → `-e` - これから作るパスを先に求めたい → `-f` か `-m` ::: ## スクリプトでの実践例 {#script} > **結論**: スクリプト冒頭で自分自身の設置ディレクトリを求める定番イディオムが `realpath` / `readlink -f` の典型用途。シンボリックリンク経由で起動されても実体ディレクトリを取得できる。 シェルスクリプトが「自分の置かれたディレクトリ」を基準に他ファイルを参照したい場面は多い。`realpath` を使うと symlink 経由で呼ばれても実体の場所を解決できる。 ```bash #!/bin/bash # スクリプト自身の実体ディレクトリを取得 script_path=$(realpath "$0") script_dir=$(dirname "$script_path") echo "実行中のスクリプト: $script_path" echo "設置ディレクトリ: $script_dir" # 同じディレクトリの設定ファイルを安全に参照 config="$script_dir/config.env" ``` `realpath` が無い最小環境向けには `readlink -f "$0"` で代替できる。両者はデフォルトで同じ「最後の要素以外は存在必須」の挙動になる。 ::: warning macOS など BSD 系の `readlink` には `-f` が無い(または挙動が異なる)。GNU coreutils を前提にできない可搬スクリプトでは、`realpath` の有無を確認するか `cd "$(dirname "$0")" && pwd` 系のイディオムを検討する。 ::: ## 次に読む {#next} - [シンボリックリンク入門](/articles/tutorials/symbolic-links) - [stat でファイル情報を調べる](/articles/tutorials/stat-file-inspection) - [cannot stat No such file の対処](/articles/troubleshooting/cannot-stat-no-such-file) # rename コマンド入門 - 複数ファイルを一括リネームする Source: https://penguin-gym-linux.com/articles/tutorials/rename-command ## この記事で学べること {#intro} - `rename` で **複数ファイルの名前を一気に変える** 方法が分かります - 実行前に `-n` で **「何が起きるか」を必ず確認** する安全な型が身につきます - `s/古い/新しい/` の **置換パターン** で拡張子や文字列を一括変換できます - 名前が似ている **2 種類の rename(Perl 版と util-linux 版)の違い** を見分けられます ::: tip **言葉の整理** - **リネーム**:ファイルの名前を変えることです。「名前の変更」と同じ意味です。 - **拡張子**:ファイル名の末尾にある `.jpg` や `.txt` の部分です。 - **パターン**:「こういう形の文字を探す」という書き方のことです。この記事では `s/古い/新しい/` の形を使います。 - **dry-run(ドライラン)**:実際には実行せず、予定だけを表示することです。この記事では `-n` がそれにあたります。 - **クォート**:`'` で文字列を囲むことです。囲むと、記号がそのままの文字として渡ります。 - **カレントディレクトリ**:いま自分がいるディレクトリのことです。「現在地」と読み替えて構いません。 `rename` はファイルの名前を書きかえるコマンドです。だからこそ、最初に `-n` を覚えます。`-n` を付けている間は 1 個も変わりません。 ::: ::: tip **結論(先に覚えるべき型)** - まず確認 → `rename -n 's/古い/新しい/' *`(実行せず予定だけ表示) - 問題なければ実行 → `rename 's/古い/新しい/' *` - 大文字を小文字に → `rename 'y/A-Z/a-z/' *` - どっちの rename か迷ったら → `rename --version` で確認 ::: ::: warning **前提(対象環境)** - OS:Ubuntu / Debian です。既定の `rename` は **Perl 版** です - 他のディストリビューション(Fedora 等)では **util-linux 版** のことがあります。書き方が違います(第 6 章で解説します) ::: ::: warning **安全に試すための 3 つの型** `rename` を間違えると、複数のファイル名が一度に変わります。元の名前は自動では戻りません。次の 3 つを守れば安全に練習できます。 1. 練習用のディレクトリを作り、その中だけで試す(例:`mkdir -p ~/rename-practice && cd ~/rename-practice`) 2. `touch` で作った空ファイルなど、消えて困らないファイルで試す 3. 本実行の前に必ず `-n` を付けて、変更の予定を目で確かめる なお `rename` は本サイトの仮想ターミナルには入っていません。実機の Linux か WSL で試してください。だからこそ、上の 3 つを守ってください。 ::: ## 1. rename コマンドとは何か? {#what} > **結論**: `rename` は複数ファイルの名前を、パターンを使って一括で変換するコマンド。1個ずつ `mv` する手間を消せる。 ::: dialogue @lina: 先輩、`photo1.jpeg` や `photo2.jpeg` のようなファイルが 50 個あります。全部の拡張子を `.jpg` に直したいです。 @lina: `mv` で 1 個ずつ直すしかないですか? @linny: 50 個を手でやるのは大変だよね。そういうときに使うのが `rename` なんだ。 @linny: `mv` は「1 個の名前を変える道具」だね。`rename` は「たくさんの名前を一括で変える道具」だよ。 @lina: 50 回 `mv` を打たなくていいんですか? @linny: そう。1 回のコマンドで 50 個まとめて変えられる。 @linny: しかも `.jpeg` の部分だけを `.jpg` に置き換えるような **パターン指定** ができる。これが強みなんだ。 ::: ::: highlight **rename の基本イメージ** ``` rename 's/jpeg/jpg/' *.jpeg ↑ ↑ | 対象ファイル(ここでは .jpeg 全部) 変換ルール(jpeg を jpg に置き換える) ``` やっていることは 1 つです。「ファイル名の中の `jpeg` を見つけて `jpg` に書きかえる」だけです。これを対象ファイル全部に対して一気に行います。 ::: ## 2. なぜ rename を使うのか? {#why} > **結論**: `mv` は1個ずつの変更には向くが、数十個を同じルールで変えるには非効率。rename ならパターンを1回書くだけで済む。 ::: dialogue @lina: そもそも `mv` でも名前は変えられますよね。なぜ `rename` を覚えるんですか? @linny: いい疑問だね。`mv old.txt new.txt` のように **1 個の名前を変える** なら `mv` で十分なんだ。 @linny: 問題は「100 個のファイルを同じルールで変えたい」ときだね。`mv` だと 100 回打つことになる。 @lina: たしかに、それは現実的ではありません。 @linny: `rename` なら **ルールを 1 回書くだけ** で全部に適用される。 @linny: 「ファイル名の先頭に `2026_` を付ける」「スペースをアンダースコアに変える」といった作業が一度で終わるよ。手作業のミスも減るんだ。 ::: ::: warning ファイル名の変更は **元に戻しにくい操作** です。`mv` を 50 回打てば、打ち間違いも起きます。 `rename` でルールをまとめれば、確認すべき箇所が「ルール 1 個」に集約されます。その分だけ安全になります。 ただし逆の面もあります。**ルールを間違えると、一度に 50 個の名前が変わります。** だから次の章の `-n`(事前確認)が必須になります。 ::: ## 3. まず -n で確認する(最重要の型) {#dry-run} > **結論**: `rename` は実行前に必ず `-n` を付けて「何がどう変わる予定か」を表示させる。問題なければ `-n` を外して本実行する。 `rename` でいちばん大事なのは、**いきなり実行しない** ことです。まず `-n` を付けます。これは `--nono` の短い書き方で、何もしないモードのことです。 `-n` を付けると、変更の **予定だけ** が表示されます。 ```bash $ rename -n 's/jpeg/jpg/' *.jpeg ``` ```output rename(photo1.jpeg, photo1.jpg) rename(photo2.jpeg, photo2.jpg) rename(photo3.jpeg, photo3.jpg) ``` ::: dialogue @lina: `rename(photo1.jpeg, photo1.jpg)` と出ました。ファイルは変わっていないんですか? @linny: そう。`-n` を付けている間は **1 個も変わっていない** よ。 @linny: これは「もし実行したら、こう変える予定だよ」という下見なんだ。`rename(変更前, 変更後)` の形で表示されるね。 @lina: なるほど。先に予定を見せてくれるんですね。安心です。 @linny: 表示された内容が思ったとおりなら、`-n` を外して同じコマンドをもう一度打つ。それで本当に名前が変わるよ。 ::: 予定を確認できたら、`-n` を外して本実行します。 ```bash $ rename 's/jpeg/jpg/' *.jpeg ``` これで実際にファイル名が変わります。`-v`(`--verbose`)を付けると、変えた名前を表示してくれます。 ```bash $ rename -v 's/jpeg/jpg/' *.jpeg ``` ```output photo1.jpeg renamed as photo1.jpg photo2.jpeg renamed as photo2.jpg ``` ::: tip **安全の鉄則** 1. `rename -n 'ルール' 対象` で予定を確認します 2. 表示が正しければ `-n` を外して実行します 3. 不安なときは `-v` を付けて実行結果も表示します この「**確認 → 実行**」の 2 段構えを必ず守ってください。 ::: ## 4. s/// で文字列を置き換える {#substitute} > **結論**: `'s/置換前/置換後/'` がもっとも使う形。ファイル名の中の文字列を別の文字列に置き換える。 Ubuntu の `rename`(Perl 版)には、`'s/古い/新しい/'` という **置換ルール** を書きます。これは Perl という言語の書き方です。`s` は substitute(置換)の頭文字です。 ```bash # 拡張子 .jpeg を .jpg に $ rename -n 's/jpeg/jpg/' *.jpeg # ファイル名の "draft" を "final" に $ rename -n 's/draft/final/' * # 先頭に "2026_" を付ける(^ は行頭の意味) $ rename -n 's/^/2026_/' * # 末尾に ".bak" を付ける($ は行末の意味) $ rename -n 's/$/.bak/' * ``` ::: dialogue @lina: `s/jpeg/jpg/` の最初の `s` は何ですか。スラッシュで区切られていますね。 @linny: `s` は「置き換える(substitute)」という合図だよ。`s/A/B/` で **「A を見つけて B に置き換える」** という意味になる。 @linny: スラッシュ `/` は区切り文字だね。`s/置換前/置換後/` と覚えればいいよ。 @lina: `^` や `$` も出てきましたが、これは何ですか。 @linny: それは **位置を表す記号** なんだ。`^` は「名前の先頭」、`$` は「名前の末尾」だね。 @linny: だから `s/^/2026_/` は「先頭に 2026\_ を足す」になる。`s/$/.bak/` は「末尾に .bak を足す」だよ。 @linny: 最初はこの 2 つの型だけ覚えれば十分だよ。 ::: ::: highlight **よく使う置換パターン** | やりたいこと | ルール | | ------------------ | ---------------- | | 拡張子を変える | `s/jpeg/jpg/` | | 文字列を入れ替える | `s/draft/final/` | | 先頭に文字を足す | `s/^/prefix_/` | | 末尾に文字を足す | `s/$/_suffix/` | | 文字列を消す | `s/_copy//` | 置換後を空の `//` にすると「その文字列を削除する」という意味になります。 ::: ## 5. 大文字小文字をそろえる(y///) {#translate} > **結論**: `'y/A-Z/a-z/'` で大文字を小文字に一括変換できる。文字を1対1で置き換える変換ルール。 ファイル名の大文字を小文字にそろえたいときは、`'y/A-Z/a-z/'` を使います。`y` は文字単位の置き換えを表します。英語では transliterate と呼びます。 ```bash # 大文字をすべて小文字に $ rename -n 'y/A-Z/a-z/' * # 小文字をすべて大文字に $ rename -n 'y/a-z/A-Z/' * ``` ::: dialogue @lina: `IMG_001.JPG` のように大文字が混ざったファイルを、全部小文字にそろえたいです。 @linny: それなら `y/A-Z/a-z/` がぴったりだね。`A-Z`(大文字の A から Z)を、対応する `a-z`(小文字の a から z)に 1 文字ずつ置き換えるんだ。 @linny: `IMG_001.JPG` なら `img_001.jpg` になるよ。 @lina: `s///` とは違うんですか。 @linny: `s///` は「文字列のかたまり」を置き換えるものだね。`y///` は「1 文字ずつ」を対応表で置き換えるものだよ。 @linny: 大文字小文字の変換のように **文字の種類をまとめて変える** ときは `y///` が向いているんだ。 ::: ### 5-1. リナの失敗:片方だけ名前が変わらなかった {#failure} ::: dialogue @lina: 練習用フォルダで `File.txt` と `file.txt` を作りました。そこで `rename 'y/A-Z/a-z/' *` を実行したら、見慣れないメッセージが出ました。 ::: ```bash $ ls ``` ```output File.txt file.txt ``` ```bash $ rename 'y/A-Z/a-z/' * ``` ```output File.txt not renamed: file.txt already exists ``` ```bash $ ls ``` ```output File.txt file.txt ``` ::: dialogue @lina: えっ、2 つとも小文字にそろうと思っていました。片方が変わっていません。 @linny: 変換後の名前は `file.txt` になるよね。でも、その名前のファイルはもうそこにあったんだ。 @linny: Ubuntu の `rename`(Perl 版)は、変換先が既にある場合は上書きせずに止まる。だから `not renamed` と出たんだよ。 @lina: 勝手に上書きされないんですね。それは意外でした。 @linny: そう。ただし `-f` を付けたときだけは上書きする。取り消せないから、この記事では `-f` を使わないでほしい。 @lina: 納得しました。`not renamed` が出たら「その名前はもうある」という合図として読みます。 ::: `-n` を付けても、この判定は先に働きます。だから予定を見る段階で気づけます。 ```bash $ rename -n 'y/A-Z/a-z/' * ``` ```output File.txt not renamed: file.txt already exists ``` ::: warning 大文字小文字だけが違う 2 つのファイルに注意してください。`File.txt` と `file.txt` は、小文字にそろえると同じ名前になります。 既定では **上書きされずスキップされます**。`not renamed` と出た行は、変換されていないという意味です。 `-f`(`--force`)を付けたときだけは上書きされ、元のファイルは戻せません。この記事では `-f` を使いません。 ::: ## 6. 2種類の rename に注意(Perl 版 / util-linux 版) {#two-versions} > **結論**: 同じ `rename` でも実装が2種類あり構文が違う。`rename --version` でどちらか確認する。Ubuntu の既定は Perl 版。 実は `rename` には **名前が同じで中身が違う 2 つのコマンド** があります。これが初心者にとって最大の混乱ポイントです。 ::: dialogue @lina: ネットで調べたら、`rename .jpeg .jpg *.jpeg` のように **スラッシュなし** で書く例も出てきました。どちらが正しいんですか。 @linny: どちらも正しいよ。ただし **別物の rename** なんだ。世の中には主に 2 種類ある。 @lina: 同じ名前なのに中身が違うんですか。 @linny: そう。だから最初に **自分の rename がどちらか** を確かめるのが大事だね。`rename --version` を実行すれば分かるよ。 ::: ```bash $ rename --version ``` Perl 版(Ubuntu / Debian の既定)なら、`File::Rename` のような表示が出ます。 ```output /usr/bin/rename using File::Rename version 1.xx ``` util-linux 版なら、`util-linux` の文字が出ます。 ```output rename from util-linux 2.xx ``` ::: highlight **2つの rename の違い** | 項目 | Perl 版(file-rename) | util-linux 版 | | -------- | ---------------------- | -------------------- | | 主な環境 | Ubuntu / Debian | Fedora / RHEL 等 | | 書き方 | `rename 's/A/B/' *` | `rename A B files` | | パターン | 正規表現が使える | 単純な文字列置換のみ | | 事前確認 | `-n` | `-n` | 正規表現とは、文字の並びをパターンで指定する書き方です。この記事で使う `s/A/B/` もその一種です。 **この記事は Perl 版(Ubuntu 既定)が前提です。** util-linux 版での `.jpeg` から `.jpg` への変更は、次のように書きます。 ::: util-linux 版を使っている場合、同じ操作はこうなります。 ```bash # util-linux 版:rename 置換前 置換後 対象ファイル $ rename .jpeg .jpg *.jpeg ``` ::: tip Ubuntu で Perl 版が入っていない場合は `sudo apt install rename` で導入できます。インストールには管理者権限が必要です。 まずは `rename --version` で確認してください。それが先です。 ::: ## 7. よくある初心者のつまずき {#pitfalls} > **結論**: `-n` を付け忘れて即実行、対象ファイルの指定ミス、Perl 版と util-linux 版の取り違えが3大失敗。 ### 7-1. -n を付けずにいきなり実行する ```bash # NG: 確認なしで即実行(ルールが間違っていたら全部巻き込む) $ rename 's/a/b/' * # OK: まず -n で予定を確認してから $ rename -n 's/a/b/' * ``` ::: warning リネームは元に戻しにくい操作です。**必ず `-n` で予定を見てから** 本実行してください。これだけで事故の大半は防げます。 ::: ### 7-2. 対象ファイルの指定が広すぎる ```bash # 危険: カレントの全ファイルが対象になる $ rename -n 's/o/0/' * # 安全: 対象を .txt に絞る $ rename -n 's/o/0/' *.txt ``` `*` はカレントディレクトリの **すべてのファイル** を指します。意図しないファイルまで対象に入りかねません。`*.txt` のように範囲を絞ってください。 ### 7-3. Perl 版のつもりで util-linux 版を使っている ::: dialogue @lina: `rename 's/jpeg/jpg/' *.jpeg` を打ったのに、何も変わりませんでした。 @linny: util-linux 版の rename を使っている可能性が高いね。util-linux 版は `s/.../.../` の書き方を理解しないんだ。 @linny: `rename --version` で確認しよう。util-linux 版なら `rename .jpeg .jpg *.jpeg` の書き方に切り替えるといいよ。 ::: ## 8. ミニ課題:実際にやってみよう {#exercise} > **結論**: 練習用ファイルを作り、置換・接頭辞付与・小文字化の3問を `-n` 付きで確かめる。 ::: dialogue @lina: 手を動かして試したいです。でも本物のファイルで失敗するのが怖いです。 @linny: そのために **練習用の空ファイル** を作ろう。`touch` で作れば中身が空だから、消えても困らないよ。 @linny: まず練習用のディレクトリを作って、その中だけで試そう。 ::: 練習用ファイルを用意します。 ```bash $ mkdir -p ~/rename-practice && cd ~/rename-practice $ touch photo1.JPEG photo2.JPEG report_draft.txt ``` **課題 1**: `.JPEG` を `.jpg` に変える予定を、実行せずに表示してみよう。 :::details ヒント 1(方向づけ)を見る まだ実行はしません。「もし実行したらこうなる」という予定だけを出します。 そのためのオプションを 1 つ付けます。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `rename` です。予定だけを出すのは `-n` です。ルールは `'s/置換前/置換後/'` の形で書きます。 対象は `*.JPEG` と指定します。 ::: :::details 答えを見る ```bash $ rename -n 's/JPEG/jpg/' *.JPEG ``` ```output rename(photo1.JPEG, photo1.jpg) rename(photo2.JPEG, photo2.jpg) ``` このように予定が表示されれば成功です。ファイルはまだ 1 個も変わっていません。 ::: **課題 2**: `report_draft.txt` の `draft` を `final` に変えよう。予定を確認してから本実行します。 :::details ヒント 1(方向づけ)を見る 同じコマンドを 2 回打ちます。1 回目は予定の確認、2 回目が本実行です。 2 回目では、確認用のオプションを外します。 ::: :::details ヒント 2(コマンド名)を見る ルールは `'s/draft/final/'` です。1 回目は `-n` を付けます。2 回目は `-n` を外します。 対象はファイル名を直接書けます。 ::: :::details 答えを見る ```bash $ rename -n 's/draft/final/' report_draft.txt $ rename 's/draft/final/' report_draft.txt $ ls ``` ```output photo1.JPEG photo2.JPEG report_final.txt ``` `report_final.txt` になっていれば成功です。 課題 1 は `-n` だけで終えたので、`.JPEG` はまだ変わっていません。ここでも「`-n` の間は 1 個も変わらない」を確認できます。 ::: **課題 3**: 残ったファイル名の大文字を、すべて小文字にそろえよう。 :::details ヒント 1(方向づけ)を見る 文字列のかたまりではなく、1 文字ずつを対応表で置き換えます。`s///` とは別の記号を使います。 先に `-n` で予定を確認してください。`not renamed` の行が出ないかを見ます。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `y/A-Z/a-z/` です。`rename` の後ろにクォートで囲んで書きます。対象は `*` です。 ::: :::details 答えを見る ```bash $ rename -n 'y/A-Z/a-z/' * $ rename 'y/A-Z/a-z/' * ``` `not renamed: ... already exists` と出た行は、変換されていません。その名前のファイルが既にあるという合図です。 ::: ## 9. 練習用ディレクトリを片づける {#cleanup} ::: danger **片づけには `rm` を使います。ここだけ扱いが違います** `rm` で消したファイルは、GUI と違ってゴミ箱に行きません。その場で完全に消えます。元には戻せません。 - `-r`:ディレクトリを中身ごと消します - `-i`:1 つずつ「消してよいか」を聞きます `-i` を付けると、対象ごとに確認が表示されます。消してよければ `y` を入力して `Enter` を押します。やめるときは `n` を入力します。 消す前に、必ず `ls` でパスと中身を目で確かめてください。 ```bash $ cd ~ $ ls ~/rename-practice $ rm -ri ~/rename-practice ``` ::: ## 10. コピペ用テンプレート {#templates} > **結論**: 確認・置換・接頭辞接尾辞・小文字化・確認方法のよく使う型をまとめて手元に置いておく。 ::: tip **よく使う型をまとめておく(Perl 版前提)** ```bash # まず予定を確認(最重要) rename -n 's/古い/新しい/' * # 確認後に本実行 rename 's/古い/新しい/' * # 拡張子を変える rename -n 's/jpeg/jpg/' *.jpeg # 先頭に文字を足す rename -n 's/^/2026_/' * # 末尾に文字を足す rename -n 's/$/.bak/' * # 文字列を削除する rename -n 's/_copy//' * # 大文字を小文字にそろえる rename -n 'y/A-Z/a-z/' * # どの rename か確認する rename --version ``` ::: ## 11. 振り返り {#review} ::: dialogue @lina: 整理します。`rename` はルールを 1 回書くだけで、たくさんの名前をまとめて変えられるんですね。 @linny: そのとおり。だからこそ、ルールを間違えると一度に全部が変わる。 @lina: そこで `-n` を先に付けて、予定を目で確かめるんでした。 @linny: よく覚えていたね。`File.txt` と `file.txt` の失敗も、`-n` を見ていれば気づけたはずだよ。 ::: ## 今日の 3 行まとめ {#three-lines} 1. `rename` はルールを 1 回書くだけで、複数のファイル名をまとめて変えられる 2. 本実行の前に必ず `-n` を付ける。予定が出るだけで、ファイルは 1 個も変わらない 3. `s/古い/新しい/` は文字列の置き換え、`y/A-Z/a-z/` は 1 文字ずつの置き換えに使う ## 次に読む {#next} - [ワイルドカード(glob)入門 - \* と ? でファイルをまとめて指定する](/articles/tutorials/globbing-wildcards) - [xargs 実践 - コマンドの出力を引数にして一括処理する](/articles/tutorials/xargs-practical) - [ファイル転送の基本 - scp / rsync の使い分け](/articles/tutorials/scp-rsync-basics) - [仮想ターミナルで練習する](/terminal) # ripgrep(rg)入門 - grepより速い高速検索ツール Source: https://penguin-gym-linux.com/articles/tutorials/ripgrep-basics ## ripgrep(rg)とは? {#intro} > **結論**: ripgrep は Rust 製の超高速 grep 代替。`rg パターン` で**カレント以下を再帰検索**し、`.gitignore` を自動で尊重してノイズを除外する。コマンド名は `rg`。 ripgrep はコードベースを横断検索するために作られたツール。`grep -rn` で日常的にやっていた「ディレクトリを丸ごと検索」が、引数を増やさずデフォルト動作になる。 ::: tip **最初に覚える 1 行** ```bash rg TODO ``` カレントディレクトリ以下を再帰的に検索し、`.git/` やビルド成果物(`.gitignore` に書かれたもの)を自動でスキップする。 ::: ::: warning **前提(対象環境)** - OS:Ubuntu / Debian 系(他ディストリでもパッケージ名はほぼ同じ) - バイナリ名は `rg`(パッケージ名は `ripgrep`) ::: ## なぜ grep より速いのか? {#why-fast} > **結論**: ①Rust の高速な正規表現エンジン、②複数ファイルの並列検索、③`.gitignore` とバイナリ判定による検索対象の事前削減、の 3 点が効いている。 速さの主因は単なる実装言語ではなく「**検索しないファイルを増やしている**」点にある。`grep -r` は `.git/` や `node_modules/` まで律儀に走査するが、ripgrep はそれらを最初から候補から外す。 | 項目 | grep | ripgrep | | ----------------- | ----------- | ---------------------- | | 再帰検索 | `-r` が必要 | デフォルト | | `.gitignore` 尊重 | しない | する(デフォルト) | | バイナリファイル | 走査する | 自動スキップ | | 並列処理 | なし | あり | | 行番号表示 | `-n` が必要 | 端末出力時はデフォルト | ::: tip 「速い」だけでなく「**出力が最初からきれい**」なのが実務での価値。検索結果がノイズで埋まらない。 ::: ## どうやってインストールするのか? {#install} > **結論**: Ubuntu/Debian は `apt install ripgrep`。バージョンが古い場合は GitHub Releases の `.deb` か `cargo install ripgrep` を使う。 ```bash # Ubuntu / Debian sudo apt install ripgrep # Fedora / RHEL 系 sudo dnf install ripgrep # macOS(Homebrew) brew install ripgrep # Rust 環境がある場合 cargo install ripgrep ``` インストール確認: ```bash rg --version ``` ```output ripgrep 14.1.0 -SIMD -AVX (compiled) +SIMD +AVX (runtime) ``` ::: warning 古い Ubuntu(18.04 等)の apt 版はバージョンが古く `-t` の型一覧が少ない。最新機能を使うなら GitHub Releases の `.deb` を推奨。 ::: ## 基本的な検索はどうするのか? {#basic} > **結論**: `rg パターン` が基本形。大文字小文字無視は `-i`、単語単位は `-w`、固定文字列(正規表現を無効化)は `-F`。 ```bash # カレント以下から "config" を検索 rg config # 大文字小文字を無視 rg -i config # 単語単位("configure" にはマッチしない) rg -w config # 正規表現として解釈せず、文字列そのものを検索 rg -F "a.b.c" ``` 検索結果はデフォルトで「ファイル名・行番号・該当行」が色付きで表示される。 ```output src/main.rs 12: let config = load_config(); 48: config.reload(); ``` ::: tip `rg -F` は `.` や `*` を含む文字列(バージョン番号・正規表現の例など)をそのまま探したいときに有効。エスケープ不要。 ::: ## ファイルの種類で絞り込むには? {#filetype} > **結論**: `-t 型名` で言語・拡張子グループを指定、`-g 'グロブ'` で任意のパターン指定。`rg --type-list` で利用可能な型を確認できる。 ```bash # Python ファイルだけを検索 rg -t py "import requests" # JavaScript / TypeScript を除外して検索 rg -T js "function" # glob で拡張子を直接指定 rg -g '*.md' "TODO" # 複数 glob(! で除外) rg -g '*.rs' -g '!target/*' "unsafe" ``` 利用可能な型の一覧: ```bash rg --type-list | head ``` ```output agda: *.agda, *.lagda asciidoc: *.adoc, *.asc, *.asciidoc asm: *.S, *.a51, *.asm, *.s ... ``` ::: tip `-t`(小文字)が「含める」、`-T`(大文字)が「除外する」。覚え方は **T = exclude の T**(type を否定)。 ::: ## 検索結果の前後を表示するには? {#context} > **結論**: `-A N`(後ろ)、`-B N`(前)、`-C N`(前後)で文脈行を表示する。エラーログの周辺確認に必須。 ```bash # マッチ行の後ろ 3 行も表示 rg -A 3 "panic" # マッチ行の前 2 行も表示 rg -B 2 "Exception" # 前後 3 行(最も使う) rg -C 3 "Traceback" ``` その他の出力制御: ```bash # マッチしたファイル名だけ(一覧が欲しいとき) rg -l "deprecated" # マッチ件数だけ rg -c "TODO" # マッチした部分だけを抜き出す rg -o 'https?://[^ ]+' ``` ::: tip `rg -l パターン | xargs ...` のように、ヒットしたファイルだけを後続処理に渡す使い方が強力。 ::: ## .gitignore されたファイルも検索したい {#hidden} > **結論**: `--hidden` で隠しファイル、`-u`(`--unrestricted`)で `.gitignore` を無視、`-uu` で両方、`-uuu` でバイナリも含めて完全に全探索する。 ripgrep のデフォルトは「きれいな検索」だが、ログや `.env`、`node_modules` の中身を探したいこともある。その場合は段階的にフィルタを外す。 ```bash # 隠しファイル(.env など)も対象に rg --hidden "API_KEY" # .gitignore を無視(node_modules なども検索) rg -u "lodash" # 隠しファイル + .gitignore 無視 rg -uu "TODO" # さらにバイナリも含めて完全に全部 rg -uuu "magic_bytes" ``` ::: warning `-uuu` は `.git/` の中身やバイナリまで走査するため遅くなり、ノイズも増える。原則は「まずデフォルト、足りなければ `-u` を 1 つずつ増やす」。 ::: ## 文字列を置換できるのか? {#replace} > **結論**: `-r` で置換後の出力をプレビューできる。**ファイルは変更しない**(標準出力のみ)。実ファイルを書き換えるなら `sed -i` 等と組み合わせる。 ```bash # foo を bar に置換した結果を表示(ファイルは無変更) rg 'foo' -r 'bar' # キャプチャグループを使った置換プレビュー rg '(\w+)@(\w+)' -r '$2.$1' ``` `rg -r` はあくまで確認用。実際に書き換えるには結果を見てから `sed` を使うのが安全な型。 ```bash # まず rg で対象と置換結果を確認してから sed -i 's/foo/bar/g' $(rg -l 'foo') ``` ::: warning `sed -i` は破壊的操作。実行前に `rg -l` でヒットするファイル一覧を必ず確認すること。 ::: ## grep からの乗り換え早見表 {#cheatsheet} > **結論**: 多くの場面で `grep -rn` を `rg` に置き換えるだけで動く。型絞り込みと文脈表示を覚えれば日常検索はほぼ ripgrep で完結する。 | やりたいこと | grep | ripgrep | | -------------------- | -------------------------- | -------------- | | ディレクトリ再帰検索 | `grep -rn pat .` | `rg pat` | | 大文字小文字無視 | `grep -i pat` | `rg -i pat` | | 固定文字列検索 | `grep -F str` | `rg -F str` | | ファイル名のみ | `grep -rl pat .` | `rg -l pat` | | 拡張子で絞り込み | `grep -r --include='*.py'` | `rg -t py pat` | | 前後の文脈 | `grep -C 3 pat` | `rg -C 3 pat` | ::: tip **コピペ用:実務で頻出の型** ```bash # プロジェクト全体から TODO を一覧 rg -n 'TODO|FIXME' # Python だけ、文脈つきでエラー箇所を探す rg -t py -C 3 'raise ' # 隠しファイル含めて環境変数の漏洩チェック rg --hidden -g '!.git/*' 'API_KEY|SECRET' ``` ::: ## 次に読む {#next} - [find・grep・awkの使い方入門](/articles/tutorials/find-grep-awk-basics) - [fzf であいまい検索を高速化する](/articles/tutorials/fzf-fuzzy-finder) - [sed でテキストを置換する](/articles/tutorials/sed-basics) # rsync 差分同期 実践 - バックアップとミラーリングの定石 Source: https://penguin-gym-linux.com/articles/tutorials/rsync-sync-practical ## この記事で解決できること {#intro} - `rsync` の差分同期を **バックアップ・ミラーリングの実務** で使えるようになる - `--delete` / `--link-dest` / `--exclude` の **正しい使い方と事故防止** が分かる - 「同期したつもりが消えた」「容量が足りない」などの **定番トラブルを避ける型** が身につく ::: tip **結論(実務の型)** - **片方向の最新化** → `rsync -av src/ dst/` - **完全ミラー(不要ファイルも削除)** → `rsync -av --delete src/ dst/` - **世代を残す増分バックアップ** → `--link-dest` で前回分をハードリンク - 破壊的操作の前は **必ず `--dry-run`** で確認する ::: ::: warning **前提(対象環境)** - OS:Ubuntu(rsync 3.x 系) - ローカル ↔ リモート(SSH)両方を想定 - `scp` / `rsync` の基本は [scp / rsync の使い分け](/articles/tutorials/scp-rsync-basics) を参照 ::: ## rsync の差分同期とは何か? {#what} > **結論**: rsync はコピー先に既にあるファイルを比較し、変化した分だけを転送する。2 回目以降が劇的に速いのがバックアップ・ミラーリングに向く理由。 `rsync` は単なるコピーツールではなく、**転送元と転送先の差分だけを送る** 同期ツールである。初回はフルコピーになるが、2 回目以降は「変わったファイルだけ」「変わったブロックだけ」を送るため、大量のデータでも短時間で最新化できる。 この性質が、定期バックアップ(毎日同じディレクトリを最新化)やミラーリング(2 拠点を一致させる)に向いている。 ## なぜ差分転送が速いのか? {#why-fast} > **結論**: rsync はデフォルトで「サイズ + 更新時刻」が一致するファイルを転送スキップする。差分があるファイルだけ、さらに差分ブロックだけを送るため転送量が小さい。 rsync の高速さは 2 段階の差分判定による。 1. **ファイル単位のスキップ判定**: デフォルトでは各ファイルの **サイズと更新時刻(mtime)** を比較し、両方一致すれば「変更なし」とみなして転送しない 2. **ブロック単位の差分転送**: 変更ありと判定したファイルは、ローリングチェックサムで一致するブロックを検出し、**変わった部分だけ** を送る(rsync アルゴリズム) ```bash # 初回:フルコピー $ rsync -av src/ /backup/dst/ # 2 回目以降:差分だけ転送(変更がなければ一瞬で終わる) $ rsync -av src/ /backup/dst/ ``` ::: tip 更新時刻が信用できない環境(クロックずれ・ファイルシステム移行直後など)では、後述の `--checksum` で中身を直接比較できる。 ::: ## バックアップの基本形はどう書くのか? {#backup-basic} > **結論**: `rsync -av src/ dst/` が基本。`-a`(アーカイブモード)で権限・所有者・タイムスタンプ・シンボリックリンクを保持したまま再帰コピーできる。 実務のバックアップは、まず属性を保持する `-a` を付けるところから始める。 ```bash $ rsync -av /home/user/data/ /backup/data/ ``` `-a`(アーカイブモード)は次のオプションをまとめたもの。 | 含まれる内容 | 意味 | | ------------ | -------------------------------- | | `-r` | 再帰(ディレクトリを下位まで) | | `-l` | シンボリックリンクをそのまま保持 | | `-p` | パーミッションを保持 | | `-t` | タイムスタンプを保持 | | `-g` / `-o` | グループ / 所有者を保持 | | `-D` | デバイス・特殊ファイルを保持 | よく併用するオプション。 - `-v`:転送内容を表示(`-vv` でさらに詳細) - `-z`:転送時に圧縮(遅い回線で有効。LAN では無効化した方が速いこともある) - `-h`:サイズを人間可読表示(`--progress` と相性が良い) ::: warning **末尾スラッシュの罠(最重要)** ```bash rsync -av src/ dst/ # src の「中身」を dst 直下に置く rsync -av src dst/ # dst の中に「src ディレクトリごと」置く(dst/src/...) ``` 転送元の末尾 `/` の有無で結果が変わる。バックアップ先の階層が一段ずれる事故の大半はこれが原因。 ::: ## ミラーリングはどうするのか?(--delete) {#mirror} > **結論**: 完全ミラーには `--delete` を付ける。転送元に存在しないファイルを転送先から削除し、両者を完全一致させる。破壊的なので必ず `--dry-run` を先に実行する。 `-a` だけのバックアップは「追加・更新」しかしない。転送元で削除したファイルは、転送先に残り続ける。**転送元と転送先を完全に一致させたい**(ミラーリング)場合は `--delete` を使う。 ```bash # まず必ず dry-run で「何が削除されるか」を確認 $ rsync -av --delete --dry-run src/ /mirror/dst/ # 表示内容に問題なければ本番実行 $ rsync -av --delete src/ /mirror/dst/ ``` dry-run の出力では、削除されるファイルが `deleting ...` の行で表示される。 ```output sending incremental file list deleting old-report.csv deleting cache/tmp.dat ./ new-report.csv ``` ::: danger **`--delete` は転送先を破壊する** 転送元のパスを間違える(例: 空ディレクトリを指定する)と、転送先の **全ファイルが削除される**。特に次の組み合わせは事故が起きやすい。 - 転送元の末尾スラッシュの付け忘れ・付けすぎ - `src/` の `src` 部分が変数で、その変数が空のまま展開された `--delete` を含む rsync は、本番前に `--dry-run` を例外なく実行すること。 ::: ::: tip 削除のタイミングを制御するオプションもある。 - `--delete-after`:転送が全部終わってから削除(途中失敗時に転送先を壊しにくい) - `--delete-excluded`:`--exclude` で除外したファイルも転送先から削除する ::: ## 世代バックアップ(増分スナップショット)はどう作るのか? {#snapshot} > **結論**: `--link-dest` で前回のバックアップを参照すると、変更のないファイルはハードリンクで共有される。各世代が「フルバックアップに見えて、実容量は差分だけ」という効率的なスナップショットになる。 毎日上書きするバックアップは、「3 日前の状態に戻したい」に対応できない。**世代を残す** には `--link-dest` を使う。 `--link-dest=DIR` は、転送先に書き込む際、変更のないファイルを `DIR`(通常は前回のバックアップ)への **ハードリンク** として作る。中身が同じファイルはディスクを二重に消費しないため、複数世代を保持しても容量効率が高い。 ```bash # 日付ごとのディレクトリにスナップショットを作る例 $ SRC=/home/user/data/ $ DEST=/backup/snapshots $ TODAY=$(date +%F) # 例: 2026-06-05 $ LATEST=$DEST/latest # 前回のスナップショットを指すシンボリックリンク $ rsync -av --delete \ --link-dest="$LATEST" \ "$SRC" "$DEST/$TODAY/" # latest を最新スナップショットに更新 $ ln -sfn "$DEST/$TODAY" "$LATEST" ``` これで `/backup/snapshots/2026-06-05/` には全ファイルが揃って見えるが、前日から変わっていないファイルは前日分とハードリンクを共有しているため、増えた実容量は差分だけになる。 ::: tip - `--link-dest` のパスは **転送先からの相対** または絶対パスで指定する。相対指定する場合は転送先ディレクトリ基準になる点に注意 - 世代の削除は、対応する日付ディレクトリを `rm -rf` するだけでよい。ハードリンクなので、他世代が参照しているファイルの実体は消えない ::: ## 不要ファイルを除外するには?(--exclude) {#exclude} > **結論**: `--exclude=PATTERN` で対象から外す。除外パターンが多い場合は `--exclude-from=FILE` にまとめる。キャッシュ・ログ・一時ファイルを除くとバックアップが軽くなる。 バックアップにキャッシュや一時ファイルを含めると、容量も転送時間も無駄になる。`--exclude` で除外する。 ```bash $ rsync -av --delete \ --exclude='*.tmp' \ --exclude='cache/' \ --exclude='node_modules/' \ src/ /backup/dst/ ``` 除外が多いときはファイルにまとめる。 ```bash # .rsync-exclude の中身(1 行 1 パターン) # *.tmp # cache/ # node_modules/ # .git/ $ rsync -av --delete --exclude-from='.rsync-exclude' src/ /backup/dst/ ``` ::: warning 除外パターンの先頭 `/` は **転送元のルート基準** を意味する。 - `--exclude='/cache'`:転送元直下の `cache` だけ除外 - `--exclude='cache/'`:どの階層の `cache/` も除外 意図しない階層まで除外しないよう、`--dry-run` で挙動を確認すること。 ::: ## 帯域・中断・進捗をどう制御するのか? {#transfer-control} > **結論**: `-P`(`--partial --progress`)で中断再開と進捗表示、`--bwlimit` で帯域制限ができる。大容量・低速回線・本番時間帯の転送で必須のオプション。 大きなデータをリモートに送る場合、回線を埋め尽くしたり、途中で切れてやり直しになったりする。次のオプションで制御する。 ```bash # 進捗表示 + 中断時に途中ファイルを保持(再開で続きから) $ rsync -avP src/ user@server:/backup/dst/ # 帯域を 10 MB/s に制限(本番時間帯のバックアップで有効) $ rsync -av --bwlimit=10M src/ user@server:/backup/dst/ # SSH ポートが標準と違う場合 $ rsync -av -e "ssh -p 2222" src/ user@server:/backup/dst/ ``` | オプション | 効果 | | ------------------ | --------------------------------------------------------- | | `-P` | `--partial`(途中ファイル保持)+ `--progress`(進捗表示) | | `--partial` | 中断しても途中まで転送したファイルを残し、次回続行 | | `--bwlimit=RATE` | 転送帯域の上限(例: `10M` = 10 MB/s) | | `-e "ssh -p PORT"` | リモートシェルの指定(非標準ポート等) | ::: tip `-z`(圧縮)は CPU を使う。LAN や既に圧縮済みのデータ(動画・画像・アーカイブ)では `-z` を外した方が速いことが多い。低速 WAN で効果が出る。 ::: ## チェックサム比較はいつ使うのか? {#checksum} > **結論**: `--checksum`(`-c`)はサイズ・更新時刻ではなく中身のチェックサムで差分判定する。クロックずれ・FS 移行後の検証など、更新時刻が当てにならない場面で使う。常用は遅いので避ける。 デフォルトの「サイズ + 更新時刻」判定は高速だが、更新時刻が信用できない状況では取りこぼす可能性がある。`--checksum` は全ファイルのチェックサムを計算して中身で比較するため、確実だが遅い。 ```bash # 中身で厳密に差分判定(移行検証・整合性チェック向き) $ rsync -avc src/ /backup/dst/ ``` 使いどころ。 - ファイルシステム / サーバ移行後に、コピーが完全か検証したい - バックアップ元と先の **内容一致を保証** したい(mtime がずれている可能性がある) ::: warning `--checksum` は両側で全ファイルを読み込むため、大量データでは非常に遅い。日常の定期同期はデフォルトの mtime 判定を使い、検証時だけ `--checksum` を併用するのが定石。 ::: ## 事故を防ぐチェックリスト(まとめ) {#summary} > **結論**: `--delete` を伴う rsync は `--dry-run` を必須に、末尾スラッシュとパスの空変数を毎回確認する。これだけで rsync の重大事故はほぼ防げる。 ::: danger **実行前チェック(特に `--delete` 使用時)** - [ ] 転送元 / 転送先のパスは正しいか(空変数で展開されていないか) - [ ] 末尾スラッシュは意図通りか(`src/` と `src` は別物) - [ ] `--dry-run` で削除・転送対象を確認したか - [ ] 転送先の空き容量は足りるか(`df -h` で確認) ::: ::: tip **コピペ用:安全テンプレ** ```bash # 1. 片方向バックアップ(追加・更新のみ) rsync -av src/ /backup/dst/ # 2. 完全ミラー(必ず dry-run を先に) rsync -av --delete --dry-run src/ /mirror/dst/ rsync -av --delete src/ /mirror/dst/ # 3. 世代スナップショット(前回分をハードリンク) rsync -av --delete --link-dest=/backup/snapshots/latest \ src/ /backup/snapshots/$(date +%F)/ # 4. リモートへ(進捗 + 帯域制限 + 非標準ポート) rsync -avP --bwlimit=10M -e "ssh -p 2222" src/ user@server:/backup/dst/ ``` ::: ## 次に読む {#next} - [scp / rsync の使い分けと事故らない型](/articles/tutorials/scp-rsync-basics) - [cron で定期バックアップを自動化する](/articles/tutorials/cron-basics) - [転送先の容量が足りないときの対処](/articles/troubleshooting/no-space-left-on-device) # ファイル転送の基本:scp / rsync の使い分けと事故らない型 Source: https://penguin-gym-linux.com/articles/tutorials/scp-rsync-basics ## この記事で解決できること {#intro} - `scp` と `rsync` の **正しい使い分け** が分かる - 「Permission denied」「転送されたはずなのに無い」などの **定番事故を即切り分け** できる - サーバ運用で **安全にファイルを運ぶ型** が身につく ::: tip **結論(実務の型)** - **一発コピー・小規模** → `scp` - **大量 / 定期 / 再実行前提** → `rsync` - 事故る原因の8割は **①ユーザー ②パス ③権限 ④容量** のどれか ::: ::: warning **前提(対象環境)** - OS:Ubuntu - SSH接続可能 - ローカル ↔ サーバ、サーバ ↔ サーバ両方を想定 ::: ## 1. scp:単発コピーの基本 {#scp} > **結論**: `scp`は単発コピー向け。ディレクトリは`-r`が必須で、付け忘れると何もコピーされない。 ### 1-1. ローカル → サーバ ```bash $ scp local.txt user@server:/path/to/dir/ ``` ### 1-2. サーバ → ローカル ```bash $ scp user@server:/path/to/file.txt . ``` ### 1-3. ディレクトリを丸ごと ```bash $ scp -r mydir user@server:/path/to/ ``` ::: warning `-r` 忘れ=何もコピーされない(初心者あるある) ::: ## 2. scp の事故パターン {#scp-error} > **結論**: `Permission denied`は権限・ユーザー違いが原因。相対パス誤解で転送先がホーム配下になる事故も多い。 ### 2-1. Permission denied 原因候補: - 書き込み権限がない - ユーザーを間違えている - `/root` 配下に置こうとしている 切り分け: ```bash $ ssh user@server $ ls -ld /path/to/dir $ whoami ``` ### 2-2. 転送された「はず」なのに見当たらない 原因:相対パスを誤解している ```bash # NG: ホームディレクトリの tmp/ に入る $ scp file.txt user@server:tmp/ # OK: /tmp/ に入る $ scp file.txt user@server:/tmp/ ``` ## 3. rsync:実務で使う本命 {#rsync} > **結論**: 実務の本命は`rsync -av`。`-a`で属性保持、`-v`で詳細表示、`-z`で圧縮が基本セット。 ### 3-1. 基本形(まずはこれ) ```bash $ rsync -av src/ user@server:/path/to/dst/ ``` **オプションの意味:** - `-a`:属性保持(ほぼ必須) - `-v`:詳細表示 - `-z`:圧縮(遅い回線で有効) ## 4. rsync 最大の罠:スラッシュ問題 {#slash} > **結論**: `src/`は中身だけ、`src`はディレクトリごとコピー。末尾スラッシュの有無で結果が変わる最大の罠。 ```bash rsync -av src/ dst/ # 中身だけコピー rsync -av src dst/ # srcディレクトリごとコピー ``` は **意味が違う**。 - `src/` → **中身だけ** コピー - `src` → **srcディレクトリごと** コピー ::: warning 事故率が一番高いポイント。必ず意識する。 ::: ## 5. dry-run(事故防止の切り札) {#dryrun} > **結論**: 本番前は`--dry-run`で確認。表示内容が実際に起きることなので、不安なら必須。 本番前に必ず確認。 ```bash $ rsync -av --dry-run src/ user@server:/path/ ``` ::: tip **表示された内容=実際に起きること** 不安なら必須。 ::: ## 6. Permission denied(rsync編) {#permission} > **結論**: 書き込み先の権限不足・所有者違いが原因。`--rsync-path="sudo rsync"`で回避できる場合がある。 よくある原因: - 書き込み先の権限不足 - 所有者違い - sudoが必要 ### 対処例(sudo rsync) ```bash $ rsync -av --rsync-path="sudo rsync" src/ user@server:/root/path/ ``` ※ sudo権限がある前提 ## 7. 容量不足で失敗するケース {#space} > **結論**: 転送途中で止まる場合は転送先の容量不足を疑う。`df -h`と`du -sh`で事前確認する。 症状: - 転送途中で止まる - エラーが曖昧 確認: ```bash $ df -h $ du -sh src/ ``` ::: tip 転送先ディスクが満杯だと失敗する(no space left on device) ::: ## 8. scp と rsync の使い分けまとめ {#compare} > **結論**: 単発ファイルはscp、ディレクトリ・再実行・大量データの転送にはrsyncを使い分けるのが推奨。 | 用途 | 推奨 | | ------------ | ----- | | 単発ファイル | scp | | ディレクトリ | rsync | | 再実行前提 | rsync | | 大量データ | rsync | ::: warning **やってはいけないこと** - パスを確認せず実行 - `-r` を付け忘れる - dry-run無しで rsync - root直下に投げる ::: ::: tip **コピペ用:安全テンプレ** ```bash # scp(単発) scp file.txt user@server:/path/ # rsync(安全) rsync -av --dry-run src/ user@server:/path/ rsync -av src/ user@server:/path/ ``` ::: ## 次に読む {#next} - [権限エラーの解決方法](/articles/troubleshooting/permission-denied-fix) - [転送先の容量不足の場合](/articles/troubleshooting/no-space-left-on-device) - [tarでアーカイブして転送する場合](/articles/tutorials/tar-basics) # sed 入門 - ストリームエディタでテキスト置換 Source: https://penguin-gym-linux.com/articles/tutorials/sed-basics ## この記事で解決できること {#intro} - `sed` の **代表 5 パターン**(置換 / 削除 / 印字 / 範囲 / 複数式)を最短ルートで習得できる - `-i` インプレース編集の **事故を避ける型** が身につく - BRE と ERE(`-E`)の違いを把握し、`grep -E` / `awk` と一貫した正規表現が書けるようになる ::: tip **結論(実務で使う型)** - まず **標準出力で確認** → 問題なければ `-i.bak` で上書き - 区切り文字は `/` 以外も使える(パスを置換するときは `|` か `#`) - 「全置換したい」なら必ず末尾 `g`。`g` を忘れると **行内 1 件のみ** 置換 ::: ::: warning **前提(対象環境)** - OS: Ubuntu / Debian / RHEL 系 Linux - 想定 sed: GNU sed(macOS の BSD sed は `-i` の書式が異なるため別途注意) ::: ## sed とは? {#what-is-sed} `sed`(stream editor)は **入力ストリームを 1 行ずつ読み、編集スクリプトを適用して出力する** コマンド。対話編集の Vim と違い、**シェルパイプライン上で非対話に動く** のが本質。ログ整形・設定ファイル一括書き換え・テキスト前処理で使う。 ```bash $ echo "hello world" | sed 's/world/Linux/' hello Linux ``` `s/old/new/` は **substitute(置換)** コマンドで、sed の使用頻度の 8 割を占める。 ## なぜ最初に「標準出力で確認」が鉄則なのか? {#dry-run} `sed -i` は **ファイルを直接書き換える**。書き換えた後で「置換パターンを間違えた」「区切り文字に含まれる `/` をエスケープし忘れた」と気づくと復旧できない。標準出力に出して **目で確認してから** `-i` を付けるのが事故防止の唯一の型。 ```bash # 1. まず標準出力で確認 $ sed 's/foo/bar/g' config.txt # 2. 問題なければバックアップ付きで上書き $ sed -i.bak 's/foo/bar/g' config.txt # 3. config.txt.bak が自動生成されている $ ls config.txt* config.txt config.txt.bak ``` ::: warning `-i` 単独(バックアップなし)は本番運用では非推奨。`-i.bak` を癖にすると、想定外置換からのロールバックが `mv config.txt.bak config.txt` で済む。 ::: ## 1. 置換: s/old/new/ の基本形 {#substitute} ### 1-1. 行内最初の 1 件だけ置換(デフォルト) ```bash $ echo "apple apple apple" | sed 's/apple/orange/' orange apple apple ``` ### 1-2. 行内すべて置換(g フラグ) ```bash $ echo "apple apple apple" | sed 's/apple/orange/g' orange orange orange ``` ::: tip **`g` の付け忘れは sed 初心者の最頻出ミス。** 「1 行に複数ある置換対象が一部しか変わらない」と感じたらまず `g` を確認する。 ::: ### 1-3. 大文字小文字を無視(I フラグ) ```bash $ echo "Apple APPLE apple" | sed 's/apple/orange/gI' orange orange orange ``` `I` フラグは GNU sed 拡張。POSIX sed には存在しないため、移植性が必要な場面では使わない。 ## 2. 区切り文字を `/` 以外に変える {#delimiter} 置換対象にパス(`/usr/local`)が含まれると `/` のエスケープ地獄になる。**`s` 直後の文字が区切り文字になる** ため、`|` `#` `,` などを選べる。 ```bash # NG: エスケープが必要で読みにくい $ sed 's/\/usr\/local\/bin/\/opt\/bin/' file # OK: パイプを区切り文字に $ sed 's|/usr/local/bin|/opt/bin|' file # OK: # を区切り文字に $ sed 's#/usr/local/bin#/opt/bin#' file ``` ::: tip 区切り文字を変えるだけでバグの 8 割は消える。パス置換は `|` か `#` がデファクト。 ::: ## 3. アドレス指定(範囲を絞る) {#address} sed の編集コマンドは **アドレス(範囲)を前置** することで対象行を絞れる。 ### 3-1. 行番号で指定 ```bash $ sed '3s/foo/bar/' file # 3 行目のみ $ sed '1,5s/foo/bar/g' file # 1〜5 行目 $ sed '10,$s/foo/bar/g' file # 10 行目〜最終行($ = EOF) ``` ### 3-2. パターンで指定 ```bash # "ERROR" を含む行だけ置換 $ sed '/ERROR/s/foo/bar/g' file # /START/ から /END/ までの範囲で置換 $ sed '/START/,/END/s/foo/bar/g' file ``` ### 3-3. 否定(`!`) ```bash # 1 行目「以外」を置換 $ sed '1!s/foo/bar/g' file # コメント行(#始まり)以外で置換 $ sed '/^#/!s/foo/bar/g' file ``` ## 4. 削除(d)と印字(p) {#delete-print} ### 4-1. 行削除 ```bash $ sed '/^$/d' file # 空行を削除 $ sed '/^#/d' file # コメント行(# 始まり)を削除 $ sed '1,5d' file # 1〜5 行目を削除 $ sed '$d' file # 最終行を削除 ``` ### 4-2. 特定行のみ印字(-n + p) `sed` のデフォルトは「すべての行を出力 + 編集適用」。`-n` で **デフォルト出力を抑止** し、`p` コマンドで明示的に出力する。 ```bash # "ERROR" を含む行だけ出力(grep "ERROR" と同等) $ sed -n '/ERROR/p' file # 10〜20 行目だけ出力 $ sed -n '10,20p' file ``` ::: tip `sed -n 'Np'` は `head -n N | tail -1` より高速で読みやすい。 ::: ## 5. 複数の編集を一度に適用 {#multiple} ### 5-1. -e で複数式 ```bash $ sed -e 's/foo/bar/g' -e 's/hoge/fuga/g' file ``` ### 5-2. セミコロンで連結(GNU sed) ```bash $ sed 's/foo/bar/g; s/hoge/fuga/g' file ``` ### 5-3. スクリプトファイルから読み込み 繰り返し使う変換セットはファイルに保存できる。 ```bash $ cat replace.sed s/foo/bar/g s/hoge/fuga/g /^#/d $ sed -f replace.sed file ``` ## 6. BRE と ERE:`-E` を使うべきか? {#regex} sed はデフォルトで **BRE(基本正規表現)** を使う。`+` `?` `|` `(...)` を使うには **バックスラッシュでエスケープ** が必要で読みにくい。`-E` を付けると **ERE(拡張正規表現)** に切り替わり、`grep -E` / `awk` と同じ記法になる。 ### 6-1. BRE(デフォルト) ```bash # グループとキャプチャに \( \) が必要 $ echo "2026-05-20" | sed 's/\([0-9]\{4\}\)-\([0-9]\{2\}\)-\([0-9]\{2\}\)/\3\/\2\/\1/' 20/05/2026 ``` ### 6-2. ERE(`-E`) ```bash # エスケープ不要で読みやすい $ echo "2026-05-20" | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/' 20/05/2026 ``` ::: tip **実務では `-E` を癖にする。** BRE のエスケープは読み手の負担が大きく、レビューでの取りこぼし原因になる。 ::: ### 6-3. 後方参照(`\1` `\2` ...) キャプチャした部分は置換側で `\1` `\2` ... で参照できる。BRE / ERE どちらでも記法は同じ。 ```bash # Last, First → First Last に並び替え $ echo "Doe, John" | sed -E 's/(.+), (.+)/\2 \1/' John Doe ``` ## 7. やってはいけない・事故パターン {#pitfalls} ::: warning **事故パターン Top 5** 1. **`-i` を確認なしで実行** → 復旧不能。必ず `-i.bak` か事前 dry-run 2. **`g` 忘れで全置換できない** → 行内 1 件しか変わらない 3. **区切り文字エスケープ漏れ** → `|` か `#` に切り替えれば回避 4. **BRE/ERE 混乱** → 統一して `-E` を使う 5. **`*` `.` をリテラルとして扱おうとする** → 正規表現のメタ文字なのでエスケープ必要 ::: ### 7-1. リテラル文字のエスケープ ```bash # NG: . が「任意の 1 文字」として解釈される $ echo "192x168x0x1" | sed 's/192.168.0.1/X/' X # OK: . をエスケープ $ echo "192x168x0x1" | sed 's/192\.168\.0\.1/X/' 192x168x0x1 ``` ### 7-2. シェル変数を埋め込むとき シングルクォート内ではシェル変数は展開されない。ダブルクォートに切り替えるが、`$` や `\` の扱いに注意する。 ```bash NEW="bar" # NG: $NEW がそのまま文字列扱い $ sed 's/foo/$NEW/g' file # OK: ダブルクォートで展開 $ sed "s/foo/$NEW/g" file ``` ::: warning `$NEW` の中身に `/` `&` `\` が含まれると壊れる。安全に変数展開したいなら区切り文字を別の文字に変えるか、`printf '%s' "$NEW"` でサニタイズする。 ::: ## 8. 実務で使えるレシピ集 {#recipes} ::: tip **コピペ用テンプレート** ```bash # CRLF を LF に変換 sed -i.bak 's/\r$//' file # 空行と # で始まるコメント行を除去 sed -e '/^$/d' -e '/^#/d' /etc/nginx/nginx.conf # IP アドレスをマスク sed -E 's/\b([0-9]{1,3}\.){3}[0-9]{1,3}\b/x.x.x.x/g' access.log # 末尾の空白を削除 sed -i.bak 's/[[:space:]]*$//' file # 指定行の前後に行を挿入 sed -i.bak '/^pattern/i\ inserted line before' file # YAML の値だけ書き換え(key: の後ろ) sed -i.bak -E 's/^(version:\s*).*/\1"1.2.3"/' config.yaml ``` ::: ## 比較: sed と awk / grep の使い分け {#vs-awk-grep} | 用途 | 推奨 | | -------------------------------- | ------------------------ | | 単純な文字列置換 | **sed** | | 行抽出(grep 互換) | grep / sed -n | | 列単位の処理(カラム抽出・計算) | **awk** | | 複数行にまたがる変換 | sed の N コマンド or awk | | 構造化データ(JSON/YAML)の編集 | jq / yq | ::: tip **sed の強み**: ストリーミング 1 行処理、軽量、ほぼすべての Unix で利用可能 **sed の限界**: 列処理・構造化データ・多行処理が苦手。複雑になったら awk / jq に切り替える ::: ## 次に読む {#next} - [find・grep・awk の使い方 - 基礎編](/articles/tutorials/find-grep-awk-basics) - [grep・awk の応用テクニック - データ処理の実践](/articles/tutorials/find-grep-awk-advanced) - [パイプとリダイレクト入門 - データの流れを理解する](/articles/tutorials/pipe-redirect-basics) - [Vim 入門 - 基本操作と実践テクニック](/articles/tutorials/vim-basics) # SELinux 入門 - 拒否ログを読み解く第一歩 Source: https://penguin-gym-linux.com/articles/tutorials/selinux-introduction ## SELinux とは何か? {#what-is-selinux} SELinux(Security-Enhanced Linux)は、Linux カーネルに組み込まれた強制アクセス制御(MAC)機構で、通常の Unix ファイル権限(DAC)の上にもう一層のセキュリティを追加する。RHEL / CentOS / Fedora 系では標準で有効になっており、Apache・PostgreSQL などのプロセスが想定外のファイルにアクセスしようとすると自動的にブロックする。 通常の chmod / chown(DAC)では「このファイルの所有者か?グループに属しているか?」しか問わない。SELinux は加えて「このプロセスタイプがこのファイルタイプにこの操作を行ってよいか?」をポリシーで制御する。 ### 動作モード {#modes} | モード | 動作 | | -------------- | ------------------------------------------------------ | | **Enforcing** | ポリシー違反を拒否してログに記録。通常運用はこのモード | | **Permissive** | 拒否せずログのみ記録。トラブルシュート時に使用 | | **Disabled** | SELinux 完全無効。再起動が必要 | ::: warning Disabled への変更は `/etc/selinux/config` の編集と再起動が必要。再び Enforcing に戻す際はファイルコンテキストの再ラベル付けが必要になる場合がある。 ::: ## 動作モードを確認する {#check-mode} 現在のモードを確認するには `getenforce` が最速。詳細を確認したい場合は `sestatus` を使う。 ```bash $ getenforce Enforcing ``` ```bash $ sestatus SELinux status: enabled SELinuxfs mount: /sys/fs/selinux SELinux mount point: /sys/fs/selinux Loaded policy name: targeted Current mode: enforcing Mode from config file: enforcing Policy MLS status: enabled Policy deny_unknown status: allowed Memory protection checking: actual (secure) Max kernel policy version: 33 ``` `Current mode` と `Mode from config file` の両方を見ると、現在の状態と再起動後の状態を確認できる。 ### Permissive モードに一時切り替え {#setenforce} 再起動せずにモードを切り替えたい場合は `setenforce` を使う。**再起動すると設定ファイルの値に戻る**。 ```bash $ sudo setenforce 0 # Permissive に変更 $ sudo setenforce 1 # Enforcing に戻す ``` ::: tip トラブルシュートの鉄則: まず `setenforce 0` で Permissive にして問題が再現しなくなれば、SELinux が原因と確定できる。本番環境では確認後すぐ Enforcing に戻すこと。 ::: ## AVC 拒否ログを読む {#read-avc} SELinux がアクセスを拒否すると **AVC(Access Vector Cache)ログ**を記録する。主な記録先は `/var/log/audit/audit.log`。 ```output type=AVC msg=audit(1748780400.123:1234): avc: denied { read } for pid=1122 comm="httpd" name="app.conf" dev="sda1" ino=56789 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0 ``` 各フィールドの意味: | フィールド | 意味 | 上記の例 | | ---------------------------- | ------------------------------------ | ------------------ | | `denied { read }` | 拒否された操作 | 読み取りを拒否 | | `comm="httpd"` | プロセス名 | Apache | | `name="app.conf"` | アクセス対象のファイル名 | app.conf | | `scontext=...httpd_t...` | ソースコンテキスト(プロセス側) | httpd_t タイプ | | `tcontext=...user_home_t...` | ターゲットコンテキスト(ファイル側) | user_home_t タイプ | | `tclass=file` | リソースの種類 | 通常ファイル | | `permissive=0` | Enforcing モードで発生 | 実際に拒否 | ::: highlight `scontext` と `tcontext` の**タイプ部分**(`_t` で終わる)がミスマッチしているときが最も多い原因。httpd(`httpd_t`)がホームディレクトリのファイル(`user_home_t`)にアクセスしようとすると拒否される。 ::: ### ファイルのコンテキストを確認する {#check-context} ```bash $ ls -Z /var/www/html/app.conf unconfined_u:object_r:user_home_t:s0 /var/www/html/app.conf ``` このファイルのタイプが `user_home_t` になっているため、`httpd_t` からアクセスできない。本来は `httpd_sys_content_t` であるべき。 ## ausearch で拒否ログを絞り込む {#ausearch} `/var/log/audit/audit.log` を直接 grep するよりも、`ausearch` を使うほうが効率的に絞り込める。 ```bash # 最近(直近10分)の AVC 拒否ログを確認 $ sudo ausearch -m AVC -ts recent # 今日の AVC 拒否ログをすべて確認 $ sudo ausearch -m AVC --start today # 特定のプロセス名で絞り込む $ sudo ausearch -m AVC -ts recent -c httpd # 特定のファイル名で絞り込む $ sudo ausearch -m AVC --start today | grep "app.conf" ``` `-ts recent` は直近10分間のログを表示する。`--start today` は当日0時以降のすべてのログを対象にする。 ## audit2why で原因を特定する {#audit2why} `audit2why` は AVC ログを人間が読みやすい形式で解説してくれるツール。`ausearch` の出力をパイプで渡す。 ```bash $ sudo ausearch -m AVC -ts recent | audit2why ``` ```output type=AVC msg=audit(1748780400.123:1234): avc: denied { read } for pid=1122 comm="httpd" ... Was caused by: Missing type enforcement (TE) allow rule. You can use audit2allow to generate a loadable module to allow this access. ``` 出力に "Missing type enforcement (TE) allow rule." とあれば、ポリシーに許可ルールが定義されていないことが原因。 ::: tip `audit2why` は原因を説明するだけで、修正は行わない。修正方法の提案が必要な場合は `audit2allow` を使う(本番環境での安易な使用は避けること)。 ::: ## よくある対処パターン {#fix-patterns} ### ファイルのコンテキストを正しいタイプに戻す {#restorecon} ファイルを別ディレクトリからコピーした場合などに元のコンテキストが引き継がれる問題が起きる。`restorecon` でデフォルトのコンテキストに戻すのが最初の選択肢。 ```bash # 単一ファイルのコンテキストを復元 $ sudo restorecon -v /var/www/html/app.conf Relabeled /var/www/html/app.conf from unconfined_u:object_r:user_home_t:s0 to system_u:object_r:httpd_sys_content_t:s0 # ディレクトリ配下を再帰的に復元 $ sudo restorecon -Rv /var/www/html/ ``` ### 手動でコンテキストタイプを設定する {#chcon} 一時的にコンテキストを変更したい場合は `chcon` を使う。ただし `restorecon` や再ラベル付けで上書きされる点に注意。 ```bash $ sudo chcon -t httpd_sys_content_t /var/www/html/app.conf ``` ### Boolean でポリシーを切り替える {#setsebool} SELinux ポリシーには **Boolean** という on/off スイッチが多数用意されている。例えば Apache がユーザーホームディレクトリを配信する場合は以下の Boolean を有効にする。 ```bash # httpd 関連の Boolean 一覧を確認 $ getsebool -a | grep httpd # Boolean を永続的に変更(-P で再起動後も維持) $ sudo setsebool -P httpd_enable_homedirs on ``` ::: warning ポリシーを無効化する方向(Disabled への変更や過度な Boolean 有効化)よりも、ファイルのコンテキストを正しく設定する方向を優先すること。 ::: ## まとめ:トラブルシュートの手順 {#summary} 1. `getenforce` でモードを確認 → Enforcing であることを確認 2. `setenforce 0` で Permissive にして問題が消えるか確認 → 消えれば SELinux が原因 3. `sudo ausearch -m AVC -ts recent | audit2why` で拒否の原因を特定 4. `ls -Z` でファイルのコンテキストを確認 5. `restorecon -v` でコンテキストを復元、または `setsebool` で Boolean を調整 6. `setenforce 1` で Enforcing に戻す ::: tip **SELinux は無効化しない** "selinux disabled" という解決策をよく見かけるが、これはセキュリティを完全に無効化する最終手段。ほとんどのケースはコンテキスト修正か Boolean の設定で解決できる。 ::: ## 次に読む {#next} - [ファイル権限の基本 - chmod / chown の使い方](/articles/tutorials/permissions-basics) - [SUID/SGID/Sticky bit - 特殊権限ビットを理解する](/articles/tutorials/suid-sgid-sticky) - [ログファイルの読み方 - システムログ解析入門](/articles/tutorials/log-reading-basics) # seq コマンド入門 - 連番を生成してループに使う Source: https://penguin-gym-linux.com/articles/tutorials/seq-command ## 1 から 100 まで、手で打つ気ですか? {#intro} ::: dialogue @lina: せんぱい、test1.txt から test100.txt まで 100 個ファイルを作りたいです。100 回コマンドを打つしかないのでしょうか。 @linny: 待って、それは大変すぎるよ。`seq` コマンドを使えば「1, 2, 3, ... 100」のような連番を一瞬で作れる。 @linny: ループと組み合わせれば、100 個のファイルも 1 行で作れるんだ。 ::: ::: tip **言葉の整理(先に取りちがえを防ぎます)** - **連番**:1, 2, 3 のように続く数字の並びです。「シーケンス」も同じものを指します。 - **引数**:コマンドのうしろに書く値です。`seq 5` の `5` が引数です。 - **増分**:いくつずつ増やすかを表す数です。「ステップ」「刻み」も同じ意味で使われます。 - **ループ**:同じ処理をくりかえすしくみです。この記事では `for` を使います。 - **ゼロ埋め**:`1` を `01` のように、けたをそろえるために先頭に 0 を足すことです。 - **変数**:値を一時的に入れておく箱です。この記事では `i` という箱に、連番を 1 つずつ入れます。 - **シェル**:ターミナルであなたの入力を受け取り、コマンドを動かす担当です。「bash」も同じものを指す呼び方です。 ::: ## この記事でわかること {#what-you-learn} - `seq` で **連番(1, 2, 3, ...)を作る** 方法がわかります - 開始・終了・**増分(ステップ)** を指定する方法がわかります - `for` ループと組み合わせて **くりかえし処理** を回せるようになります - `-w` で **けたをそろえる**(ゼロ埋め)方法と、`-s` で **区切り文字を変える** 方法がわかります - bash の `{1..10}` 記法との **使い分け** がわかります ## 1. seq コマンドとは? {#what} > **結論**: `seq` は連続した数字(シーケンス)を 1 行に 1 つずつ出力するコマンド。`seq 5` なら 1 から 5 までを生成する。 ::: dialogue @lina: そもそも seq は何の略ですか。 @linny: sequence(シーケンス)の略だよ。連続・並び、という意味の英語だね。 @linny: いちばん簡単な使い方は、数字を 1 つだけ渡すこと。試しに `seq 5` と打ってみよう。 ::: ```bash $ seq 5 ``` ```output 1 2 3 4 5 ``` ::: dialogue @lina: 1 から 5 までが縦に並んで出ました。 @linny: そう。数字を 1 つ渡すと「1 からその数まで」を出力するんだ。 @linny: 1 行に 1 つずつ出るのがポイントだよ。これをループに流しこむと、くりかえし処理になる。 ::: ## 2. 開始する数を変えるには? {#range} > **結論**: 引数を 2 つ渡すと `seq 開始 終了` の意味になる。`seq 3 7` なら 3 から 7 まで生成する。 ::: dialogue @lina: 1 からではなく、5 から 10 までが欲しいときはどうしますか。 @linny: 数字を 2 つ並べるんだ。`seq 5 10` で「5 から 10 まで」になる。 @linny: 引数の数で意味が変わる。ここが `seq` のおもしろいところだね。 ::: ```bash $ seq 5 10 ``` ```output 5 6 7 8 9 10 ``` ::: tip **引数の数で意味が変わる** - `seq 終了` → 1 から終了まで - `seq 開始 終了` → 開始から終了まで - `seq 開始 増分 終了` → 増分(次章)を指定 ::: ## 3. 増分(ステップ)を指定するには? {#step} > **結論**: 引数を 3 つ渡すと `seq 開始 増分 終了` の意味になる。`seq 0 2 10` なら 0 から 10 まで 2 刻みで生成する。 ::: dialogue @lina: 「2, 4, 6, 8」のように 1 つ飛ばしで出したいです。 @linny: 真ん中に増分を入れるんだ。いくつずつ増やすか、という数だね。 @linny: `seq 2 2 10` と書けば「2 から 10 まで 2 きざみ」になるよ。 ::: ```bash $ seq 2 2 10 ``` ```output 2 4 6 8 10 ``` ::: dialogue @lina: 逆に、大きい数から小さい数へ数えることもできますか。 @linny: できるよ。増分をマイナスにするんだ。`seq 5 -1 1` で「5, 4, 3, 2, 1」になる。 ::: ```bash $ seq 5 -1 1 ``` ```output 5 4 3 2 1 ``` ::: tip 小数も使えます。`seq 1 0.5 3` と書くと `1.0, 1.5, 2.0, 2.5, 3.0` のように 0.5 きざみで出ます。 小数がまじると、整数のほうも同じけた数にそろえて表示されます。 ::: ## 4. for ループで連番を使うには? {#loop} > **結論**: `for i in $(seq 1 5)` の形で、生成した連番を 1 つずつ変数 `i` に入れて繰り返せる。連番ファイルの一括作成などに使う。 ::: dialogue @lina: いよいよ 100 個のファイルを作る方法ですね。 @linny: `for` ループと `seq` を合体させるよ。`$(seq 1 5)` の部分は、`seq` を実行した結果に置きかわる。 @linny: つまり `1 2 3 4 5` になるんだ。それを 1 つずつ `i` に入れて、処理をくりかえす。 ::: ```bash $ for i in $(seq 1 5); do > touch "test${i}.txt" > done ``` ```bash $ ls ``` ```output test1.txt test2.txt test3.txt test4.txt test5.txt ``` ::: dialogue @lina: 5 個のファイルが一気にできました。これを 100 にすれば。 @linny: そう、`seq 1 100` にするだけ。100 行打つ作業が 1 行で終わるんだ。 ::: ### 4-1. リナの失敗:`$( )` を付け忘れる 先ほど作ったファイルを、いったん片づけてから試します。こうすれば、新しくできたファイルだけが見えます。 ```bash $ rm test1.txt test2.txt test3.txt test4.txt test5.txt $ ls ``` `ls` に何も出ない状態から、もう一度やってみましょう。 ::: dialogue @lina: 先輩、同じことをしたつもりなのに、変な名前のファイルができました。 ::: ```bash $ for i in seq 1 5; do > touch "test${i}.txt" > done $ ls ``` ```output test1.txt test5.txt testseq.txt ``` ::: dialogue @lina: `testseq.txt` って何ですか。しかも 5 個できるはずが 3 個です。 @linny: `$( )` が抜けているね。`$( )` を付けないと、シェルは `seq` `1` `5` を **ただの 3 つの言葉** として読むんだ。 @lina: えっ、コマンドとして実行してくれないんですか。 @linny: そう。だから `i` には順に `seq`、`1`、`5` が入る。その結果が `testseq.txt`、`test1.txt`、`test5.txt` の 3 個なんだ。 @lina: なるほど、ファイル名を見れば何が起きたかわかるんですね。納得しました。 @linny: `$( )` は「中のコマンドを実行して、その結果に置きかえる」という書き方。連番を回したいときは必ず付けてね。 ::: ::: warning `seq` は本サイトの仮想ターミナルにはまだありません。この記事の例は、手元の Linux やターミナルで試してください。 作ってしまったファイルは、名前を確かめてから消せます。消す前に必ず `ls` で対象を見てください。 ```bash $ ls test*.txt $ rm test1.txt test5.txt testseq.txt ``` `rm` で消したファイルは、GUI とちがってゴミ箱に入りません。その場で完全に消えます。不安なときは `rm -i` を使ってください。1 件ずつ確認しながら消せます。 ::: ## 5. 桁をそろえる(ゼロ埋め)には? {#padding} > **結論**: `-w` を付けると最大桁数に合わせて先頭をゼロで埋める。`file01` `file02` ... `file10` のように桁がそろい、並び順が崩れない。 ::: dialogue @lina: さっきの test1.txt から test100.txt を `ls` で並べたら、test1, test10, test100, test2 という順になりました。 @linny: それはけた数がばらばらだからだね。`ls` は名前を文字として左から見くらべる。だから `10` が `2` より前に来てしまうんだ。 @linny: `-w` を使うと `001` `002` ... `100` のようにゼロ埋めしてくれる。width、つまり幅をそろえる、という意味だよ。 @lina: けたがそろえば、文字として並べても順番が崩れないんですね。 ::: ```bash $ seq -w 1 10 ``` ```output 01 02 03 04 05 06 07 08 09 10 ``` ::: dialogue @lina: 全部 2 けたにそろいました。 @linny: いちばん大きい数、ここでは 10 が 2 けただからね。それに合わせて全部 2 けたになるんだ。 @linny: 100 まで指定すれば 3 けた、つまり 001 から 100 にそろうよ。 ::: ::: tip ファイル作成と組み合わせるなら次のように書く。 ```bash for i in $(seq -w 1 100); do touch "log_${i}.txt"; done ``` `log_001.txt` 〜 `log_100.txt` が桁そろえで作られる。 ::: ## 6. 区切り文字や書式を変えるには? {#format} > **結論**: `-s` で改行以外の区切り文字(カンマ・スペース等)に変えられる。`-f` で printf 形式の書式を指定できる。 ::: dialogue @lina: 縦ではなく「1,2,3,4,5」と横に並べたいときはどうしますか。 @linny: `-s` を使う。separator、つまり区切り文字という意味だよ。`-s ,` でカンマ区切りになる。 ::: ```bash $ seq -s , 1 5 ``` ```output 1,2,3,4,5 ``` ::: dialogue @lina: 横一列になりました。スペース区切りもできますか。 @linny: `-s " "` と書けばスペース区切りになるよ。 @linny: さらに `-f` を使えば、数字の前に文字を付けたり、けた数を決めたりもできる。 ::: ```bash $ seq -f "page-%02g" 1 3 ``` ```output page-01 page-02 page-03 ``` ::: tip `-f` の `%g` は数値を表す書式です。`%02g` の `02` は「2 けた・ゼロ埋め」という指定です。 ゼロ埋めだけが目的なら、前の章の `-w` のほうが手軽です。`-f` は数字の前に文字を付けたり、小数のけたを決めたりできます。 ::: ## 7. bash の {1..10} とどう違う? {#brace} > **結論**: bash には連番を作る `{1..10}` という記法もある。固定値なら `{1..10}` が速い。**範囲を変数で指定したいとき** は `seq` を使う。 ::: dialogue @lina: 前に `echo {1..5}` で連番が出るのを見たことがあります。`seq` と何がちがうのですか。 @linny: いいところに気づいたね。`{1..5}` は bash のブレース展開というしくみで、`seq` より速いんだ。 @linny: ただし弱点がある。**変数を使うと展開されない** んだよ。 ::: ```bash $ echo {1..5} ``` ```output 1 2 3 4 5 ``` ```bash $ n=5 $ echo {1..$n} ``` ```output {1..5} ``` ::: dialogue @lina: あれ、`{1..$n}` は連番にならず、そのまま文字で出てしまいました。 @linny: そう。ブレース展開は、変数を入れると動かないんだ。 @linny: いっぽう `seq` なら `seq 1 $n` で変数がきちんと効く。だから、くりかえす回数が変数で決まる場面では `seq` が活躍するよ。 ::: ```bash $ n=5 $ seq 1 $n ``` ```output 1 2 3 4 5 ``` ::: warning `for i in $(seq 1 $n)` は、大きな数を指定すると連番すべてをメモリに広げます。 数万から数百万回のループなら、`for ((i=1; i<=n; i++))` という算術ループのほうが効率的です。ふだんの数十から数千回くらいなら `seq` で問題ありません。 ::: ## 8. よくあるつまずきと対処 {#pitfalls} > **結論**: つまずきの大半は「引数の数の勘違い」「`$( )` の付け忘れ」「ゼロ埋め忘れによる並び順崩れ」の 3 つ。 | 症状 | 原因 | 対処 | | --------------------------------- | ------------------------------ | ------------------------------------- | | 思った範囲と違う数が出る | 引数の数で意味が変わるのを誤解 | `seq 開始 増分 終了` の順序を確認 | | ループが `seq 1 5` の文字列を回す | `$( )` を付け忘れた | `for i in $(seq 1 5)` と書く | | ファイルの並び順が崩れる | ゼロ埋めしていない | `seq -w` で桁をそろえる | | `{1..$n}` が連番にならない | ブレース展開は変数非対応 | `seq 1 $n` を使う | | 横並びにしたいのに縦に出る | デフォルトの区切りが改行 | `-s ,` や `-s " "` で区切り文字を変更 | ::: warning **やってはいけないこと** - とても大きな範囲を `$(seq ...)` で一気に広げて、メモリを圧迫します - ゼロ埋めをせずに連番ファイルを作り、あとで並び順に悩みます ::: ## 9. ミニ課題:実際にやってみよう {#exercise} > **結論**: きざみの指定・ゼロ埋め・ループとの組み合わせの 3 問で、`seq` の使い方を手で確かめる。 ::: dialogue @lina: 使い方はわかりました。手を動かして確かめたいです。 @linny: いいね、3 問用意したよ。まず練習用のディレクトリを作って、その中で試してみて。 ::: ```bash $ mkdir -p ~/seq-practice && cd ~/seq-practice ``` **課題 1**:10 から 1 へ向かって、2 ずつ減る数を表示しよう。 :::details ヒント 1(方向づけ)を見る 引数を 3 つ書きます。減らしたいので、真ん中の数はマイナスにします。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `seq` です。引数は `seq 開始 増分 終了` の順に書きます。 ::: :::details 答えを見る ```bash $ seq 10 -2 1 ``` ```output 10 8 6 4 2 ``` 終わりの数は 1 ですが、2 の次は 0 になり 1 を下回ります。だから最後は 2 で止まります。 ::: **課題 2**:1 から 12 までを、2 けたのゼロ埋めで表示しよう。 :::details ヒント 1(方向づけ)を見る けたをそろえるためのオプションが 1 つあります。第 5 章で出てきました。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `seq` です。けたをそろえるオプションは width の頭文字です。 ::: :::details 答えを見る ```bash $ seq -w 1 12 ``` ```output 01 02 03 04 05 06 07 08 09 10 11 12 ``` いちばん大きい数が 12 で 2 けたなので、全体が 2 けたにそろいます。 ::: **課題 3**:`report_01.txt` から `report_12.txt` までの 12 個のファイルを、けたをそろえて一括作成しよう。 :::details ヒント 1(方向づけ)を見る 課題 2 で作った連番を、ループに流しこみます。ファイルを作るコマンドと組み合わせます。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `for` ループと `touch` です。ループに `seq` の結果を渡すときの書き方を、第 4 章で確かめてください。 ::: :::details 答えを見る ```bash $ for i in $(seq -w 1 12); do touch "report_${i}.txt"; done $ ls report_*.txt | head -n 3 ``` ```output report_01.txt report_02.txt report_03.txt ``` 作られた数も確かめてみましょう。 ```bash $ ls report_*.txt | wc -l ``` ```output 12 ``` 練習が終わったら、作ったディレクトリごと片づけられます。中身を確かめてから消してください。 ```bash $ cd ~ $ ls ~/seq-practice $ rm -r ~/seq-practice ``` `rm -r` はディレクトリを中身ごと消します。実行する前に、パスが `~/seq-practice` であることを必ず確かめてください。 ::: ## 10. 振り返り {#review} ::: dialogue @lina: 整理します。`seq` は引数の数で意味が変わるんですね。1 つなら終わり、2 つなら開始と終わり、3 つなら間が増分。 @linny: そのとおり。ここを覚えておけば、思ったとおりの範囲が出せるよ。 @lina: ループで使うときは `$( )` を必ず付ける。ファイル名をそろえたいときは `-w` ですね。 @linny: 完璧だね。範囲を変数で決めたいときは `{1..10}` ではなく `seq` を使う。そこも忘れずに。 ::: ## 今日の 3 行まとめ {#three-lines} 1. `seq` は引数の数で意味が変わる。`seq 終了` / `seq 開始 終了` / `seq 開始 増分 終了` 2. ループで使うときは `$( )` を付ける。忘れると `seq` という文字がそのまま回る 3. けたをそろえたいときは `-w`。範囲を変数で決めたいときは `{1..10}` ではなく `seq` ## まとめ / 次に読む {#next} - [パイプとリダイレクトの基本](/articles/tutorials/pipe-redirect-basics) - [ファイルを分割する split コマンド](/articles/tutorials/split-command) - [ファイル作成の基本(mkdir / touch / echo)](/articles/tutorials/file-creation-basics) # シェルスクリプトの書き方入門 - Bash変数・条件分岐・ループ Source: https://penguin-gym-linux.com/articles/tutorials/shell-scripting-basics **シェルスクリプト**とは、ターミナルで打つコマンドをファイルにまとめて、まとめて実行できるようにしたものです。「シェルスクリプト」「bash スクリプト」「シェルプログラム」はいずれも同じものを指します。 毎回手で打っていた作業を 1 ファイルにしておけば、次回は 1 回の実行で済みます。この基礎編では、Bash スクリプトの基本構文から条件分岐、ループ処理までを扱います。 ## この記事で身につくこと {#what-you-will-learn} - シェバンと実行権限を理解して、スクリプトを自分で作成・実行できるようになります。 - 変数・条件分岐・ループを組み合わせて、繰り返し作業を自動化できるようになります。 - よくある構文エラーの原因を切り分けて、自分で直せるようになります。 **想定読者**:ターミナルでコマンドを打った経験があり、同じ作業の繰り返しを自動化したい人。 **前提**:`$HOME` のような変数の読み方を扱います。未習なら [環境変数の基礎](/articles/tutorials/environment-variables) を先に読んでください。 ### 先に用語を整理する {#terms} 初めて出てくる言葉を、ここで一度だけ定義します。 - **シェル**とは、打ったコマンドを受け取って実行してくれるプログラムです。Linux では **Bash**(バッシュ)が標準です。 - **シェバン**とは、スクリプトの 1 行目に書く `#!/bin/bash` のことです。「シバン」「hashbang」「ハッシュバン」とも呼びます。この行でどのシェルに実行させるかを指定します。 - **実行権限**とは、そのファイルをプログラムとして起動してよいという許可です。付いていないスクリプトは `Permission denied` で止まります。 - **変数**とは、値に名前を付けて置いておく箱です。`name="値"` で入れて、`$name` で取り出します。 - **引用符**(クォート)とは、`"` や `'` のことです。値に空白が入る場合は引用符で囲まないと、シェルが別々の単語として解釈します。 - **終了ステータス**とは、コマンドが終わったときに返す 0〜255 の数値です。0 が成功、0 以外が失敗を意味します。「終了コード」「exit status」「リターンコード」とも呼びます。 - **コマンド置換**とは、`$(date)` のようにコマンドの実行結果をその場に埋め込む書き方です。 - **算術展開**とは、`$((1 + 2))` のように計算結果を埋め込む書き方です。シェルは何もしないと数値も文字列として扱うため、計算にはこの書き方が必要です。扱えるのは整数のみで、小数は切り捨てられます。 ## はじめてのシェルスクリプト {#getting-started} > **結論**: #!/bin/bashのシェバンで始まり、chmod +xで実行権限を付けてから./script.shで実行する。 ### 基本構造 {#basic-structure} ```bash #!/bin/bash # これはコメントです echo "Hello, Shell Script!" echo "現在の日時: $(date)" echo "ユーザー名: $USER" ``` ### スクリプトの作成と実行 {#create-run} **ステップ1: ファイル作成** ```bash $ nano hello.sh ``` 上記のコードを入力して保存する。 **ステップ2: 実行権限の付与** ```bash $ chmod +x hello.sh ``` **ステップ3: スクリプト実行** ```bash $ ./hello.sh ``` ```output Hello, Shell Script! 現在の日時: Sat Jan 11 14:30:00 JST 2025 ユーザー名: user ``` `date` の表示形式はロケール設定で変わる。日本語ロケール(`ja_JP.UTF-8`)の環境では `2025年 1月11日 土曜日 14時30分00秒 JST` のように出る。 `./hello.sh` の `./` は「今いるディレクトリの」という意味。これを省くとシェルは `PATH` の中だけを探すため、`command not found` になる。 ::: danger **スクリプトは書いた内容を無条件に実行する** スクリプトは確認画面を出さない。中に `rm` があれば、そのまま削除が走る。**失敗するとどうなるか**は次の 2 点。 - 中身を読まずに実行したスクリプトが、削除や上書きを含んでいた場合、取り消せない。 - `sudo ./script.sh` で実行すると、スクリプト内の全操作が root 権限で走る。システムファイルまで壊せる状態になる。 **安全に試す方法**は次の 3 つ。 - 練習は `mkdir ~/script-practice && cd ~/script-practice` で作った専用ディレクトリの中だけで行う。 - 他人が書いたスクリプトは `cat script.sh` で中身を読んでから実行する。 - 最初は `sudo` を付けずに実行する。権限エラーが出てから、なぜ必要なのかを考える。 ::: ### シェバン(Shebang)について {#shebang} `#!/bin/bash` は**シェバン**(shebang)と呼ばれ、このファイルを実行するインタープリター(実行を担当するプログラム)を指定する。 - `#!/bin/bash` — Bashを使用 - `#!/bin/sh` — POSIXシェルを使用 - `#!/usr/bin/env bash` — 環境から自動検出 シェバンを書き忘れると、実行するシェルによって挙動が変わる。特に `#!/bin/sh` を指定した場合、Bash 固有の書き方(`[[ ]]` や配列)は使えない。迷ったら `#!/bin/bash` と書いておけばよい。 ## 変数と入力 {#variables} > **結論**: 変数はname="値"で定義して$nameで参照し、readコマンドで対話的なユーザー入力を受け付けられる。 ### 変数の基本 {#variable-basics} ```bash #!/bin/bash # 変数の定義(=の前後にスペースを入れない) name="Linux User" age=25 today=$(date +%Y-%m-%d) # 変数の使用 echo "名前: $name" echo "年齢: ${age}歳" echo "今日の日付: $today" ``` **計算の例** ```bash #!/bin/bash num1=10 num2=3 sum=$((num1 + num2)) diff=$((num1 - num2)) product=$((num1 * num2)) quotient=$((num1 / num2)) echo "足し算: $num1 + $num2 = $sum" echo "引き算: $num1 - $num2 = $diff" echo "掛け算: $num1 × $num2 = $product" echo "割り算: $num1 ÷ $num2 = $quotient" ``` ```output 足し算: 10 + 3 = 13 引き算: 10 - 3 = 7 掛け算: 10 × 3 = 30 割り算: 10 ÷ 3 = 3 ``` 割り算が 3 になるのは誤りではない。**シェルの算術展開は整数演算で、小数は切り捨てられる**(`$((10 / 3))` は 3)。小数が必要な場合は `bc` か `awk` を使う。 ```bash echo "scale=2; 10/3" | bc # 3.33 awk 'BEGIN {print 10/3}' # 3.33333 ``` ### 特殊変数 {#special-variables} | 変数 | 説明 | 例 | | ------------- | ------------------------------ | ---------------------- | | `$0` | スクリプト名 | ./script.sh | | `$1, $2, ...` | コマンドライン引数 | 第1引数、第2引数 | | `$#` | 引数の個数 | 3個の引数なら3 | | `$@` | すべての引数 | "arg1" "arg2" "arg3" | | `$?` | 直前のコマンドの終了ステータス | 成功なら0、失敗なら非0 | | `$$` | 現在のプロセスID | 12345 | | `$USER` | 現在のユーザー名 | user | | `$HOME` | ホームディレクトリ | /home/user | | `$PWD` | 現在のディレクトリ | /home/user/scripts | ### ユーザー入力 {#user-input} **基本的な入力** ```bash #!/bin/bash echo "あなたの名前を入力してください:" read name echo "こんにちは、${name}さん!" ``` **プロンプト付き入力** ```bash #!/bin/bash read -p "年齢を入力してください: " age read -s -p "パスワードを入力してください: " password echo echo "年齢: $age" echo "パスワードは非表示で入力されました" ``` **複数の値を一度に入力** ```bash #!/bin/bash echo "名前と年齢をスペース区切りで入力:" read name age echo "名前: $name, 年齢: $age" ``` ## 条件分岐 {#conditionals} > **結論**: if [ 条件 ]; then〜fiの形でファイル存在・数値比較・文字列比較の条件分岐をcase文も含めて記述できる。 ### if文 {#if-statement} **基本的なif文** ```bash #!/bin/bash read -p "数値を入力してください: " num if [ "$num" -gt 0 ]; then echo "$num は正の数です" elif [ "$num" -lt 0 ]; then echo "$num は負の数です" else echo "$num はゼロです" fi ``` **ファイル存在チェック** ```bash #!/bin/bash filename="test.txt" if [ -f "$filename" ]; then echo "$filename は存在します" echo "ファイルサイズ: $(wc -c < "$filename") bytes" else echo "$filename は存在しません" echo "ファイルを作成します..." touch "$filename" fi ``` ### 条件演算子 {#conditional-operators} **数値比較** | 演算子 | 意味 | 例 | | ------ | ---------- | --------------- | | `-eq` | 等しい | `[ $a -eq $b ]` | | `-ne` | 等しくない | `[ $a -ne $b ]` | | `-gt` | より大きい | `[ $a -gt $b ]` | | `-ge` | 以上 | `[ $a -ge $b ]` | | `-lt` | より小さい | `[ $a -lt $b ]` | | `-le` | 以下 | `[ $a -le $b ]` | **文字列比較** | 演算子 | 意味 | 例 | | ------ | ---------- | ------------------ | | `=` | 等しい | `[ "$a" = "$b" ]` | | `!=` | 等しくない | `[ "$a" != "$b" ]` | | `-z` | 空文字列 | `[ -z "$str" ]` | | `-n` | 空でない | `[ -n "$str" ]` | **ファイルテスト** | 演算子 | 意味 | 例 | | ------ | ------------ | ----------------- | | `-f` | 通常ファイル | `[ -f file.txt ]` | | `-d` | ディレクトリ | `[ -d /home ]` | | `-e` | 存在する | `[ -e path ]` | | `-r` | 読み取り可能 | `[ -r file ]` | | `-w` | 書き込み可能 | `[ -w file ]` | | `-x` | 実行可能 | `[ -x script ]` | ### case文 {#case-statement} ```bash #!/bin/bash echo "操作を選択してください:" echo "1) ファイル一覧表示" echo "2) 現在時刻表示" echo "3) システム情報表示" echo "4) 終了" read -p "選択 (1-4): " choice case $choice in 1) echo "=== ファイル一覧 ===" ls -la ;; 2) echo "=== 現在時刻 ===" date ;; 3) echo "=== システム情報 ===" uname -a ;; 4) echo "終了します。" exit 0 ;; *) echo "無効な選択です。" ;; esac ``` ## ループ処理 {#loops} > **結論**: for i in {1..5}やwhile [ 条件 ]でループし、breakで脱出・continueで次の反復へ移行できる。 ### forループ {#for-loop} **基本的なforループ** ```bash #!/bin/bash # 数値の範囲 for i in {1..5} do echo "カウント: $i" done echo "---" # ファイル処理 for file in *.txt do echo "処理中: $file" wc -l "$file" done ``` **配列を使ったループ** ```bash #!/bin/bash fruits=("apple" "banana" "orange" "grape") echo "果物リスト:" for fruit in "${fruits[@]}" do echo "- $fruit" done ``` **C言語風forループ** ```bash #!/bin/bash echo "九九表の一部:" for ((i=1; i<=5; i++)) do for ((j=1; j<=5; j++)) do result=$((i * j)) printf "%2d " $result done echo done ``` ### whileループ {#while-loop} **基本的なwhileループ** ```bash #!/bin/bash count=1 while [ $count -le 5 ] do echo "ループ $count 回目" count=$((count + 1)) done ``` **ファイル読み込み** ```bash #!/bin/bash filename="data.txt" if [ -f "$filename" ]; then while IFS= read -r line do echo "読み込み: $line" done < "$filename" else echo "ファイル $filename が見つかりません" fi ``` ### untilループ {#until-loop} ```bash #!/bin/bash count=1 until [ $count -gt 5 ] do echo "カウント: $count" count=$((count + 1)) done ``` ### ループ制御 {#loop-control} - **break** — ループを抜ける - **continue** — 次の反復へ ```bash #!/bin/bash for i in {1..10} do if [ $i -eq 3 ]; then echo "3はスキップ" continue fi if [ $i -eq 8 ]; then echo "8で終了" break fi echo "数値: $i" done ``` ## よくある間違いと落とし穴 {#pitfalls} > **結論**: 変数代入時の=前後スペース禁止・条件式の[ ]内スペース必須・文字列比較での引用符が主な落とし穴。 ### 間違い1: 変数定義時のスペース {#mistake-var-space} ::: warning **NG(エラーになる)** ```bash name = "太郎" # = の前後にスペース age= 25 # = の後にスペース city ="東京" # = の前にスペース ``` 「command not found」エラーになる。 ::: ::: tip **OK(正しい書き方)** ```bash name="太郎" # = の前後にスペースなし age=25 city="東京" ``` 変数の代入では `=` の前後にスペースを入れない。 ::: ### 間違い2: 変数参照時の{}忘れ {#mistake-var-braces} ::: warning **NG(予期しない結果)** ```bash filename="test" echo "$filenameback.txt" # ".txt" だけが出力される($filenameback は未定義) echo "$filename_backup" # 何も出力されない($filename_backup は未定義) ``` シェルは `$filenameback` までを 1 つの変数名として読む。`$filename` + `back` とは解釈されない。 ::: ::: tip **OK(正しい書き方)** ```bash filename="test" echo "${filename}back.txt" # testback.txt echo "${filename}_backup" # test_backup ``` `{}` で変数名の範囲を明確にする。 ::: ### 間違い3: if文の条件式でのスペース不足 {#mistake-if-space} ::: warning **NG(構文エラー)** ```bash if [$num -gt 5]; then # [の後ろにスペースなし if [ $num -gt 5]; then # ]の前にスペースなし ``` ::: ::: tip **OK(正しい書き方)** ```bash if [ $num -gt 5 ]; then # [ ] の内側にスペース必須 if [[ $num -gt 5 ]]; then # [[ ]] を使う場合も同様 if (( num > 5 )); then # 算術式の場合 ``` ::: ### 間違い4: 文字列比較での引用符忘れ {#mistake-str-quote} ::: warning **NG(危険な例)** ```bash if [ $name = John Doe ]; then # スペースが含まれると問題 if [ $empty_var = "" ]; then # 空の場合エラー ``` ::: ::: tip **OK(安全な書き方)** ```bash if [ "$name" = "John Doe" ]; then # 両辺を引用符で囲む if [ "$empty_var" = "" ]; then # 空でもエラーにならない if [ -z "$var" ]; then # 空文字チェックの専用オプション ``` ::: ### 間違い5: コマンド置換での古い記法 {#mistake-cmd-sub} ::: warning **NG(非推奨)** ```bash date=`date` # バッククォート result=`cat `which ls`` # ネストでエラー ``` ::: ::: tip **OK(現代的な記法)** ```bash date=$(date) # $() を使用 result=$(cat $(which ls)) # ネストが容易 ``` ::: ### 間違い6: 算術演算での間違い {#mistake-arithmetic} ::: warning **NG(計算されない)** ```bash result = $num1 + $num2 # "result" コマンドとして解釈され command not found sum="$a + $b" # 計算されない ``` ::: ::: tip **OK(正しい計算方法)** ```bash result=$((num1 + num2)) # 算術展開 let "result = num1 + num2" # let コマンド使用 ``` ::: ### 間違いを防ぐための基本ルール {#prevent-mistakes} **記述の基本** - 変数代入時: `=` の前後にスペースを入れない - 変数使用時: 常に引用符で囲む `"$var"` - 複雑な変数: `{}` で囲む `"${var}"` **条件文の基本** - `[ ]` の使用: 内側に必ずスペースを入れる - 文字列比較: 両辺を引用符で囲む - 数値比較: `-eq`, `-gt`, `-lt` などを使用 ### デバッグとトラブルシューティング {#debugging} スクリプトが期待どおり動かないときは、推測せずに `set -x` で実行過程を表示させる。変数がどう展開されたかが 1 行ずつ見えるため、原因が特定しやすい。 **`set -x`:実行過程を表示する** ```bash #!/bin/bash set -x # ここから実行するコマンドを表示 filename="data.txt" echo "DEBUG: filename = [$filename]" if [ ! -f "$filename" ]; then echo "ERROR: ファイル $filename が見つかりません" >&2 exit 1 fi ``` ```output + filename=data.txt + echo 'DEBUG: filename = [data.txt]' DEBUG: filename = [data.txt] + '[' '!' -f data.txt ']' + echo 'ERROR: ファイル data.txt が見つかりません' ERROR: ファイル data.txt が見つかりません + exit 1 ``` `+` で始まる行が、シェルが実際に実行したコマンド。変数が展開されたあとの姿が見えるため、「変数が空だった」「引用符が効いていなかった」といった原因をその場で判別できる。値を `[ ]` で囲んで出力しているのは、空文字列と空白 1 文字を区別するため。 **`set -e` / `set -u`:異常を早い段階で止める** ```bash #!/bin/bash set -e # コマンドが失敗した時点で停止 set -u # 未定義変数を参照した時点で停止 ``` この 2 つは「気づかないまま処理が進む」ことを防ぐための設定。特に `set -u` は変数名のタイポ検出に効く。 ::: warning `set -u` を有効にしたスクリプトで未定義の変数を参照すると、その行で `unbound variable` エラーになって停止する。これは意図した挙動であり、`set -x` の不具合ではない。 ```output script.sh: line 5: var: unbound variable ``` 停止させたくない箇所では、`${var:-}`(未定義なら空文字列として扱う)のように既定値を書く。 ::: **よくあるエラーメッセージと対処法** ### 症状: `command not found` **原因**: 変数代入時に `=` の前後へスペースを入れている。または `./` を付けずに実行している。 **確認**: ```bash bash -n script.sh # 構文だけをチェック(実行しない) ``` **対処**: `=` の前後のスペースを削る。実行時は `./script.sh` の形にする。 ### 症状: `Permission denied` **原因**: スクリプトに実行権限が付いていない。 **確認**: ```bash ls -l script.sh ``` `-rw-r--r--` のように `x` がなければ実行権限なし。 **対処**: ```bash chmod +x script.sh ``` ### 症状: `syntax error near unexpected token` **原因**: `[ ]` の内側のスペース不足、引用符の閉じ忘れ、`then` / `fi` / `done` の記述漏れ。 **確認**: ```bash bash -n script.sh ``` **対処**: エラーが指す行番号の前後を見直す。`if` には `then` と `fi`、`for` / `while` には `do` と `done` が対で必要。 ### 症状: 変数が空になる **原因**: 変数名の直後に文字が続き、シェルが別の変数名として解釈している。 **確認**: ```bash echo "DEBUG: var = [$var]" ``` `[]` で囲むと、空文字列なのか空白が入っているのかを判別できる。 **対処**: `${var}` の形で変数名の範囲を明示する。 ## 作業完了チェックリスト {#checklist} - [ ] 1 行目にシェバン(`#!/bin/bash`)を書いた - [ ] `chmod +x` で実行権限を付けた - [ ] 変数代入の `=` の前後にスペースを入れていない - [ ] 変数参照を `"$var"` の形で引用符で囲んだ - [ ] 練習用ディレクトリの中で実行し、`sudo` を付けずに動作を確認した - [ ] 動かない場合は `bash -n` と `set -x` で原因を切り分けた ## 次に読む {#next} - [シェルスクリプト実践編](/articles/tutorials/shell-scripting-practical) — 関数・配列・ファイル操作・実践例 - [プロセス管理(基礎編)](/articles/tutorials/process-management-basics) - [Linux基本コマンド10選](/articles/tutorials/basic-commands) # シェルスクリプト実践 - 関数・配列・ファイル操作の応用テクニック Source: https://penguin-gym-linux.com/articles/tutorials/shell-scripting-practical シェルスクリプトの基礎をマスターしたら、次は**実践的な高度技術**です。この記事では関数・配列・ファイル操作・エラー処理を扱います。最後に、業務でそのまま使える実用例を 3 本紹介します。 **用語の整理**(この記事で使う言葉を先に定義します) - **関数**: 処理に名前を付けてまとめたものです。同じ処理を何度も書かずに済みます - **ローカル変数**: 関数の中だけで有効な変数です。`local` を付けて宣言します。付けないとスクリプト全体で共有される**グローバル変数**になります - **配列**: 複数の値を 1 つの変数にまとめたものです。`${arr[0]}` のように番号で取り出します - **連想配列**: 番号ではなく文字列をキーにする配列です。「ハッシュ」「辞書」も同じものを指します。Bash 4.0 以降で使えます - **終了コード**: コマンドが終わるときに返す数値です。`0` が成功、それ以外が失敗を意味します。「終了ステータス」「exit status」も同じものです - **厳格モード**: `set -euo pipefail` のことです。失敗を見逃さずスクリプトを止めるための設定です - **`trap`**: 特定の出来事(エラー発生、スクリプト終了など)が起きたときに実行する処理を登録する仕組みです - **サブシェル**: 親のシェルから分岐して作られる別プロセスのシェルです。中で変数を変えても親側には戻りません ## この記事でわかること {#what-you-will-learn} - `function_name() {}` による関数定義と `local` を使ったスコープ管理 - 配列の定義・要素アクセス・全展開・連想配列の使い方 - ファイルの行単位読み込み・書き込み・CSV 処理の実装方法 - `set -euo pipefail` と `trap` を使った堅牢なエラー処理 - バックアップ・ログ監視・ファイル整理の実践スクリプト例 ## 関数 {#functions} > **結論**: function_name() {}で関数を定義しlocalでスコープを限定、echoの結果を$(func)でキャプチャして返す。 ### 関数の定義と呼び出し {#function-define} **基本的な関数** `$1` は関数に渡された 1 つめの引数です。`$2` なら 2 つめになります。 ```bash #!/bin/bash greet() { echo "こんにちは、$1さん!" } greet "太郎" greet "花子" ``` ```output こんにちは、太郎さん! こんにちは、花子さん! ``` **戻り値のある関数** シェルの関数は、他の言語のように値を `return` できません。`return` で返せるのは 0〜255 の終了コードだけです。値を返したいときは `echo` で出力し、呼び出し側で `$(関数名)` を使って受け取ります。 ```bash #!/bin/bash square() { local num=$1 local result=$((num * num)) echo $result } number=5 result=$(square $number) echo "$number の平方は $result です" ``` **複数引数の関数** ```bash #!/bin/bash file_info() { local filepath=$1 if [ -f "$filepath" ]; then echo "ファイル: $filepath" echo "サイズ: $(wc -c < "$filepath") bytes" echo "行数: $(wc -l < "$filepath") lines" echo "最終更新: $(stat -c %y "$filepath")" else echo "ファイル $filepath が存在しません" return 1 fi } file_info "/etc/passwd" file_info "nonexistent.txt" ``` ### 高度な関数 {#function-advanced} **ローカル変数とグローバル変数** ```bash #!/bin/bash global_var="グローバル" demo_scope() { local local_var="ローカル" global_var="変更されたグローバル" echo "関数内: local_var = $local_var" echo "関数内: global_var = $global_var" } echo "関数呼び出し前: global_var = $global_var" demo_scope echo "関数呼び出し後: global_var = $global_var" echo "関数外: local_var = $local_var" # 空になる ``` **再帰関数** ```bash #!/bin/bash factorial() { local n=$1 if [ $n -le 1 ]; then echo 1 else local prev=$(factorial $((n - 1))) echo $((n * prev)) fi } for i in {1..5}; do result=$(factorial $i) echo "$i! = $result" done ``` **エラーハンドリング付き関数** ```bash #!/bin/bash safe_mkdir() { local dir_path=$1 if [ -z "$dir_path" ]; then echo "エラー: ディレクトリパスが指定されていません" >&2 return 1 fi if [ -d "$dir_path" ]; then echo "ディレクトリ $dir_path は既に存在します" return 0 fi if mkdir -p "$dir_path" 2>/dev/null; then echo "ディレクトリ $dir_path を作成しました" return 0 else echo "エラー: ディレクトリ $dir_path の作成に失敗しました" >&2 return 1 fi } safe_mkdir "/tmp/test_dir" safe_mkdir "/root/forbidden" # 権限エラーの例 ``` ## 配列 {#arrays} > **結論**: arr=()で配列定義し${arr[0]}で要素取得、${arr[@]}で全展開、${#arr[@]}で要素数を取得できる。 ### 配列の基本操作 {#array-basic} **配列の作成と要素アクセス** ```bash #!/bin/bash fruits=("apple" "banana" "orange" "grape") numbers=(1 2 3 4 5) echo "最初の果物: ${fruits[0]}" echo "3番目の数字: ${numbers[2]}" echo "全ての果物: ${fruits[@]}" echo "果物の数: ${#fruits[@]}" ``` **配列への要素追加と削除** ```bash #!/bin/bash colors=("red" "green" "blue") echo "初期配列: ${colors[@]}" colors+=("yellow") echo "追加後: ${colors[@]}" unset 'colors[1]' # "green"を削除(クォートしないとファイル名として展開されることがある) echo "削除後: ${colors[@]}" echo "インデックス: ${!colors[@]}" ``` ```output 初期配列: red green blue 追加後: red green blue yellow 削除後: red blue yellow インデックス: 0 2 3 ``` `unset` した後は添字が飛びます(`1` が欠番)。`${#colors[@]}` は 3 になりますが、`${colors[1]}` は空です。番号で回すと欠番で取りこぼすため、`"${colors[@]}"` か `"${!colors[@]}"` を使ってください。 **連想配列(Bash 4.0以降)** ```bash #!/bin/bash declare -A person person["name"]="田中太郎" person["age"]="30" person["city"]="東京" echo "名前: ${person["name"]}" echo "年齢: ${person["age"]}" echo "全キー: ${!person[@]}" ``` ```output 名前: 田中太郎 年齢: 30 全キー: city age name ``` 連想配列のキーの並び順は保証されません。定義した順とは無関係な順序で返ります。順序が必要な場合は、キーの配列を別に持つか `sort` を通してください。 **配列の実践的な使用例** ```bash #!/bin/bash log_patterns=("/var/log/*.log" "/tmp/*.log" "$HOME/*.log") for pattern in "${log_patterns[@]}"; do echo "パターン: $pattern" files=($pattern) if [ ${#files[@]} -gt 0 ] && [ -f "${files[0]}" ]; then for file in "${files[@]}"; do if [ -f "$file" ]; then size=$(wc -c < "$file") echo " - $file ($size bytes)" fi done else echo " - マッチするファイルなし" fi done ``` ## ファイル操作 {#file-operations} > **結論**: while IFS= read -r lineでファイル行単位読み込み、ヒアドキュメントやmapfileで効率的に書き込める。 ### ファイルの読み書き {#file-rw} **ファイル読み込みの様々な方法** ```bash #!/bin/bash filename="data.txt" # 方法1: while read を使用 while IFS= read -r line || [ -n "$line" ]; do echo "行: $line" done < "$filename" # 方法2: 配列への読み込み mapfile -t lines < "$filename" for i in "${!lines[@]}"; do echo "行 $((i+1)): ${lines[i]}" done ``` **ファイル書き込み** ```bash #!/bin/bash output_file="output.txt" echo "新しいファイル内容" > "$output_file" echo "追加の行1" >> "$output_file" cat << EOF >> "$output_file" 複数行のテキスト 2行目 3行目 EOF cat "$output_file" ``` **CSVファイルの処理** ```bash #!/bin/bash csv_file="employees.csv" cat << EOF > "$csv_file" 名前,年齢,部署 田中,30,開発部 佐藤,25,営業部 鈴木,35,管理部 EOF tail -n +2 "$csv_file" | while IFS=',' read -r name age dept; do echo "社員: $name (${age}歳) - $dept" done ``` ```output 社員: 田中 (30歳) - 開発部 社員: 佐藤 (25歳) - 営業部 社員: 鈴木 (35歳) - 管理部 ``` この例はパイプの右側で変数を書き換えないため、[サブシェルの問題](#mistake-pipeline)は起きません。集計値を外に持ち出す場合はリダイレクトに変えてください。 **ファイル操作の高度な例** ```bash #!/bin/bash source_dir="/path/to/source" backup_dir="/path/to/backup" backup_files() { local src="$1" local dest="$2" if [ ! -d "$src" ]; then echo "エラー: ソースディレクトリが存在しません: $src" return 1 fi mkdir -p "$dest" rsync -av --delete "$src/" "$dest/" echo "バックアップ完了: $src -> $dest" } log_file="/tmp/backup.log" { echo "バックアップ開始: $(date)" backup_files "$source_dir" "$backup_dir" echo "バックアップ終了: $(date)" } >> "$log_file" 2>&1 ``` ::: danger **`rsync --delete` は転送先のファイルを消す** `--delete` は「転送元に無いファイルを転送先から削除する」オプションです。転送元のパスを間違えると、転送先にあったデータが消えます。特に、空のディレクトリを転送元に指定すると転送先が空になります。 さらに `rsync` は末尾のスラッシュで意味が変わります。`rsync -a src/ dest/` は src の**中身**を dest に置きます。`rsync -a src dest/` は dest の中に `src` ディレクトリごと置きます。 **安全なやり方** 1. 先に `--dry-run` を付けて実行し、何が削除されるかを確認する(`rsync -av --delete --dry-run "$src/" "$dest/"`) 2. 転送元のディレクトリが存在し、空でないことを確認してから本実行する 3. 世代を残したい場合は `--delete` ではなく `--backup --backup-dir=` を使う ::: ## エラー処理 {#error-handling} > **結論**: set -euo pipefailで即座エラー終了・未定義変数エラーを有効化し、trapでエラー行番号を記録する。 ### エラー処理のベストプラクティス {#error-best} **set オプションによる厳密なエラー処理** `set -euo pipefail` は 3 つの設定をまとめて有効にします。どれも「失敗に気づかないまま処理を続ける」事故を防ぐためのものです。 ```bash #!/bin/bash set -euo pipefail # set -e: コマンドが失敗したら即座に終了 # set -u: 未定義変数を使用したらエラー # set -o pipefail: パイプラインで一つでも失敗したらエラー trap 'echo "エラーが発生しました: 行番号 $LINENO" >&2' ERR echo "正常処理1" ``` **手動エラーチェック** ```bash #!/bin/bash process_file() { local filename="$1" if [ ! -f "$filename" ]; then echo "エラー: ファイル '$filename' が見つかりません" >&2 return 1 fi if [ ! -r "$filename" ]; then echo "エラー: ファイル '$filename' の読み取り権限がありません" >&2 return 1 fi local line_count if ! line_count=$(wc -l < "$filename" 2>/dev/null); then echo "エラー: 行数カウントに失敗しました" >&2 return 1 fi echo "ファイル '$filename' の行数: $line_count" return 0 } if process_file "test.txt"; then echo "処理が正常に完了しました" else echo "処理が失敗しました" exit 1 fi ``` **ログ記録付きエラー処理** ```bash #!/bin/bash LOG_FILE="/tmp/script.log" log() { local level="$1" shift local message="$*" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE" } handle_error() { local exit_code=$? local line_no=$1 log "ERROR" "スクリプトがエラーで終了しました (終了コード: $exit_code, 行: $line_no)" exit $exit_code } trap 'handle_error $LINENO' ERR log "INFO" "スクリプト開始" log "INFO" "スクリプト終了" ``` ## 実践的なスクリプト例 {#practical} > **結論**: バックアップ・ログ監視・ファイル整理の3つの実践例でset -euo pipefail・trap・配列の実戦活用を学べる。 ### 例1: システムバックアップスクリプト {#example-backup} ```bash #!/bin/bash set -euo pipefail BACKUP_ROOT="/backup" LOG_FILE="/var/log/backup.log" RETENTION_DAYS=7 DATE=$(date +%Y%m%d_%H%M%S) BACKUP_DIRS=( "/etc" "/home" "/var/www" "/usr/local/bin" ) log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" } cleanup() { log "古いバックアップの削除を開始" find "$BACKUP_ROOT" -type f -name "backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete log "古いバックアップの削除完了" } main_backup() { local backup_file="$BACKUP_ROOT/backup_$DATE.tar.gz" log "バックアップ開始: $backup_file" mkdir -p "$BACKUP_ROOT" if tar -czf "$backup_file" "${BACKUP_DIRS[@]}" 2>/dev/null; then local size=$(du -h "$backup_file" | cut -f1) log "バックアップ成功: $backup_file (サイズ: $size)" else log "エラー: バックアップの作成に失敗しました" exit 1 fi } if [ "$(id -u)" -ne 0 ]; then echo "このスクリプトはroot権限で実行してください" exit 1 fi log "システムバックアップ処理開始" main_backup cleanup log "システムバックアップ処理完了" ``` ::: danger **削除を含むスクリプトは、まず削除なしで動かす** `cleanup()` の `find ... -delete` は確認なしにファイルを消します。`BACKUP_ROOT` の値を間違えたまま実行すると、無関係なディレクトリのファイルが消えます。 **安全な導入手順** 1. `-delete` を `-print` に置き換えて実行し、対象一覧を目で確認する 2. 一覧が意図どおりだと確認できてから `-delete` に戻す 3. `-name "backup_*.tar.gz"` の絞り込みを外さない。外すと対象が一気に広がる 4. 初回は `RETENTION_DAYS` を長め(例: 90)に設定し、動作を確認してから短くする `find` の式は左から順に評価されます。`-delete` を条件より前に書くと、条件が効かないまま起点ディレクトリ配下がすべて削除されます。`-name` などの条件は必ず `-delete` の**前**に書いてください。 ::: ### 例2: ログ監視スクリプト {#example-monitor} ```bash #!/bin/bash set -euo pipefail LOG_FILE="/var/log/syslog" ALERT_PATTERNS=("ERROR" "CRITICAL" "FAILED") CHECK_INTERVAL=10 LAST_CHECK_FILE="$HOME/.log_monitor_last_check" # 固定の /tmp よりホームディレクトリが安全 send_alert() { local message="$1" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] ALERT: $message" >> "/var/log/alerts.log" logger "LOG_MONITOR_ALERT: $message" } monitor_logs() { local last_position=0 if [ -f "$LAST_CHECK_FILE" ]; then last_position=$(cat "$LAST_CHECK_FILE") fi local current_size=$(wc -c < "$LOG_FILE") if [ "$current_size" -gt "$last_position" ]; then local new_lines=$(tail -c +$((last_position + 1)) "$LOG_FILE") for pattern in "${ALERT_PATTERNS[@]}"; do if echo "$new_lines" | grep -q "$pattern"; then local matches=$(echo "$new_lines" | grep "$pattern") send_alert "検出されたパターン '$pattern': $matches" fi done echo "$current_size" > "$LAST_CHECK_FILE" fi } echo "ログ監視開始: $LOG_FILE" while true; do monitor_logs sleep "$CHECK_INTERVAL" done ``` ### 例3: ファイル整理スクリプト {#example-organize} ```bash #!/bin/bash set -euo pipefail SOURCE_DIR="$HOME/Downloads" ORGANIZE_ROOT="$HOME/Organized" LOG_FILE="/tmp/file_organizer.log" declare -A FILE_TYPES=( ["pdf"]="Documents/PDF" ["doc,docx"]="Documents/Word" ["jpg,jpeg,png,gif"]="Images" ["mp4,avi,mov"]="Videos" ["zip,rar,7z,tar,gz"]="Archives" ) log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" } get_file_type() { local filename="$1" local extension="${filename##*.}" extension=$(echo "$extension" | tr '[:upper:]' '[:lower:]') for types in "${!FILE_TYPES[@]}"; do if [[ ",$types," == *",$extension,"* ]]; then echo "${FILE_TYPES[$types]}" return 0 fi done echo "Others" } organize_files() { log "ファイル整理開始: $SOURCE_DIR" if [ ! -d "$SOURCE_DIR" ]; then log "エラー: ソースディレクトリが存在しません: $SOURCE_DIR" exit 1 fi local file_count=0 while IFS= read -r -d '' file; do if [ -f "$file" ]; then local file_type=$(get_file_type "$(basename "$file")") local dest_dir="$ORGANIZE_ROOT/$file_type" mkdir -p "$dest_dir" if [ -e "$dest_dir/$(basename "$file")" ]; then log "スキップ(同名ファイルあり): $(basename "$file")" continue fi mv "$file" "$dest_dir/" log "移動完了: $(basename "$file") -> $file_type/" file_count=$((file_count + 1)) fi done < <(find "$SOURCE_DIR" -maxdepth 1 -type f -print0) log "ファイル整理完了: $file_count 個のファイルを処理しました" } organize_files ``` ::: warning **`mv` は同名ファイルを黙って上書きする** 移動先に同じ名前のファイルがあると、`mv` は確認せずに上書きします。上のスクリプトでは移動前に `[ -e ... ]` で存在を確認し、あればスキップしてログに残しています。 `mv -n`(`--no-clobber`)でも上書きは防げますが、スクリプトに組み込む場合は注意が必要です。新しい GNU coreutils(9.x)はスキップ時に終了コード 1 を返すため、`set -e` と併用すると同名ファイルが 1 件あった時点でスクリプト全体が止まります。上書きせずに両方残したい場合は、`-n` を外して `mv --backup=numbered` を使ってください(`--backup` と `-n` は同時に指定できません)。 初回実行時は `mv` を `echo mv` に置き換えて実行し、どのファイルがどこへ動くかを先に確認することを勧めます。ファイル整理は一度実行すると元の配置に戻すのが困難です。 ::: ## よくある間違いと落とし穴 {#pitfalls} > **結論**: 配列展開は"${arr[@]}"・スコープはlocal・パイプ変数はリダイレクト・エラー連鎖防止が実践の落とし穴。 ### 間違い1: 配列の誤った使用 {#mistake-array} ::: warning **NG(スペースで分割される)** ```bash files=("file1.txt" "file 2.txt" "file3.txt") for file in $files; do # スペース区切りで分割される echo $file done ``` ::: ::: tip **OK(正しい展開)** ```bash files=("file1.txt" "file 2.txt" "file3.txt") for file in "${files[@]}"; do # 配列として正しく展開 echo "$file" done ``` `"${array[@]}"` で配列全体を安全に展開する。 ::: ### 間違い2: 関数のスコープ理解不足 {#mistake-scope} ::: warning **NG(グローバル変数が意図せず変更される)** ```bash counter=0 increment() { counter=$((counter + 1)) # グローバル変数を変更 local result=$counter echo $result } increment echo "Global counter: $counter" # 意図しない変更 ``` ::: ::: tip **OK(適切なスコープ管理)** ```bash global_counter=0 increment() { local local_counter=$1 local_counter=$((local_counter + 1)) echo $local_counter } result=$(increment $global_counter) global_counter=$result ``` ::: ### 間違い3: パイプラインでの変数変更の罠 {#mistake-pipeline} ::: warning **NG(変数変更が反映されない)** パイプの右側はサブシェル(別プロセス)で実行されます。そこで変えた変数は、パイプを抜けると元に戻ります。 ```bash count=0 cat file.txt | while IFS= read -r line; do count=$((count + 1)) done echo "Lines: $count" # 0のまま(サブシェルで実行されるため) ``` ::: ::: tip **OK(リダイレクションを使用)** ```bash count=0 while IFS= read -r line; do count=$((count + 1)) done < file.txt echo "Lines: $count" # 正しくカウントされる # または count=$(wc -l < file.txt) ``` ::: ### 間違い4: エラー処理の不備 {#mistake-error} ::: warning **NG(エラーが連鎖する)** ```bash cp source.txt backup.txt rm source.txt # コピーが失敗していても削除 curl -o data.json http://api.example.com/data process_data data.json # ダウンロード失敗でも処理続行 ``` ::: ::: tip **OK(堅牢なエラー処理)** ```bash set -euo pipefail if cp source.txt backup.txt; then echo "バックアップ成功" rm source.txt else echo "エラー: バックアップに失敗しました" >&2 exit 1 fi ``` ::: ### 間違い5: セキュリティを考慮しない実装 {#mistake-security} ::: warning **NG(セキュリティ上の問題)** `eval` は渡された文字列をそのままコマンドとして実行します。利用者が `rm -rf ~` と入力すれば、そのとおりに実行されます。予測できる名前の一時ファイルは、他の利用者に先回りして作られる余地を残します(シンボリックリンク攻撃)。コマンドラインに書いたパスワードは `ps` や履歴に残ります。 ```bash read -p "コマンドを入力: " user_command eval $user_command # 任意のコマンド実行(危険) temp_file="/tmp/script_data.txt" # 予測可能なファイル名 echo "sensitive data" > $temp_file mysql -u user -p'password123' -e "SELECT * FROM users" # ps の出力とシェル履歴に残る ``` ::: ::: tip **OK(セキュアな実装)** ```bash # 入力値の検証 read -p "ファイル名を入力: " filename if [[ "$filename" =~ ^[a-zA-Z0-9._-]+$ ]]; then echo "処理: $filename" else echo "エラー: 無効なファイル名" >&2 exit 1 fi # 安全な一時ファイル作成 temp_file=$(mktemp) trap 'rm -f "$temp_file"' EXIT # 認証情報は設定ファイルに置き、コマンドラインには出さない # ~/.my.cnf(chmod 600 で保護) # [client] # user=appuser # password=... mysql --defaults-extra-file="$HOME/.my.cnf" -e "SELECT * FROM users" ``` ::: ### 間違い6: パフォーマンスを考慮しない実装 {#mistake-performance} ::: warning **NG(非効率な処理)** ```bash for file in /path/to/large/directory/*; do wc -l "$file" # ファイルごとにプロセス起動 done for i in {1..1000}; do current_time=$(date +%s) # 毎回dateコマンド実行 echo "Processing $i at $current_time" done ``` ::: ::: tip **OK(効率的な実装)** ```bash # バッチ処理で効率化 find /path/to/large/directory -name "*" -type f -exec wc -l {} + # 一度だけ取得して再利用 start_time=$(date +%s) for i in {1..1000}; do current_time=$((start_time + i)) echo "Processing $i at $current_time" done ``` ::: ## ベストプラクティス {#best-practices} > **結論**: 変数名の明確化・mktemp一時ファイル・入力値バリデーション・set -xデバッグが堅牢なスクリプトの基本。 ### コーディング規約 {#coding-standards} - 変数名は意味のある名前を使います - 関数は 1 つの責任に絞ります - コメントには「なぜそうするか」を書きます - インデントを統一します(2 スペース推奨) ### セキュリティ {#security} - 入力値の検証を必ず実施します - 一時ファイルは `mktemp` で作ります。名前が予測されないため、他者に先回りされません - 権限は最小限に設定します - パスワードやトークンをスクリプトに直接書きません ### デバッグ {#debugging} - `set -x` を付けると、実行したコマンドが 1 行ずつ表示されます - 適切なログ出力を実装します - 段階的にテストしながら開発します - ShellCheck などの静的解析ツールを使います。引用符の付け忘れなど、事故につながる書き方を実行前に検出できます ## まとめ {#summary} シェルスクリプトの実践技術を身につけると、**効率的で信頼性の高い自動化**が実現できます。破壊的な操作を含むスクリプトは、必ず `--dry-run` や `-print` で対象を確認してから本実行してください。 - **関数** でコードの再利用性と保守性を向上 - **配列** で複雑なデータ処理を効率化 - **適切なエラー処理** で堅牢なスクリプトを作成 - **実践例** を参考に業務に応用 ## 次に読む {#next} - [シェルスクリプト基礎編](/articles/tutorials/shell-scripting-basics) — 変数・条件分岐・ループの基本 - [プロセス管理(実践編)](/articles/tutorials/process-management-practical) — ジョブコントロール・pkill・nice - [ファイル操作(応用編)](/articles/tutorials/file-operations-advanced) # sort と uniq の使い方 - データを並べ替えて重複を削る Source: https://penguin-gym-linux.com/articles/tutorials/sort-uniq-basics ## この記事で学べること {#intro} - `sort` で **行を並べ替える** 基本操作がわかります。辞書順・数値順・逆順の 3 つです - `uniq` で **重複行を削る** 使い方がわかります。「先に `sort` が要る」という決まりも身につきます - `sort | uniq -c | sort -rn` という **頻度ランキングの定番形** が書けるようになります - 初心者がつまずく **「`uniq` だけでは消えない」「数字が変な順に並ぶ」** の理由がわかります ::: tip **言葉の整理(先に取りちがえを防ぎます)** - **行**:ファイルの中の 1 行のことです。改行から改行までのひとかたまりを指します。 - **フィールド**:1 行の中を空白で区切ったときの、それぞれの部分です。「列」「カラム」もほぼ同じ意味で使われます。この記事では「列」にそろえます。 - **辞書順**:文字を 1 文字ずつ左から見くらべる並べ方です。「アルファベット順」「文字列順」も同じものを指します。 - **数値順**:行の中身を数として見くらべる並べ方です。辞書順とは結果が変わります。ここが最初の関門です。 - **重複**:まったく同じ中身の行が 2 つ以上あることです。 - **パイプ**:`|` の記号です。前のコマンドの結果を、次のコマンドにそのまま渡すしくみです。 - **リダイレクト**:`>` の記号です。画面に出るはずの結果を、ファイルに書き出します。「上書き保存」とは動きがちがう点に注意してください。7-3 でくわしく説明します。 ::: ::: tip **結論(先に覚える型)** - 並べ替えたい → `sort` - 並べ替え+重複を消したい → `sort -u` - 「何が何回出たか」を集計したい → `sort | uniq -c | sort -rn` ::: ::: warning **前提(対象環境)** - OS:Ubuntu / 一般的な Linux - GNU coreutils の `sort` / `uniq`(macOS の BSD 版と細部の挙動が異なるオプションあり) - 並べ替えの順番は **ロケール**(言語と地域の設定)で変わります。この記事の出力は `LC_ALL=C` の場合のものです。`LC_ALL=C` は、文字を文字コードの番号どおりに見くらべる設定です ::: ## 1. まずはここから:行を「並べ替える」とは {#what-is-sort} > **結論**: `sort` は元ファイルを壊さず並べ替え結果を表示する。辞書順・数値順・逆順の 3 つを押さえる。 ::: dialogue @lina: 先輩、ログやリストを「アルファベット順に並べ替えたい」ことがよくあります。あれはどうやるのですか。 @linny: そういうときの基本コマンドが `sort` だよ。`sort ファイル名` と書くと、**1 行ずつ並べ替えて表示** してくれる。 @linny: 大事なのは、中身を書きかえるわけではないという点。並べ替えた結果を画面に出すだけなんだ。だから打ちまちがえても、元のファイルは壊れないよ。 @lina: 元ファイルにはさわらないんですね。それなら安心です。 @linny: そう。並べ替えには **辞書順・数値順・逆順** の 3 種類がある。これだけ覚えれば 8 割は対応できるよ。 ::: サンプルとして次のファイルを用意しよう。 ```bash $ cat fruits.txt ``` ```output banana apple cherry apple banana date ``` ### 1-1. 基本:辞書順に並べ替える ```bash $ sort fruits.txt ``` ```output apple apple banana banana cherry date ``` ::: highlight **ポイント** - `sort` は **はじめの設定では辞書順** に並べます - 大文字と小文字は、別の文字としてあつかわれます。どちらが先に来るかはロケールで変わります - 元のファイルは **変わりません**。並べ替えた結果を画面に出すだけです ::: ### 1-2. 逆順(降順)に並べ替える `-r` ```bash $ sort -r fruits.txt ``` ```output date cherry banana banana apple apple ``` `-r` は **r**everse(逆)の頭文字です。 ## 2. 数値の並べ替えで初心者がハマる罠 {#numeric-sort} > **結論**: `sort` のデフォルトは文字列比較。数値として正しく並べたいときは `-n` を付ける。 ::: dialogue @lina: 数字を並べ替えたら、順番が変になりました。 @linny: ちょうどいい例だね。実際に見てみよう。 ::: ```bash $ cat scores.txt ``` ```output 100 3 25 9 1000 ``` ```bash $ sort scores.txt ``` ```output 100 1000 25 3 9 ``` ::: dialogue @lina: えっ、`100` が `25` より先に来ています。`3` と `9` は最後です。これはバグですか。 @linny: バグではないよ。`sort` は、はじめの設定では **文字列として 1 文字ずつ左から見くらべる**。 @linny: つまり最初の 1 文字だけで見ると、`1` は `2` や `3` より小さい。だから `100` や `1000` が前に来るんだ。 @lina: なるほど、数として見ていなかったんですね。びっくりしました。 @linny: 数として並べたいときは `-n` を付ける。それだけで直るよ。 ::: ### 2-1. 数値ソート `-n` ```bash $ sort -n scores.txt ``` ```output 3 9 25 100 1000 ``` `-n` は **n**umeric(数値)の頭文字です。 ::: warning **初心者あるある** - ログのサイズや件数を並べるとき、`-n` を忘れて変な順序になります - 「数字の列なら `-n` を付ける」と覚えておくと事故が減ります ::: ### 2-2. 数値の大きい順(降順) ```bash $ sort -nr scores.txt ``` ```output 1000 100 25 9 3 ``` `-n` と `-r` は **組み合わせられます**。ランキングを作るときによく出てくる形です。 ## 3. 並べ替え+重複削除 `sort -u` {#sort-u} > **結論**: `sort -u` は並べ替えと重複削除を 1 コマンドで行う。ユニークな一覧が欲しいときの近道。 ```bash $ sort -u fruits.txt ``` ```output apple banana cherry date ``` ::: dialogue @lina: あれ、`apple` と `banana` が 1 行ずつになりました。 @linny: そう。`-u` は **u**nique(一意)の頭文字だよ。`sort` の結果から重複した行を取りのぞいてくれる。 @linny: 「並べ替えてユニークにしたい」だけなら、**これ 1 つで済む** んだ。 ::: ::: tip 実務では「ユニークな値の一覧が欲しい」場面がとても多いです。`sort -u` を覚えておくと早く書けます。 ::: ## 4. uniq:重複を削る専門コマンド {#uniq} > **結論**: `uniq` は連続した重複しか削れない。必ず `sort` の後ろに置く。`-c` で出現回数を数えられる。 ### 4-1. リナの失敗:`uniq` だけでは消えない ```bash $ uniq fruits.txt ``` ```output banana apple cherry apple banana date ``` ::: dialogue @lina: あれっ、`apple` も `banana` もまだ重複しています。コマンドが効いていないのでしょうか。 @linny: 効いてはいるよ。ただし `uniq` は **「となり合っている重複だけ」** を削るんだ。 @lina: えっ、となり合っているかどうかで結果が変わるんですか。 @linny: そう。このファイルでは `apple` が 2 行目と 4 行目にある。間に `cherry` がはさまっているから、`uniq` から見ると別のかたまりなんだ。 @lina: なるほど、離れた重複は見つけてもらえないんですね。納得しました。では、どうすればいいですか。 @linny: 先に `sort` する。`sort` で同じ行を **となり合わせ** にしてから `uniq` に渡す。そうすれば重複がきちんと消えるよ。 ::: ::: highlight **なぜ `sort` が先に必要なのか** `uniq` は 1 行ずつ上から読み、**直前の行とだけ** 見くらべます。ファイル全体を覚えているわけではありません。 そのため、同じ行が離れた場所にあると別ものとして残ります。`sort` は同じ行をとなり合わせに集めます。だから `sort` を先に通すと `uniq` が働けるようになります。 ::: ### 4-2. `sort | uniq` の組み合わせ ```bash $ sort fruits.txt | uniq ``` ```output apple banana cherry date ``` ::: highlight **鉄則** - `uniq` は **かならず `sort` の後ろに置きます** - 単独で使うのは「すでに並んでいるとわかっているとき」だけです - 「並べてユニークにする」だけが目的なら、`sort -u` のほうが短く書けます ::: ### 4-3. 各行が何回出たかを数える `uniq -c` ```bash $ sort fruits.txt | uniq -c ``` ```output 2 apple 2 banana 1 cherry 1 date ``` `-c` は **c**ount(数える)の頭文字です。行の先頭に **出現回数** が付きます。集計にとても便利です。 ### 4-4. 重複している行だけ・1 回だけの行だけ ```bash # 重複している行だけ表示 $ sort fruits.txt | uniq -d ``` ```output apple banana ``` ```bash # 1 回しか出ていない行だけ表示 $ sort fruits.txt | uniq -u ``` ```output cherry date ``` | オプション | 意味 | 用途 | | ---------- | ---------------- | -------------------------- | | `-c` | カウント付与 | 集計 | | `-d` | 重複行だけ | 重複している項目を洗い出す | | `-u` | ユニーク行だけ | 1 回しか出ない行を抽出 | | `-i` | 大文字小文字無視 | 表記ゆれをまとめる | ::: warning `sort` にも `uniq` にも `-u` があります。ただし意味が少しちがいます。`sort -u` は「重複を 1 行にまとめる」、`uniq -u` は「1 回しか出ない行だけを残す」です。取りちがえに気をつけてください。 ::: ## 5. 実務でいちばん使う型:頻度ランキング {#ranking} > **結論**: `sort | uniq -c | sort -rn` の 3 段パイプが頻度ランキングの定番。`head` で上位 N 件に絞れる。 ::: dialogue @lina: アクセスログで「どの IP からのアクセスが多いか」を出したいです。どうすればいいですか。 @linny: それが今日のクライマックスだよ。`sort | uniq -c | sort -rn` という **3 段パイプ** が定番。 @linny: この形は丸暗記していい。なぜ 3 段になるかは、このあと分解して説明するね。 ::: サンプルログ: ```bash $ cat access.log ``` ```output 192.168.1.10 192.168.1.20 192.168.1.10 192.168.1.30 192.168.1.10 192.168.1.20 ``` 頻度ランキング: ```bash $ sort access.log | uniq -c | sort -rn ``` ```output 3 192.168.1.10 2 192.168.1.20 1 192.168.1.30 ``` ::: highlight **3 段パイプの分解** | 段 | コマンド | やっていること | | --- | ---------- | ------------------------------------ | | 1 | `sort` | 同じ行を隣り合わせにする | | 2 | `uniq -c` | 隣り合った重複をまとめてカウント付き | | 3 | `sort -rn` | カウント(数値)の大きい順に並べる | 1 段目の `sort` を省くと、2 段目の `uniq -c` が数えまちがえます。離れた同じ行を、別のものとして数えてしまうからです。 3 段目でもう一度 `sort` を使うのは、並べ替える対象が変わるからです。2 段目までは行の中身で並べていました。3 段目では、新しく付いたカウントの数で並べ直します。 ::: ### 5-1. 上位 N 件だけ欲しい ```bash $ sort access.log | uniq -c | sort -rn | head -n 3 ``` `head -n 3` で **上位 3 件** にしぼれます。`head` と組み合わせるのが現場の定番です。 ## 6. キー指定(応用):特定の列で並べ替える `-k` {#key} > **結論**: `-k2` で 2 列目を基準に並べ替えできる。数値列なら `-n`、区切り変更は `-t` を併用する。 空白区切りや CSV のデータでは、`-k` を使います。**何列目で並べ替えるか** を指定できます。 ```bash $ cat sales.txt ``` ```output apple 120 banana 80 cherry 200 date 50 ``` ```bash # 2 列目(数値)で降順に並べ替える $ sort -k2 -nr sales.txt ``` ```output cherry 200 apple 120 banana 80 date 50 ``` ::: tip - `-k2` で 2 列目を基準にします - 数値の列なら `-n` を忘れずに付けます - 区切り文字を変えたいときは `-t,`(カンマ区切り)のように書きます ::: ## 7. よくある初心者のつまずき {#pitfalls} > **結論**: 重複が消えないのは `sort` 忘れ、数字が変なのは `-n` 忘れが原因。自分自身への上書きは厳禁。 ### 7-1. `uniq` を使ったのに重複が消えない **原因**:`sort` をしていません。 ```bash # NG: 連続していない重複は消えない $ uniq fruits.txt # OK $ sort fruits.txt | uniq $ sort -u fruits.txt ``` ### 7-2. 数字が変な順に並ぶ **原因**:`-n` を付けていません。文字列としての比較になっています。 ```bash $ sort -n scores.txt # 数値として並べる ``` ### 7-3. 元ファイルが書き換わらない `sort` は **画面に結果を出すだけ** です。元ファイルは変わりません。書きかえたいときは **自分でリダイレクトします**。 ```bash $ sort fruits.txt > fruits-sorted.txt ``` ::: warning **やってはいけないこと** ```bash # NG: ファイルが空になる $ sort fruits.txt > fruits.txt ``` `>` は **コマンドを実行する前にファイルを空にします**。そのため `sort` が読む前に中身が消えます。GUI の「上書き保存」とは動きがちがう点に注意してください。 同じファイルに書き戻したいときは、別ファイルに出してから差しかえます。または `sort -o` を使います。 ```bash # OK: -o は読み終わってから書き込む $ sort -o fruits.txt fruits.txt ``` 本サイトの仮想ターミナルは学習用です。あなたのパソコンのファイルは壊れません。安心して試してください。 ::: ### 7-4. 大文字・小文字が別物扱いされる ```bash $ cat names.txt ``` ```output Alice bob Alice BOB ``` ```bash $ LC_ALL=C sort -u names.txt ``` ```output Alice BOB bob ``` `LC_ALL=C` を付けたのは、並び順をこの記事と同じにそろえるためです。付けないと、ロケールによっては `bob` が `BOB` より先に来ます。 大文字と小文字を区別したくないときは `-f`(**f**old case)を付けます。 ```bash $ LC_ALL=C sort -uf names.txt ``` ```output Alice bob ``` ## 8. ミニ課題:実際にやってみよう {#exercise} > **結論**: ユニーク化・出現回数集計・降順ランキングの 3 問で、`sort` と `uniq` の連携を手で確かめる。 ::: dialogue @lina: 知識は入りました。手を動かして確かめたいです。 @linny: いいね、3 問用意したよ。ターミナルで試してみて。 ::: **課題 1**:次のファイルからユニークな単語の一覧を出そう。 ```bash $ cat << 'EOF' > words.txt apple banana apple cherry banana EOF ``` :::details ヒント 1(方向づけ)を見る 「並べ替え」と「重複削除」を 1 回でやってくれるオプションがありました。第 3 章を思い出してください。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `sort` です。並べ替えと重複削除を同時にやるオプションが 1 つあります。`sort --help` で探してみてください。 ::: :::details 答えを見る ```bash $ sort -u words.txt ``` ```output apple banana cherry ``` ::: **課題 2**:上のファイルから **各単語が何回出たか** を集計しよう。 :::details ヒント 1(方向づけ)を見る 数える前に、同じ単語をとなり合わせに集める必要があります。つまり 2 段のパイプになります。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `sort` と `uniq` です。`uniq` には回数を数えるオプションがあります。 ::: :::details 答えを見る ```bash $ sort words.txt | uniq -c ``` ```output 2 apple 2 banana 1 cherry ``` ::: **課題 3**:上の集計結果を **多い順(降順)** に並べ替えて、上位 2 件だけ表示しよう。 :::details ヒント 1(方向づけ)を見る カウントの列は数字です。数として大きい順に並べ直します。そのあと先頭から 2 件だけ取り出します。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `sort` と `head` です。`sort` には数値として見るオプションと、逆順にするオプションがあります。 ::: :::details 答えを見る ```bash $ sort words.txt | uniq -c | sort -rn | head -n 2 ``` ```output 2 banana 2 apple ``` `apple` と `banana` はどちらも 2 回で同じ数です。回数が同じときは、行全体を見くらべた結果で並びます。`-r` が付いているので逆順になり、`banana` が先に来ます。 ::: ## 9. コピペ用テンプレート {#templates} > **結論**: 並べ替え・重複削除・頻度ランキング・列指定・安全な上書きのよく使う型を手元に置いておく。 ::: tip **よく使う型をまとめておく** ```bash # 並べ替え(辞書順) sort file.txt # 並べ替え+重複削除 sort -u file.txt # 数値として並べ替え(昇順 / 降順) sort -n file.txt sort -nr file.txt # 重複行のカウント sort file.txt | uniq -c # 頻度ランキング(多い順) sort file.txt | uniq -c | sort -rn # 頻度ランキング上位 10 件 sort file.txt | uniq -c | sort -rn | head -n 10 # 2 列目で降順 sort -k2 -nr file.txt # 大文字小文字を無視してユニーク化 sort -uf file.txt # 元ファイルにそのまま書き戻す(事故防止: -o を使う) sort -o file.txt file.txt ``` ::: ## 10. 振り返り {#review} ::: dialogue @lina: 整理します。`uniq` はとなり合った行しか見ないから、先に `sort` で集めておくんですね。 @linny: そのとおり。この順番だけは理由まで覚えておくといいよ。 @lina: 数字を並べるときは `-n`。付け忘れると文字として比べられてしまう。ここも大丈夫です。 @linny: 完璧だね。あとは `sort file > file` をしないこと。中身が消えてしまうからね。 ::: ## 今日の 3 行まとめ {#three-lines} 1. `uniq` はとなり合った重複しか削らない。だから先に `sort` で同じ行を集める 2. 数字を並べるときは `-n` を付ける。付けないと文字として比べられる 3. 頻度ランキングは `sort | uniq -c | sort -rn` の 3 段パイプ ## まとめ:次に読む {#next} - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [head・tail・パイプの使い方](/articles/tutorials/file-operations-advanced) - [find・grep・awkの使い方入門](/articles/tutorials/find-grep-awk-basics) # split コマンド入門 - 大きなファイルを分割・結合する Source: https://penguin-gym-linux.com/articles/tutorials/split-command ## 大きすぎるファイル、どうする? {#intro} ::: dialogue @lina: せんぱい、5GB のログファイルを USB メモリに入れようとしたら「ファイルが大きすぎます」と言われました。 @linny: それは `split` コマンドの出番だね。大きなファイルを小さなかたまりに切り分けられるんだ。 @linny: しかも、あとで元どおりにくっつけられる。一緒に見ていこう。 ::: ::: tip **言葉の整理(先に取りちがえを防ぎます)** - **分割**:1 つのファイルを複数の小さなファイルに切り分けることです。元のファイルは残ります。 - **結合**:切り分けたファイルを順番につないで、元の 1 つに戻すことです。 - **接頭辞**:作られるファイル名の先頭に付く文字列です。「プレフィックス」も同じものを指します。 - **サフィックス**:ファイル名の末尾に付く文字です。`part_aa` の `aa` がそれにあたります。 - **ハッシュ値**:ファイルの中身から計算される、指紋のような文字列です。「チェックサム」もほぼ同じ意味で使われます。 ::: ## この記事でわかること {#what-you-learn} - `split` で大きなファイルを **サイズ・行数・個数** で分割する方法がわかります - 分割したファイルを `cat` で **元どおりに結合** する方法がわかります - 連番のサフィックス(`xaa` ではなく `part_01`)を付けられるようになります - 分割・結合のあとに **ファイルが壊れていないか** 確かめられるようになります ## 1. split コマンドとは? {#what} > **結論**: `split` は 1 つのファイルを複数の小さなファイルに分割するコマンド。`cat` で連結すれば完全に元のファイルへ戻せる。 ::: dialogue @lina: そもそも分割すると、ファイルが壊れてしまわないでしょうか。 @linny: 大丈夫。`split` は中身を読んで別のファイルに書き出すだけなんだ。元のファイルはそのまま残るよ。 @linny: しかも切り分けたものを順番につなげれば、1 バイトも欠けずに元に戻せる。 @lina: それなら安心しました。どんなときに使うのですか。 @linny: 容量に制限のあるメディアに入れたいときだね。大きなファイルを少しずつ転送したいときにも使う。 @linny: サイズの大きいログを、あつかいやすく小分けしたいときにも便利だよ。まずはいちばん簡単な形を見てみよう。 ::: まずは練習用のファイルを用意します。 ```bash # 50MB の練習用ファイルを作る(中身は 0 が並んだだけのダミー) $ head -c 50M /dev/zero > bigfile.dat ``` `/dev/zero` は「0 をいくらでも出してくれる特別なファイル」です。`head -c 50M` で先頭 50MB 分だけ取り出し、`>` で `bigfile.dat` に書き出しています。 ::: warning 同じことを `dd` というコマンドで書いている記事も多くあります。`dd` は書き込み先を `of=` で指定します。 ここでは `dd` を使いません。`of=` にディスクの名前を書いてしまうと、そのディスクを直接上書きしてしまうためです。GUI のようにゴミ箱へ入ることも、確認画面が出ることもありません。パソコンが起動しなくなることもあります。 `head -c` なら書き込み先はふつうのファイルだけです。初心者のうちはこちらを使ってください。 ::: ```bash $ ls -lh bigfile.dat ``` ```output -rw-r--r-- 1 user user 50M Jun 5 10:00 bigfile.dat ``` ## 2. サイズで分割するには? {#by-size} > **結論**: `split -b サイズ 元ファイル 接頭辞` でサイズ指定。`-b 100M` なら 100MB ごと、`-b 10M` なら 10MB ごとに切り分けられる。 ::: dialogue @lina: さっそく 10MB ずつに分けてみたいです。 @linny: `-b` を使うよ。bytes、つまりバイトの頭文字だね。 @linny: 最後に書いた `part_` は接頭辞だよ。できるファイルの名前の先頭に付く文字列だね。 ::: ```bash $ split -b 10M bigfile.dat part_ ``` ```bash $ ls -lh part_* ``` ```output -rw-r--r-- 1 user user 10M Jun 5 10:01 part_aa -rw-r--r-- 1 user user 10M Jun 5 10:01 part_ab -rw-r--r-- 1 user user 10M Jun 5 10:01 part_ac -rw-r--r-- 1 user user 10M Jun 5 10:01 part_ad -rw-r--r-- 1 user user 10M Jun 5 10:01 part_ae ``` ::: dialogue @lina: `part_aa`, `part_ab` と、アルファベットが増えていくんですね。 @linny: そう。接頭辞を省略すると `xaa`, `xab` という名前になるよ。 @linny: サイズの単位は `K`(キロ)`M`(メガ)`G`(ギガ)が使える。 @linny: 1 つだけ注意。`10M` は 10×1024×1024 バイト、`10MB` は 10×1000×1000 バイトなんだ。数え方がちがう点を覚えておこう。 ::: ::: tip **単位の早見表** - `split -b 700M` → CD 1 枚に収まるサイズ - `split -b 100M` → クラウドアップロードしやすいサイズ - `split -b 1G` → 1GB ごと ::: ## 3. 行数で分割するには? {#by-lines} > **結論**: テキストやログは `split -l 行数 元ファイル 接頭辞` で行単位に分割できる。途中で行が切れないので CSV・ログ向き。 ::: dialogue @lina: ログファイルを 1000 行ずつに分けたいときはどうしますか。 @linny: `-l` を使う。lines、つまり行の頭文字だね。 @linny: サイズで切ると、行のとちゅうで切れてしまう。`-l` なら必ず行の区切りで分けてくれるんだ。 @linny: CSV やログを分けるときは、こちらのほうが安全だよ。 ::: ```bash $ split -l 1000 access.log chunk_ ``` ```bash $ wc -l chunk_* ``` ```output 1000 chunk_aa 1000 chunk_ab 342 chunk_ac 2342 合計 ``` ::: warning サイズで分ける `-b` は、バイト数だけを見て切ります。そのためテキストファイルでは、行のとちゅうで分かれてしまいます。 行の意味を保ちたいときは必ず `-l` を使ってください。 ::: ## 4. 個数を指定して分割するには? {#by-count} > **結論**: `split -n 個数 元ファイル 接頭辞` でファイルを指定した個数に等分割できる。「ちょうど 5 個に分けたい」ときに便利。 ::: dialogue @lina: 「10MB ずつ」ではなく「とにかく 5 個に分けたい」ときもありますよね。 @linny: そのときは `-n` を使う。number、つまり個数の頭文字だね。 @linny: ファイル全体を 5 等分してくれる。自分でサイズを計算しなくていいのが楽なところだよ。 ::: ```bash $ split -n 5 bigfile.dat group_ ``` ```bash $ ls -lh group_* ``` ```output -rw-r--r-- 1 user user 10M Jun 5 10:05 group_aa -rw-r--r-- 1 user user 10M Jun 5 10:05 group_ab -rw-r--r-- 1 user user 10M Jun 5 10:05 group_ac -rw-r--r-- 1 user user 10M Jun 5 10:05 group_ad -rw-r--r-- 1 user user 10M Jun 5 10:05 group_ae ``` ::: warning `-n 5` はバイト数で 5 等分します。そのためテキストファイルでは、行のとちゅうで切れます。 行を守ったまま 5 個に分けたいときは `split -n l/5` と書きます。`l` は line(行)の頭文字です。 ```bash $ split -n l/5 access.log group_ ``` ::: ## 5. 分割したファイルを元に戻すには? {#join} > **結論**: 結合には専用コマンドは不要。`cat 接頭辞* > 復元ファイル` で順番どおり連結すれば元のファイルへ戻る。 ::: dialogue @lina: 分けたのはいいのですが、どうやって元に戻すのですか。`join` コマンドのようなものがありますか。 @linny: いい質問だね。`join` という別のコマンドはある。でもそれは表を結合するためのもので、別ものなんだ。 @linny: `split` で分けたものを戻すには `cat` を使うよ。 @lina: えっ、ファイルを表示する `cat` でですか。 @linny: そう。`cat` は複数のファイルを順番につなげて出力するコマンドでもあるんだ。 @linny: だから `>` でファイルに書き出せば、それで結合が終わる。 ::: ```bash $ cat part_* > restored.dat ``` ```bash $ ls -lh restored.dat ``` ```output -rw-r--r-- 1 user user 50M Jun 5 10:10 restored.dat ``` ::: warning **並び順に注意してください。** `cat part_*` の `*` は、名前を文字として見くらべた順に展開されます。 `split` が付ける `aa` `ab` `ac` は、けた数がそろっています。だから正しい順序になります。 いっぽう `part_1, part_2, ... part_10` のような名前を自分で付けると、`part_10` が `part_2` より先に来てしまいます。次の章のゼロ埋め連番を使えば安全です。 ::: ## 6. 連番のサフィックスを付けるには? {#numeric-suffix} > **結論**: `-d` で数字サフィックス(`00`, `01`...)になり、`-a` で桁数を指定できる。`--additional-suffix` で拡張子も付けられる。 ::: dialogue @lina: `aa`, `ab` ではなく `01`, `02` のような数字のほうがわかりやすいです。 @linny: `-d` を付けると数字になるよ。digits、つまりけたの頭文字だね。 @linny: けた数は `-a` で指定する。さらに `--additional-suffix` を使えば `.part` のような拡張子も付けられるよ。 ::: ```bash $ split -b 10M -d -a 2 --additional-suffix=.part bigfile.dat backup_ ``` ```bash $ ls backup_* ``` ```output backup_00.part backup_01.part backup_02.part backup_03.part backup_04.part ``` ::: tip ゼロ埋めの連番なら、`cat backup_*.part > restored.dat` はいつも正しい順序で結合できます。`00`, `01`, ... `10`, `11` のように、けたがそろうからです。 100 個を超えそうなときは `-a 3` を使い、3 けたにしておくと安心です。 ::: ## 7. ファイルが壊れていないか確認するには? {#verify} > **結論**: 分割・結合の前後で `sha256sum` のハッシュ値を比べる。値が一致すれば 1 バイトも欠けずに復元できている証拠。 ::: dialogue @lina: 結合したファイルが本当に元と同じか、不安です。 @linny: そこで `sha256sum` を使う。ファイルの中身から計算される、指紋のような値だよ。 @linny: 元のファイルと復元したファイルで指紋が一致すれば、中身は完全に同じ。 @linny: 転送やコピーのとちゅうで壊れていないかも、これで確かめられるんだ。 ::: ```bash $ sha256sum bigfile.dat restored.dat ``` ```output 9f2c7b1a4e6d8035c1af52e0b73d94e6... bigfile.dat 9f2c7b1a4e6d8035c1af52e0b73d94e6... restored.dat ``` (ハッシュ値はファイルの中身によって変わります。上の値は表示の形を示す例です) ::: dialogue @lina: 左側の長い文字列が同じです。これで安心しました。 @linny: もし値がちがっていたら、結合の順番がまちがっているサインだよ。転送のとちゅうで壊れた可能性もある。 @linny: そのときは分割からやり直そう。 ::: ## 8. リナの失敗:自分で付けた数字で順番が崩れる {#pitfall-order} > **結論**: `*` は名前を文字として見くらべて展開する。けたがそろっていない数字を自分で付けると、結合の順番が崩れる。 ::: dialogue @lina: 先輩、分けたファイルに自分で `part_1` から `part_10` まで名前を付けました。でも結合したら中身が壊れていました。 ::: 小さな例で、何が起きたのかを見てみます。 ```bash $ touch part_1 part_2 part_10 $ ls part_* ``` ```output part_1 part_10 part_2 ``` ::: dialogue @lina: `part_10` が `part_2` より先に出ています。数の大きさは無視されているのですね。 @linny: そう。`*` は名前を **文字として左から** 見くらべるんだ。`1` と `2` をくらべた時点で、`part_10` のほうが先だと決まる。 @lina: えっ、では `cat part_* > restored.dat` は、この順番でつないでしまうんですか。 @linny: そのとおり。だから中身の並びが入れかわり、復元したファイルが壊れるんだ。 @lina: 数字にしたほうがわかりやすいと思ったのに、逆に危なかったんですね。納得しました。 @linny: だから `-d -a 2` を使う。`part_01`, `part_02`, `part_10` とけたがそろえば、文字として見くらべても正しい順序になるよ。 ::: ::: tip `split` が付ける `aa` `ab` `ac` も、けた数がそろっています。名前を自分で変えないかぎり、順番の事故は起きません。 心配なときは、結合したあとに `sha256sum` で確かめてください。値が一致すれば順番も中身も正しいという証拠になります。 ::: ## 9. よくあるつまずきと対処 {#pitfalls} > **結論**: つまずきの大半は「結合順序」「サイズ単位の勘違い」「ディスク容量不足」の 3 つ。分割前に容量と単位を確認する。 | 症状 | 原因 | 対処 | | -------------------------------- | -------------------------------- | ------------------------------------- | | 結合したら中身が壊れている | 結合順序が違う | `-d` のゼロ埋め連番にして `cat ...* ` | | 想定よりファイル数が多い/少ない | `M`(1024 系)と `MB`(1000 系) | 単位を統一する | | `No space left on device` | 分割で実質 2 倍の容量が必要 | 先に `df -h` で空きを確認 | | テキストの行が途中で切れる | `-b`(バイト)で切った | `-l`(行)で分割し直す | ::: warning **分割の前後で必ずやること** - 元のファイルを消す前に、結合できるか試す - 結合したら `sha256sum` で元ファイルとハッシュ値をくらべる - 分割を始める前に `df -h` で空き容量を確かめる ::: ::: warning `split` と `sha256sum` は、本サイトの仮想ターミナルにはまだありません。この記事の例は、手元の Linux やターミナルで試してください。 元のファイルを消すのは、結合とハッシュの確認が終わってからにしてください。`rm` で消したファイルは、GUI とちがってゴミ箱に入りません。その場で完全に消えます。 ::: ## 10. ミニ課題:実際にやってみよう {#exercise} > **結論**: 行数分割・サイズ分割と結合・ハッシュ確認の 3 問で、`split` と `cat` の往復を手で確かめる。 ::: dialogue @lina: 手順はわかりました。手を動かして確かめたいです。 @linny: いいね、3 問用意したよ。まず練習用のディレクトリを作って、その中で試してみて。 ::: ```bash $ mkdir -p ~/split-practice && cd ~/split-practice $ seq 1 25 > numbers.txt ``` **課題 1**:`numbers.txt` を 10 行ずつに分割し、できたファイルの行数を確かめよう。 :::details ヒント 1(方向づけ)を見る サイズではなく行の数で分けます。行の区切りを守ってくれるオプションを使います。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `split` と `wc` です。`split` には行の数で分けるオプションがあります。 ::: :::details 答えを見る ```bash $ split -l 10 numbers.txt num_ $ wc -l num_* ``` ```output 10 num_aa 10 num_ab 5 num_ac 25 合計 ``` 25 行を 10 行ずつに分けたので、最後だけ 5 行になります。 ::: **課題 2**:分割したファイルを結合して `joined.txt` を作ろう。 :::details ヒント 1(方向づけ)を見る 結合には専用のコマンドは要りません。複数のファイルを順につなげて出力するコマンドを使います。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `cat` です。結果をファイルに書き出す記号は第 5 章で出てきました。 ::: :::details 答えを見る ```bash $ cat num_* > joined.txt $ wc -l joined.txt ``` ```output 25 joined.txt ``` 元と同じ 25 行に戻りました。 ::: **課題 3**:`numbers.txt` と `joined.txt` の中身が完全に同じか確かめよう。 :::details ヒント 1(方向づけ)を見る 行数が同じでも、中身が同じとはかぎりません。ファイルの中身から計算される指紋をくらべます。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `sha256sum` です。ファイル名は 2 つ並べて渡せます。 ::: :::details 答えを見る ```bash $ sha256sum numbers.txt joined.txt ``` 左側の長い文字列が 2 行とも同じなら、中身は完全に同じです。 練習が終わったら、作ったディレクトリごと片づけられます。中身を確かめてから消してください。 ```bash $ cd ~ $ ls ~/split-practice $ rm -r ~/split-practice ``` `rm -r` はディレクトリを中身ごと消します。実行する前に、パスが `~/split-practice` であることを必ず確かめてください。 ::: ## 11. 振り返り {#review} ::: dialogue @lina: 整理します。分けるのは `split`、戻すのは `cat` ですね。専用の結合コマンドは要らないと。 @linny: そのとおり。そして分け方は 3 種類。サイズなら `-b`、行数なら `-l`、個数なら `-n` だね。 @lina: テキストやログは `-l` を使う。行のとちゅうで切れないからですね。 @linny: 完璧だね。最後に `sha256sum` で確かめる。この習慣があれば、元のファイルを安心して消せるよ。 ::: ## 今日の 3 行まとめ {#three-lines} 1. 分けるのは `split`、戻すのは `cat 接頭辞* > 復元ファイル` 2. サイズは `-b`、行数は `-l`、個数は `-n`。テキストやログは `-l` を使う 3. 結合したら `sha256sum` で元ファイルとくらべる。一致してから元ファイルを消す ## まとめ / 次に読む {#next} - [ファイル転送の基本(scp / rsync)](/articles/tutorials/scp-rsync-basics) - [tar でアーカイブしてから分割する](/articles/tutorials/tar-basics) - [ディスクがいっぱいで分割できないとき](/articles/troubleshooting/no-space-left-on-device) # ~/.ssh/config 活用術 - 接続情報を整理する Source: https://penguin-gym-linux.com/articles/tutorials/ssh-config-tips ## ~/.ssh/config とは何か? {#what-is-ssh-config} `~/.ssh/config` を使うと、`ssh user@192.0.2.10 -i ~/.ssh/mykey -p 2222` のような長いコマンドを `ssh myserver` の一行に短縮できる。接続先ごとのホスト名・ユーザー・ポート・鍵ファイルをまとめて管理できるため、複数サーバを扱う実務で必須の設定ファイルだ。 ::: tip **結論(実務の型)** - 長い `ssh` コマンドはすべて `~/.ssh/config` のエイリアスに集約する - 踏み台経由接続は `ProxyJump` で 1 行で書ける - `Host *` はファイル末尾に置いてデフォルト値を一括管理する ::: ::: warning **前提(対象環境)** - OS:Ubuntu / Linux(OpenSSH クライアント) - SSH 鍵認証が設定済みであること(未設定なら[SSH 鍵認証セットアップ](/articles/tutorials/ssh-key-setup)を先に参照) ::: ## 設定ファイルの作成と権限設定 {#setup} ファイルが存在しない場合は作成してパーミッションを設定する。 ```bash touch ~/.ssh/config chmod 600 ~/.ssh/config ``` **600 は必須。** グループ・その他に読み取り権限があると OpenSSH が設定ファイルを無視し、エラーが発生する。 ``` Bad owner or permissions on /home/user/.ssh/config ``` このエラーが出たら `chmod 600 ~/.ssh/config` で修正する。 ## 基本構文 {#syntax} `Host` キーワードで始まるブロックを並べる形式。ブロック内のオプションはインデントして記述する(空白 4 つが慣習的)。 ``` Host <エイリアス> HostName <実際のIPまたはFQDN> User <ログインユーザー名> Port <ポート番号> IdentityFile <秘密鍵のパス> ``` OpenSSH はファイルを**上から順に評価し、最初にマッチしたブロックを優先**する。同一オプションが複数のブロックにあっても上書きされないため、具体的な設定を上に、デフォルト設定は下に書く。 ## よく使うオプション一覧 {#options} | オプション | 説明 | 例 | | ----------------------- | ---------------------- | ------------------------ | | `HostName` | 実際のIPまたはFQDN | `192.0.2.10` | | `User` | ログインユーザー | `ubuntu` | | `Port` | ポート番号 | `2222` | | `IdentityFile` | 秘密鍵ファイルパス | `~/.ssh/id_ed25519_work` | | `ProxyJump` | 踏み台接続 | `bastion` | | `ServerAliveInterval` | KeepAlive 間隔(秒) | `60` | | `ServerAliveCountMax` | 無応答でのカット回数 | `3` | | `Compression` | 圧縮転送の有効化 | `yes` | | `ForwardAgent` | SSH エージェント転送 | `yes` | | `AddKeysToAgent` | ssh-agent への自動追加 | `yes` | | `StrictHostKeyChecking` | ホスト鍵検証の挙動 | `accept-new` | ## 複数サーバをエイリアスで管理する {#basic-example} 最も基本的な使い方。接続先ごとにブロックを記述する。 ``` Host web01 HostName 192.0.2.10 User ubuntu Port 22 IdentityFile ~/.ssh/id_ed25519 Host web02 HostName 192.0.2.20 User ec2-user IdentityFile ~/.ssh/id_ed25519_aws Host staging HostName staging.example.com User deploy Port 2222 IdentityFile ~/.ssh/id_ed25519_staging ``` 設定後は短いエイリアスで接続できる。 ```bash ssh web01 ssh staging scp file.txt staging:/home/deploy/ ``` `scp` や `rsync` もエイリアスをそのまま使えるのがメリットだ。 ## ProxyJump で踏み台経由接続 {#proxyjump} 本番環境では「踏み台(bastion)経由でのみアクセス可能」なサーバが多い。`ProxyJump` を使うと透過的に接続できる。 ``` Host bastion HostName bastion.example.com User ubuntu IdentityFile ~/.ssh/id_ed25519 Host prod01 HostName 10.0.0.10 User ubuntu ProxyJump bastion IdentityFile ~/.ssh/id_ed25519 Host prod02 HostName 10.0.0.11 User ubuntu ProxyJump bastion IdentityFile ~/.ssh/id_ed25519 ``` 接続コマンドは変わらない。 ```bash ssh prod01 ``` 内部で `bastion` に接続し、そこから `prod01` に転送される。`scp` / `rsync` もそのまま動作する。 ::: warning `ProxyJump` は OpenSSH 7.3 以降。古い環境では `ProxyCommand ssh -W %h:%p bastion` で代替できる。バージョンは `ssh -V` で確認。 ::: ::: tip **多段踏み台**(bastion1 → bastion2 → target)もカンマで書ける。 ``` Host deep-prod HostName 10.1.0.5 User ubuntu ProxyJump bastion1,bastion2 ``` ::: ## IdentityFile で鍵を使い分ける {#identityfile} 用途ごとに別の鍵を使うケースではワイルドカードが便利だ。 ``` Host github.com IdentityFile ~/.ssh/id_ed25519_github User git Host *.company.internal IdentityFile ~/.ssh/id_ed25519_company User sato Host *.amazonaws.com IdentityFile ~/.ssh/id_rsa_aws User ec2-user ``` `*.company.internal` のようにワイルドカードを使うと、同一ドメインのサーバ群に同じ鍵と設定を一括適用できる。 ## Host \* でデフォルト設定を一括管理 {#default-settings} `Host *` はすべての接続に適用されるデフォルト設定ブロック。**ファイルの末尾**に置くのが鉄則。 ``` # 個別設定(上に置く) Host web01 HostName 192.0.2.10 User ubuntu Host bastion HostName bastion.example.com User ubuntu # デフォルト設定(必ずファイル末尾) Host * ServerAliveInterval 60 ServerAliveCountMax 3 AddKeysToAgent yes IdentityFile ~/.ssh/id_ed25519 ``` ::: tip - `ServerAliveInterval 60` + `ServerAliveCountMax 3`:180 秒無応答で切断。ネットワーク越しに接続が固まるのを防ぐ。 - `AddKeysToAgent yes`:初回接続時に自動で `ssh-agent` へ鍵を追加し、同一セッション内ではパスフレーズ入力を省略できる。 ::: ## 設定の確認方法 {#verify} 実際にどの設定が使われるか確認するには `-G` フラグが便利。接続せずに解決済みのオプションを一覧表示する。 ```bash ssh -G prod01 ``` 接続時に詳細なデバッグ情報を表示するには `-v` を使う。 ```bash ssh -v prod01 2>&1 | grep -E "identity|proxy|config" ``` どの `IdentityFile` が試されているか、どの `ProxyJump` が使われているかを確認できる。 ## 設定例テンプレート(コピペ用) {#template} ::: tip **最小構成テンプレート** ``` # 踏み台サーバ Host bastion HostName bastion.example.com User ubuntu IdentityFile ~/.ssh/id_ed25519 # 本番サーバ(踏み台経由) Host prod01 HostName 10.0.0.10 User ubuntu ProxyJump bastion IdentityFile ~/.ssh/id_ed25519 # デフォルト設定(必ず末尾) Host * ServerAliveInterval 60 ServerAliveCountMax 3 AddKeysToAgent yes StrictHostKeyChecking accept-new ``` ::: ## 次に読む {#next} - [SSH 鍵認証セットアップ](/articles/tutorials/ssh-key-setup) - [SSH で接続できないときのチェックリスト](/articles/troubleshooting/ssh-troubleshooting) - [ファイル転送の基本:scp / rsync](/articles/tutorials/scp-rsync-basics) # SSH 鍵認証セットアップ - パスワードレスで安全にログイン Source: https://penguin-gym-linux.com/articles/tutorials/ssh-key-setup ## この記事で解決できること {#intro} - SSH 鍵ペア(公開鍵・秘密鍵)を生成してサーバに接続する手順が分かる - `ssh-copy-id` で公開鍵を正しく配置できる - `~/.ssh/config` で複数サーバを管理する方法が身につく **用語の整理**(この記事で使う言葉を先に定義します) - **鍵ペア**: 対になる 2 つのファイルです。**公開鍵**(`.pub` が付く方)と**秘密鍵**(`.pub` が付かない方)で 1 組になります - **公開鍵**: サーバ側に置く鍵です。他人に見られても問題ありません。「パブリックキー」とも呼びます - **秘密鍵**: 手元の端末に置く鍵です。**他人に渡ってはいけない**ファイルです。「プライベートキー」「identity file」も同じものを指します - **パスフレーズ**: 秘密鍵ファイル自体を暗号化するための合言葉です。サーバのログインパスワードとは別物です。混同しやすいので区別してください - **`authorized_keys`**: サーバ側で「このユーザーとしてログインしてよい公開鍵」を並べたファイルです。`~/.ssh/authorized_keys` に置きます - **`ssh-agent`**: 復号した秘密鍵をメモリ上で預かる常駐プログラムです。パスフレーズの再入力を省けます - **ED25519 / RSA**: 鍵の作り方(アルゴリズム)の名前です。現在の推奨は ED25519 です ::: tip **結論(3 ステップ)** 1. `ssh-keygen -t ed25519` で鍵ペアを生成する 2. `ssh-copy-id user@server` で公開鍵をサーバに配置する 3. `ssh user@server` でパスワードなしでログインできる ::: ::: warning **前提(対象環境)** - OS: Ubuntu / Debian / RHEL 系 Linux - クライアント・サーバ両方に OpenSSH がインストール済み - 初期設定時はパスワード認証でサーバにログインできる状態 ::: ## なぜ鍵認証を使うのか? {#why} パスワード認証より安全で、自動化にも強い方式です。鍵認証を使う主な理由は 3 点あります。 - **安全性**: 秘密鍵はクライアント端末から出ません。パスワードをネットワーク越しに送らずに認証できます - **ブルートフォース(総当たり攻撃)耐性**: 十分な長さの鍵は、片端から試す攻撃が事実上不可能です - **自動化**: rsync・Ansible・CI/CD パイプラインなど、人が入力できない場面でも接続できます ## 1. 鍵ペアの生成 {#keygen} ### どのアルゴリズムを選ぶか? {#algorithm} 現在は **ED25519** 一択です。RSA-4096 と同等以上の強度を、はるかに短い鍵と高速な処理で得られます。古い機器で ED25519 に対応していない場合だけ `-t rsa -b 4096` を使ってください。 ```bash ssh-keygen -t ed25519 -C "your_email@example.com" ``` ```output Generating public/private ed25519 key pair. Enter file in which to save the key (/home/user/.ssh/id_ed25519): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/user/.ssh/id_ed25519 Your public key has been saved in /home/user/.ssh/id_ed25519.pub ``` `-C` は鍵のコメント(識別用)。省略しても動作する。 ::: tip **パスフレーズを設定する理由** パスフレーズを設定すると、秘密鍵ファイル自体が暗号化されます。端末を盗まれても、そのままでは鍵を使えません。毎回の入力が面倒な場合は `ssh-agent` で省略できます([後述](#agent))。空のまま Enter で進めることもできますが、その場合は秘密鍵ファイルを手に入れた人が即座にログインできる状態になります。 ::: 生成後の確認: ```bash ls -la ~/.ssh/ ``` ```output total 16 drwx------ 2 user user 4096 May 31 10:00 . drwxr-xr-x 8 user user 4096 May 31 10:00 .. -rw------- 1 user user 419 May 31 10:00 id_ed25519 -rw-r--r-- 1 user user 107 May 31 10:00 id_ed25519.pub ``` - `id_ed25519`(**秘密鍵**): 権限は `600`。手元の端末から動かさないファイルです - `id_ed25519.pub`(**公開鍵**): サーバの `~/.ssh/authorized_keys` に追記する側です ::: danger **秘密鍵でやってはいけないこと** 秘密鍵が他人の手に渡ると、その鍵で入れるサーバすべてに侵入されます。パスワードの使い回しより被害範囲が広くなります。 - **メール・チャット・課題管理ツールに貼らない**: 送信先の履歴とログに永久に残ります。共有が必要なのは常に**公開鍵(`.pub`)の方**です - **共有サーバや共有ストレージに置かない**: `scp` で秘密鍵をサーバへコピーするのは典型的な事故です。サーバ側に置くのは公開鍵だけです - **リポジトリにコミットしない**: 一度 push した鍵は履歴から消しても漏洩したものとして扱います - **端末ごとに別の鍵を作る**: 1 本を全端末で使い回すと、1 台の紛失で全滅します **漏らしたかもしれないときの対処** 1. サーバ側の `~/.ssh/authorized_keys` から該当する公開鍵の行を削除する 2. `ssh-keygen -t ed25519` で新しい鍵ペアを作り、配り直す 3. その鍵を登録していたサービス(GitHub 等)でも登録を削除する ::: ## 2. 公開鍵をサーバに配置する {#copy-id} ### ssh-copy-id を使う(推奨) {#ssh-copy-id} パスワード認証がまだ有効なうちに、1 回だけ実行します。実行時に聞かれるのはサーバのログインパスワードです。鍵のパスフレーズではありません。 ```bash ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server ``` ```output /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/user/.ssh/id_ed25519.pub" /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s) /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed user@server's password: Number of key(s) added: 1 ``` `ssh-copy-id` はサーバ側の `~/.ssh/authorized_keys` に公開鍵の内容を追記します。既存の鍵は上書きされず、追記されます。`~/.ssh` を新しく作る場合は適切な権限で作成しますが、**既にあるディレクトリやファイルの権限は直しません**。次章の権限確認は必ず実施してください。 ### ssh-copy-id が使えないときの手動配置 {#manual-copy} ```bash cat ~/.ssh/id_ed25519.pub | ssh user@server \ "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" ``` ::: danger **`>>`(追記)を `>`(上書き)に書き間違えない** 上のコマンドの `cat >> ~/.ssh/authorized_keys` は追記です。`>` にすると上書きになります。サーバに登録済みだった他の公開鍵がすべて消えます。同僚や CI が使っていた鍵も消え、復旧にはコンソールからの作業が必要になります。 配置元のファイルが `.pub` であることも確認してください。`.pub` を付け忘れると秘密鍵をサーバへ送ることになります。 ::: ## 3. パーミッションを正しく設定する {#permissions} SSH は **ディレクトリとファイルの権限が緩すぎると鍵認証を拒否** する設計です。他人が読み書きできる場所に置かれた鍵は信用できない、という考え方に基づきます。 | パス | 正しい権限 | 理由 | | ------------------------ | ---------- | ------------------------------ | | `~/.ssh/` | `700` | 所有者のみ読み書き実行 | | `~/.ssh/authorized_keys` | `600` | 所有者のみ読み書き | | `~/.ssh/id_ed25519` | `600` | 秘密鍵は特に厳格に | | `~/.ssh/id_ed25519.pub` | `644` | 公開鍵は他者が読んでも問題ない | 確認・修正コマンド: ```bash chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chmod 600 ~/.ssh/id_ed25519 ``` ::: danger 権限が緩すぎると鍵認証は失敗します。ただし「どこが緩いか」で、拒否する側が変わります。 - **サーバ側**: ホームディレクトリ・`~/.ssh`・`authorized_keys` のいずれかがグループまたは他人から**書き込み可**(`775` / `777` など)だと、sshd はその鍵を無視して `Permission denied (publickey)` を返します。`755` や `644` は拒否されません - **クライアント側**: 秘密鍵にグループまたは他人の権限が 1 つでも付いていると(`644` / `755` など)、`ssh` が `UNPROTECTED PRIVATE KEY FILE` を表示して鍵を使いません。この拒否はサーバではなく手元の `ssh` によるものです 接続できないときは、まず上の表の権限にそろっているかを確認してください。 **`chmod 777 ~/.ssh` で解決しようとしない**: 権限を緩めると症状は悪化します。全ユーザーが `authorized_keys` を書き換えられる状態になり、他人が自分の公開鍵を追記して侵入できるためです。上の表の値(`700` / `600` / `644`)に**そろえる**のが正しい対処です。 ::: ## 4. 接続テスト {#test} ```bash ssh -v user@server ``` `-v`(verbose = 詳細)を付けると、認証の過程がログとして出力されます。どの鍵を提示し、サーバがそれを受け入れたかが分かります。 ```output ... debug1: Offering public key: /home/user/.ssh/id_ed25519 ED25519 SHA256:xxxxx debug1: Server accepts key: /home/user/.ssh/id_ed25519 ED25519 SHA256:xxxxx Authenticated to server ([x.x.x.x]:22) using "publickey". ``` `Authenticated to server ... using "publickey"` が出れば鍵認証は成功です。 ::: warning **パスワード認証を無効化するのは、鍵認証の成功を確認してから** `/etc/ssh/sshd_config` の `PasswordAuthentication no` は、鍵認証が確実に動く状態になるまで設定しないでください。鍵の配置に失敗したまま無効化すると、誰もログインできないサーバができあがります。 **安全な手順** 1. いま接続している SSH セッションは**閉じずに残す** 2. 別のターミナルから新しいセッションで鍵ログインできることを確認する 3. 確認できてから設定を変更し、`sudo systemctl reload ssh`(RHEL 系は `sshd`)を実行する 4. さらに別のセッションでログインできることを確認してから、最初のセッションを閉じる ::: ## トラブルシューティング {#troubleshooting} ### 症状: Permission denied (publickey) {#case-publickey} **確認 1: そもそも鍵が提示されているか** ```bash ssh -v user@server 2>&1 | grep -i "offering\|authentications that can continue" ``` 提示されていなければ、`ssh -i ~/.ssh/id_ed25519 user@server` で鍵を明示するか、`~/.ssh/config` の `IdentityFile` を確認します。 **確認 2: クライアント側の秘密鍵の権限** ```bash ls -l ~/.ssh/id_ed25519 ``` `-rw-------`(`600`)以外なら `chmod 600 ~/.ssh/id_ed25519` で直します。 **確認 3: サーバ側の権限と登録内容** ```bash ssh user@server 'ls -ld ~ ~/.ssh; ls -l ~/.ssh/authorized_keys; wc -l ~/.ssh/authorized_keys' ``` ホームディレクトリ・`~/.ssh`・`authorized_keys` のいずれかがグループまたは他人から書き込み可なら、sshd は鍵を無視します。 **対処** 1. 権限を `700` / `600` にそろえる 2. `ssh-copy-id` を実行し直す 3. それでも失敗するなら、サーバ側で `sudo journalctl -u ssh -n 30` を実行し、拒否理由を読む(RHEL 系は `-u sshd`) ## 5. ~/.ssh/config で接続設定を管理する {#config} 接続先が増えてきたら `~/.ssh/config` を使います。ホスト名・ユーザー名・鍵ファイルの組み合わせに、短い名前を付けられます。 ```bash vim ~/.ssh/config ``` 設定例: ``` Host myserver HostName 192.168.1.100 User ubuntu IdentityFile ~/.ssh/id_ed25519 Port 22 Host staging HostName staging.example.com User deploy IdentityFile ~/.ssh/id_ed25519 ``` 設定後は `ssh myserver` だけで接続できる。 ```bash chmod 600 ~/.ssh/config ``` ::: warning `~/.ssh/config` がグループまたは他人から書き込み可(`664` など)だと、`ssh` は `Bad owner or permissions on /home/user/.ssh/config` を表示して接続を中止します。無視して続行はしません。`600` にしておいてください。 ::: ## 6. ssh-agent でパスフレーズを管理する {#agent} パスフレーズを毎回入力しなくて済む仕組みです。セッション開始時に 1 回だけ入力すれば、以後は省略できます。預けた鍵はメモリ上に置かれ、ログアウトで消えます。 ```bash # エージェントを起動 eval "$(ssh-agent -s)" # 鍵を登録(1 回だけパスフレーズを入力) ssh-add ~/.ssh/id_ed25519 ``` ```output Identity added: /home/user/.ssh/id_ed25519 (your_email@example.com) ``` 登録済み鍵の確認: ```bash ssh-add -l ``` ::: tip macOS は Keychain 統合により自動的に `ssh-agent` が動作する。`~/.ssh/config` に `UseKeychain yes` と `AddKeysToAgent yes` を追記すると再起動後も引き継がれる。 ::: ## まとめ {#summary} | コマンド | 用途 | | ------------------------------------------------------ | ---------------------------------- | | `ssh-keygen -t ed25519` | 鍵ペア生成(ED25519 推奨) | | `ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server` | 公開鍵をサーバに配置 | | `chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys` | 権限を正しく設定 | | `ssh -v user@server` | 詳細ログ付きで接続テスト | | `eval "$(ssh-agent -s)" && ssh-add` | エージェントにパスフレーズを預ける | ## 次に読む {#next} - [SSH で接続できないときのチェックリスト](/articles/troubleshooting/ssh-troubleshooting) - [scp / rsync でのファイル転送](/articles/tutorials/scp-rsync-basics) - [tmux で SSH 切断後も作業を継続する](/articles/tutorials/tmux-basics) # SSH ポートフォワーディング入門 - ローカル・リモート・動的転送 Source: https://penguin-gym-linux.com/articles/tutorials/ssh-port-forwarding ## この記事で解決できること {#intro} - `-L`(ローカル)・`-R`(リモート)・`-D`(動的)の **違いと使い分け** が分かる - 踏み台越しの DB 接続・社内サービスの一時公開・SOCKS プロキシを **実例で再現** できる - 「つながらない」ときの **切り分け手順** が身につく ::: tip **結論(3 つのトンネルの型)** - **手元 → 遠くのサービスに繋ぎたい** → `-L`(ローカル転送) - **遠くから手元のサービスを見せたい** → `-R`(リモート転送) - **ブラウザ全体をトンネル経由にしたい** → `-D`(動的・SOCKS) ::: ::: warning **前提(対象環境)** - OS:Ubuntu(OpenSSH クライアント / サーバ) - SSH でログインできる状態 - ポート番号は例。実環境に合わせて読み替えること ::: ## SSH ポートフォワーディングとは? {#what} > **結論**: SSH の暗号化トンネルに任意の TCP 接続を相乗りさせる仕組み。直接届かないポートへ、SSH 接続だけを頼りに安全に到達できる。 ファイアウォールやプライベートネットワークの内側にあるサービスは、外から直接は叩けない。だが SSH で入れるなら、その SSH 接続の中に別の TCP 通信を通せる。これがポートフォワーディング(トンネリング)。 転送には 3 種類ある。方向と「待ち受ける側」が違うだけで、考え方は共通。 | 種類 | オプション | 待ち受け | 用途 | | ----------- | ---------- | -------- | -------------------------------- | | ローカル | `-L` | 手元 | 遠くのサービスを手元から使う | | リモート | `-R` | リモート | 手元のサービスを遠くに見せる | | 動的(SOCKS) | `-D` | 手元 | ブラウザ等を丸ごとトンネルに通す | ## ローカル転送(-L)はどう使うのか? {#local} > **結論**: `-L 手元ポート:宛先ホスト:宛先ポート` の形。手元のポートへの接続が、SSH 先から見た宛先へ転送される。踏み台越しの DB 接続が代表例。 ### 基本形 ```bash $ ssh -L 8080:localhost:80 user@server ``` これで **手元の** `localhost:8080` への接続が、`server` 上から見た `localhost:80` に届く。ブラウザで `http://localhost:8080` を開けば、server の 80 番にアクセスしたのと同じになる。 ::: highlight `宛先ホスト` は **SSH 接続先(server)から見た** 名前で解決される。`localhost` なら server 自身、別名なら server から到達できる別ホストを指す。 ::: ### 実例:踏み台越しに DB へ繋ぐ DB(`db.internal:5432`)が踏み台 `bastion` の内側にあり、手元からは直接届かないケース。 ```bash $ ssh -L 15432:db.internal:5432 user@bastion ``` 別ターミナルで、手元の 15432 を本物の DB のように扱える。 ```bash $ psql -h localhost -p 15432 -U dbuser appdb ``` ### トンネルだけ張りたいとき(-N / -f) ログインシェルは不要でトンネルだけ欲しい場合は `-N`、バックグラウンドに回すなら `-f` を足す。 ```bash $ ssh -fN -L 15432:db.internal:5432 user@bastion ``` - `-N`:リモートコマンドを実行しない(転送専用) - `-f`:認証後にバックグラウンドへ回す ::: warning `-f` で背後に回したトンネルは、用が済んだら必ず止める。プロセスを探して終了する。 ```bash $ ps aux | grep "ssh -fN" $ kill ``` ::: ## リモート転送(-R)はどう使うのか? {#remote} > **結論**: `-R リモートポート:宛先ホスト:宛先ポート` の形。リモート側で待ち受け、手元から見た宛先へ転送する。ローカル開発中の Web を一時的に外へ見せる用途が定番。 方向がローカル転送の逆。**リモート側の**ポートへの接続が、手元(SSH を実行したマシン)から見た宛先に届く。 ### 基本形 ```bash $ ssh -R 8080:localhost:3000 user@server ``` `server` 上の `localhost:8080` への接続が、**手元の** `localhost:3000` に転送される。ローカルで動かしている開発サーバ(3000 番)を、server からアクセスできるようになる。 ### 外部公開したいときの落とし穴:GatewayPorts デフォルトでは `-R` の待ち受けは **リモートのループバック(127.0.0.1)に限定** される。server 自身からは見えるが、server の外からは届かない。server の全インターフェースで受けたい場合は、サーバ側 `/etc/ssh/sshd_config` の設定が要る。 ```text GatewayPorts yes ``` 設定変更後は `sudo systemctl reload ssh` で反映する。 ::: danger `GatewayPorts yes` は手元のサービスを外部ネットワークへ晒す。公開範囲・認証・期間を限定し、不要になったら即座にトンネルと設定を戻すこと。 ::: ## 動的転送(-D)とは? {#dynamic} > **結論**: `-D ポート` で手元に SOCKS プロキシを立てる。宛先ごとにトンネルを張らず、アプリの通信をまとめて SSH 先から出させる。 `-L` は「1 ポート 1 宛先」だが、`-D` は宛先を固定しない。手元に SOCKS プロキシができ、それを向いたアプリの通信はすべて SSH 先を出口にして流れる。 ```bash $ ssh -D 1080 user@server ``` これで `localhost:1080` が SOCKS5 プロキシになる。ブラウザや `curl` のプロキシ設定をここに向ける。 ```bash $ curl --socks5-hostname localhost:1080 https://example.com ``` ::: tip `--socks5-hostname` は **名前解決もプロキシ側で** 行う。社内 DNS でしか引けないホストへ届かせたいときはこちら。単なる `--socks5` だと手元で名前解決してしまう。 ::: ## つながらないときの切り分けは? {#troubleshoot} > **結論**: 「ポートが衝突」「宛先名の解決位置を誤解」「リモート公開設定の不足」の 3 つが大半。エラー文言から原因を一発で絞り込む。 ### bind: Address already in use 手元(または -R ならリモート)の待ち受けポートが既に使われている。別ポートに変えるか、専有プロセスを特定する。 ```bash $ ss -tlnp | grep :8080 ``` ### channel ... open failed: connect failed トンネルは張れたが、**SSH 先から宛先へ届いていない**。宛先ホスト名・ポート・到達性を SSH 先で確認する。 ```bash $ ssh user@server $ nc -vz db.internal 5432 ``` ### -R なのに外部から繋がらない 前述の `GatewayPorts` 不足が大半。`-R 0.0.0.0:8080:...` のように bind アドレスを明示しても、サーバ側設定が `no` のままなら効かない。 ::: warning **よくある誤解:宛先名はどちらから解決される?** - `-L 15432:db.internal:5432` の `db.internal` は **SSH 先(server)** から解決 - `-R 8080:localhost:3000` の `localhost:3000` は **手元** から解決 「手元では引けるのに繋がらない」ときは、解決する側を取り違えていることが多い。 ::: ## 設定を ~/.ssh/config に固定するには? {#config} > **結論**: 毎回長いオプションを打つ代わりに、`LocalForward` / `RemoteForward` / `DynamicForward` を config に書けば `ssh host` だけで済む。 ```text Host db-tunnel HostName bastion.example.com User user LocalForward 15432 db.internal:5432 Host socks HostName server.example.com User user DynamicForward 1080 ``` 以後はホスト名を指定するだけ。 ```bash $ ssh -fN db-tunnel ``` 詳しい config の書き方は [~/.ssh/config 活用術](/articles/tutorials/ssh-config-tips) を参照。 ## まとめと安全テンプレ {#summary} > **結論**: 方向で `-L` / `-R` を選び、宛先を固定しないなら `-D`。トンネルは張りっぱなしにせず、用が済んだら止める。 ::: tip **コピペ用テンプレ** ```bash # ローカル転送(遠くのDBを手元へ) ssh -fN -L 15432:db.internal:5432 user@bastion # リモート転送(手元の開発サーバを遠くへ) ssh -R 8080:localhost:3000 user@server # 動的転送(SOCKSプロキシ) ssh -D 1080 user@server ``` ::: ## 次に読む {#next} - [SSH 鍵認証セットアップ](/articles/tutorials/ssh-key-setup) - [~/.ssh/config 活用術](/articles/tutorials/ssh-config-tips) - [nc (netcat) 入門](/articles/tutorials/netcat-basics) # sshfs 入門 - リモートのディレクトリをSSH経由でマウントする Source: https://penguin-gym-linux.com/articles/tutorials/sshfs-remote-mount ## この記事で解決できること {#intro} - `sshfs` で **リモートのディレクトリをローカルにマウント** できるようになる - `scp` / `rsync` との **使い分け** が分かる - 切断・権限・スタイルマウントなど **定番トラブルを即対処** できる ::: tip **結論(実務の型)** - マウント: `sshfs user@host:/path ~/mnt -o reconnect,ServerAliveInterval=15` - アンマウント: `fusermount3 -u ~/mnt`(旧環境は `fusermount -u`) - 頻繁に触る → `sshfs`、一括転送 → `rsync`、単発 → `scp` ::: ::: warning **前提(対象環境)** - OS: Ubuntu / Debian 系(他ディストリでもパッケージ名以外は同様) - リモートへ SSH 接続可能(鍵認証推奨) - ローカルに FUSE が利用可能(通常のデスクトップ / サーバで既定で利用可) ::: ## sshfs とは? {#what} > **結論**: sshfs は SSH 越しにリモートのファイルシステムをローカルへマウントする FUSE ベースのツール。リモートのファイルをローカルのファイルと同じように直接読み書きできる。 `sshfs`(SSH Filesystem)は、SSH の SFTP サブシステムを使ってリモートホストのディレクトリをローカルのマウントポイントに接続する。マウント後は、リモートのファイルをローカルのパスとして扱えるため、ローカルのエディタ・ファイラ・ビルドツールがそのままリモートのファイルを操作できる。 `scp` / `rsync` が「ファイルをコピーする」のに対し、`sshfs` は「リモートを見えるようにする」。コピーを介さずに直接編集したい、リモートのログをローカルの GUI で開きたい、といった用途で力を発揮する。 ::: tip FUSE(Filesystem in Userspace)上に実装されているため、**root 権限なし**で一般ユーザーがマウントできるのが NFS / CIFS との大きな違い。 ::: ## どうやってインストールするのか? {#install} > **結論**: Ubuntu / Debian なら `sudo apt install sshfs` の一発。これで `sshfs` 本体と必要な FUSE ライブラリが入る。 ```bash # Ubuntu / Debian $ sudo apt update $ sudo apt install sshfs # RHEL / Rocky / AlmaLinux(EPEL 経由) $ sudo dnf install fuse-sshfs # macOS(Homebrew、別途 macFUSE が必要) $ brew install gromgit/fuse/sshfs-mac ``` インストール確認: ```bash $ sshfs --version ``` ```output SSHFS version 3.7.3 FUSE library version 3.14.0 ``` ::: warning sshfs プロジェクトは現在メンテナンスモード(活発な新機能開発は停止)。ただし広く使われており、安定して動作する。新規構築でより高機能を求める場合は NFS / Samba も検討対象。 ::: ## どうやってマウントするのか? {#mount} > **結論**: `sshfs user@host:/remote/path /local/mountpoint` が基本形。マウントポイントは事前に空ディレクトリを作っておく。 ### 1. マウントポイントを用意する ```bash $ mkdir -p ~/mnt/server ``` ### 2. マウントする ```bash $ sshfs user@server:/var/www ~/mnt/server ``` リモート側のパスを省略するとログインユーザーのホームディレクトリがマウントされる。 ```bash # リモートのホームをマウント $ sshfs user@server: ~/mnt/server ``` ### 3. 確認する ```bash $ ls ~/mnt/server $ mount | grep sshfs ``` ```output user@server:/var/www on /home/local/mnt/server type fuse.sshfs (rw,nosuid,nodev,relatime,user_id=1000,group_id=1000) ``` ### 標準と異なるポートを使う場合 ```bash $ sshfs -p 2222 user@server:/var/www ~/mnt/server ``` ## どうやってアンマウントするのか? {#unmount} > **結論**: `fusermount3 -u マウントポイント` でアンマウントする。libfuse2 系の旧環境では `fusermount -u` を使う。 ```bash # libfuse3(現行の Ubuntu 等) $ fusermount3 -u ~/mnt/server # libfuse2(旧環境) $ fusermount -u ~/mnt/server ``` ::: warning マウントポイント内に `cd` した状態や、そのファイルを開いているプロセスがあるとアンマウントに失敗する(`device is busy`)。先にそのディレクトリから抜け、開いているアプリを閉じること。どうしても外れない場合は `fusermount3 -uz`(遅延アンマウント)が使える。 ::: ## 切れても安全に使うには?(実務オプション) {#options} > **結論**: 実務では `reconnect` と `ServerAliveInterval` を必ず付ける。ネットワークが一瞬切れても自動で復帰し、アイドル切断も防げる。 ```bash $ sshfs user@server:/var/www ~/mnt/server \ -o reconnect \ -o ServerAliveInterval=15 \ -o ServerAliveCountMax=3 ``` よく使うオプション: | オプション | 効果 | | ----------------------------------- | --------------------------------------------- | | `-o reconnect` | 接続が切れたら自動再接続 | | `-o ServerAliveInterval=15` | 15 秒ごとに keepalive を送り無通信切断を防ぐ | | `-o ServerAliveCountMax=3` | 応答が 3 回連続で無ければ切断と判断 | | `-o IdentityFile=~/.ssh/id_ed25519` | 使用する秘密鍵を明示 | | `-o follow_symlinks` | リモートのシンボリックリンクをたどる | | `-o idmap=user` | リモートの UID をローカルユーザーへマッピング | | `-C` / `-o compression=yes` | 転送を圧縮(低速回線で有効) | | `-o ro` | 読み取り専用でマウント | ::: tip 鍵認証と `~/.ssh/config` を整えておくと、`sshfs myserver:/var/www ~/mnt/server` のように Host エイリアスだけで接続できる。詳しくは関連記事を参照。 ::: ## 起動時に自動でマウントするには? {#fstab} > **結論**: `/etc/fstab` に `fuse.sshfs` タイプで記述すれば自動マウントできる。ネットワーク依存のため `_netdev` を必ず付ける。 ```bash # /etc/fstab user@server:/var/www /home/local/mnt/server fuse.sshfs _netdev,reconnect,IdentityFile=/home/local/.ssh/id_ed25519,idmap=user,allow_other 0 0 ``` `allow_other`(マウントしたユーザー以外もアクセス許可)を使う場合は、`/etc/fuse.conf` で許可が必要: ```bash # /etc/fuse.conf user_allow_other ``` ::: warning fstab に書く場合、ブート時点ではネットワーク・鍵がまだ使えないことがある。`_netdev` を付け、必要なら自動マウントではなく `users,noauto` にして手動 / ログイン時マウントにする方が事故が少ない。 ::: ## トラブルシューティング {#troubleshooting} > **結論**: 多くは「鍵 / 権限」「切断後のスタイルマウント」「allow_other 未許可」のいずれか。症状から原因を切り分ける。 ### Permission denied ```bash $ sshfs user@server:/var/www ~/mnt/server read: Connection reset by peer ``` - まず素の SSH で入れるか確認: `ssh user@server` - 鍵の指定漏れ → `-o IdentityFile=...` を付ける - リモート側ディレクトリへの読み書き権限を確認: `ls -ld /var/www` ### Transport endpoint is not connected(スタイルマウント) 接続が切れた後にマウントポイントが壊れた状態で残るとこのエラーになる。一度アンマウントしてから貼り直す。 ```bash $ fusermount3 -u ~/mnt/server $ sshfs user@server:/var/www ~/mnt/server -o reconnect ``` ### ファイルの所有者が nobody / 数字で表示される ローカルとリモートで UID が一致していないため。`-o idmap=user` を付けるとマウントユーザーへマッピングされ表示が揃う。 ### 他ユーザー(や www-data 等)からアクセスできない `-o allow_other` を付け、かつ `/etc/fuse.conf` に `user_allow_other` を記述する。 ## scp / rsync との使い分けは? {#compare} > **結論**: 頻繁に直接編集するなら sshfs、大量・定期の同期は rsync、単発コピーは scp。役割が異なるので競合しない。 | 用途 | 推奨 | | ------------------------------- | ----- | | リモートを直接編集・閲覧 | sshfs | | 大量データの同期 / バックアップ | rsync | | 単発のファイルコピー | scp | | 低速・不安定回線での確実な転送 | rsync | ::: tip **コピペ用テンプレ** ```bash # マウント(実務向け) mkdir -p ~/mnt/server sshfs user@server:/var/www ~/mnt/server -o reconnect,ServerAliveInterval=15,idmap=user # アンマウント fusermount3 -u ~/mnt/server ``` ::: ## 次に読む {#next} - [ファイル転送の基本(scp / rsync)](/articles/tutorials/scp-rsync-basics) - [~/.ssh/config 活用術で接続を整理する](/articles/tutorials/ssh-config-tips) - [SSH 鍵認証セットアップ](/articles/tutorials/ssh-key-setup) # stat コマンド入門 - ファイルメタ情報を読み解く Source: https://penguin-gym-linux.com/articles/tutorials/stat-file-inspection ## stat コマンドでわかること {#intro} `stat` は `ls -l` より詳細なファイルメタ情報を 1 コマンドで取得できる。タイムスタンプ・inode・ハードリンク数・ブロック数まで、ファイルシステムが記録するすべての属性を確認できる。 ::: tip **結論(実務の型)** - タイムスタンプを確認したい → `stat filename` - スクリプトで特定フィールドだけ欲しい → `stat -c "%Y" filename`(更新時刻の Unix 時刻) - ファイルシステムの残り容量・inode 数を確認 → `stat -f /path` ::: ## stat の基本的な使い方 {#basic} ```bash $ stat /etc/hosts ``` ```output File: /etc/hosts Size: 220 Blocks: 8 IO Block: 4096 regular file Device: fd00h/64768d Inode: 1179676 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2026-05-28 09:12:34.000000000 +0900 Modify: 2026-03-10 14:22:05.000000000 +0900 Change: 2026-03-10 14:22:05.000000000 +0900 Birth: 2025-01-15 08:00:00.000000000 +0900 ``` ファイル名・サイズ・inode 番号・パーミッション・所有者・3 種のタイムスタンプが一覧表示される。 ## 出力フィールドを読む {#fields} | フィールド | 意味 | | ------------------- | -------------------------------------------- | | `Size` | バイト単位のファイルサイズ | | `Blocks` | 512 バイトブロック単位の実使用量 | | `IO Block` | ファイルシステムのブロックサイズ | | `Inode` | inode 番号(ファイルシステム内の一意識別子) | | `Links` | ハードリンク数 | | `Access (0644/...)` | パーミッション(8 進数表記と記号表記) | | `Uid / Gid` | 所有ユーザーとグループ | ::: tip `Blocks` は実際にディスク上で使用している 512 バイトブロックの数。`Size` より大きくなることがある(ブロック境界のアライメントによる)。 ::: ## タイムスタンプ 3 種の違いを理解する {#timestamps} stat 出力には `Access`、`Modify`、`Change` の 3 つのタイムスタンプが表示される。これらは似ているようで意味が異なる。 | タイムスタンプ | 略称 | 更新タイミング | | -------------- | ----- | ------------------------------------------------------ | | `Access` | atime | ファイルを**読んだ**とき | | `Modify` | mtime | ファイルの**内容を変更した**とき | | `Change` | ctime | inode の**属性を変更した**とき(mtime 更新でも変わる) | | `Birth` | btime | ファイルを**作成した**とき(fs によっては未サポート) | ::: warning **ctime は「作成時刻」ではない** `Change` は "creation" の略ではなく "change" の略。`chmod` や `chown` を実行した場合も ctime は更新される。ファイル作成時刻が欲しい場合は `Birth`(btime)を使うが、ext4 以外のファイルシステムでは `--` と表示されることがある。 ::: ### atime の性能上の注意 多くの Linux では atime 更新がディスク書き込みを発生させる。高頻度アクセスのシステムでは `/etc/fstab` の `noatime` オプションで無効化されていることが多い。 ```bash $ grep noatime /etc/fstab ``` ## inode 番号の活用 {#inode} inode 番号は同一ファイルシステム内でファイルを一意に識別する数値。ハードリンクは同じ inode を指す。 ```bash $ stat /etc/hosts | grep Inode Inode: 1179676 ``` ```bash # ハードリンクを作成して inode が同じか確認 $ ln file.txt hardlink.txt $ stat -c "%i %n" file.txt hardlink.txt 1234567 file.txt 1234567 hardlink.txt ``` `Links` カウントが 2 になり、2 つの名前(ディレクトリエントリ)が同じ inode を指していることが確認できる。 ## フォーマットオプションで必要な情報だけ取り出す {#format} スクリプトで使う場合は `-c`(または `--format`)で出力形式を指定する。 ```bash # ファイルサイズ(バイト) $ stat -c "%s" /var/log/syslog 1048576 # 最終更新時刻(Unix タイムスタンプ) $ stat -c "%Y" /var/log/syslog 1748700000 # パーミッション(8 進数)と所有者 $ stat -c "%a %U" /etc/hosts 644 root # 複数フィールドを整形して出力 $ stat -c "Name: %n | Size: %s bytes | Modified: %y" /etc/hosts Name: /etc/hosts | Size: 220 bytes | Modified: 2026-03-10 14:22:05.000000000 +0900 ``` 主なフォーマット指定子: | 指定子 | 意味 | | ------ | -------------------------------------------- | | `%n` | ファイル名 | | `%s` | バイトサイズ | | `%a` | パーミッション(8 進数) | | `%A` | パーミッション(記号表記、例: `-rw-r--r--`) | | `%U` | 所有ユーザー名 | | `%G` | 所有グループ名 | | `%i` | inode 番号 | | `%y` | 最終更新時刻(人間が読みやすい形式) | | `%Y` | 最終更新時刻(Unix タイムスタンプ) | | `%x` | 最終アクセス時刻 | | `%z` | 最終属性変更時刻 | | `%h` | ハードリンク数 | ::: tip `%y` と `%Y` の違いに注意。小文字 `%y` は人間が読みやすいタイムスタンプ、大文字 `%Y` は Unix 時刻(エポック秒)。スクリプトで比較・計算する場合は `%Y` を使う。 ::: ## ファイルシステム情報を取得する {#filesystem} `-f` オプションを付けるとファイル単体ではなくファイルシステム全体の情報を取得する。 ```bash $ stat -f / ``` ```output File: "/" ID: fd0000000000 Namelen: 255 Type: ext2/ext3 Block size: 4096 Fundamental block size: 4096 Blocks: Total: 20000000 Free: 12000000 Available: 11000000 Inodes: Total: 5000000 Free: 4800000 ``` | フィールド | 意味 | | ------------------------------ | -------------------------------------------------- | | `Block size` | ファイルシステムのブロックサイズ | | `Blocks: Total/Free/Available` | 総ブロック数・空きブロック数・非 root で使える空き | | `Inodes: Total/Free` | inode の総数と残数 | ::: warning inode の残数(`Inodes: Free`)がゼロに近い場合、「ディスクに容量があるのに書き込めない」障害が発生する。`df -i` でも同様に確認できる。inode 枯渇はログファイル大量生成や一時ファイル放置によって起きやすい。 ::: ## 実践的なユースケース {#practical} ### ファイルが最後にいつ変更されたか確認する ```bash $ stat -c "%y" /etc/nginx/nginx.conf 2026-04-01 10:30:00.000000000 +0900 ``` ### バックアップ前に mtime を記録しておく ```bash $ stat -c "%Y %n" /important/data.db 1748700000 /important/data.db ``` ### 複数ファイルの更新時刻を比較する ```bash $ for f in /etc/nginx/*.conf; do stat -c "%Y %n" "$f"; done | sort -n 1748600000 /etc/nginx/mime.types 1748700000 /etc/nginx/nginx.conf ``` ### パーミッションを数値で確認する ```bash $ stat -c "%a" /etc/hosts 644 ``` ## 次に読む {#next} - [シンボリックリンクとハードリンク - inode の仕組み](/articles/tutorials/symbolic-links) - [du と df の違い - ディスク容量を正しく測る](/articles/tutorials/du-df-difference) - [chmod・chownの使い方 - 権限管理の基本](/articles/tutorials/permissions-basics) # strace 入門 — システムコールを覗いて障害を切り分ける Source: https://penguin-gym-linux.com/articles/tutorials/strace-basics ## strace とは何か? {#what-is-strace} プロセスが発行するシステムコール(カーネルへの処理要求)とシグナルをリアルタイムで記録・表示するデバッグツール。ファイルを開く・ネットワーク接続する・プロセスを fork するといった動作をすべて"生"で観測できる。 ::: tip **どんなときに使うか** - ログに何も出ない、エラーメッセージが不親切なとき - どの設定ファイルが実際に読み込まれているか不明なとき - 「Permission denied」が発生するがどのパスが拒否されているか不明なとき - ネットワーク接続が失敗する原因を追いたいとき ::: ## なぜ strace が障害切り分けで役立つのか? {#why-strace} アプリケーションはどれほど複雑でも、最終的にはカーネルが提供するシステムコールを通じてハードウェアや OS リソースにアクセスする。strace はその接点を直接監視するため、ソースコードがなくても「プロセスが実際に何をしようとして失敗したか」を確認できる。 ::: highlight `errno: ENOENT`(ファイルが存在しない)、`errno: EACCES`(権限なし)、`errno: ECONNREFUSED`(接続拒否)といったカーネルレベルのエラーが直接見える。ログに「エラーが発生しました」としか出ない場合でも、strace なら根本原因まで到達できる。 ::: ## どうやってインストールするのか? {#install} 多くのディストリビューションには標準で含まれているが、なければパッケージマネージャで導入する。 ```bash # Ubuntu / Debian sudo apt install strace # RHEL / CentOS / Fedora sudo dnf install strace ``` インストール確認: ```bash strace --version ``` ```output strace -- version 6.x ... ``` ## 基本的な使い方 {#basic-usage} ### コマンドを直接追跡する ```bash strace ls /tmp ``` 大量の出力が流れる。最後の数行に終了ステータスが表示される。 ```output execve("/bin/ls", ["ls", "/tmp"], 0x7fff... /* 20 vars */) = 0 brk(NULL) = 0x55d3e... ... openat(AT_FDCWD, "/tmp", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 ... +++ exited with 0 +++ ``` ### 実行中プロセスにアタッチする {#attach} ```bash sudo strace -p PID ``` `PID` は `ps aux` や `pgrep` で取得する。 ```bash # プロセス名から PID を探す pgrep nginx # アタッチ sudo strace -p 12345 ``` ::: warning `-p` でのアタッチには通常 root 権限が必要(自分のプロセスは不要)。本番サービスにアタッチするとパフォーマンスに影響するため注意。 ::: ### 子プロセスも追跡する マルチプロセスのサービスでは `-f` が必須。 ```bash strace -f -p PID ``` ## 特定のシステムコールだけ追跡するには? {#filter} 出力が膨大なため、`-e trace=` で絞り込む。 ```bash strace -e trace=openat ls /tmp ``` よく使うフィルタ: | フィルタ | 追跡対象 | | ------------ | ------------------ | | `openat` | ファイルを開く | | `read,write` | 読み書き | | `connect` | ネットワーク接続 | | `execve` | プロセス起動 | | `file` | ファイル系全般 | | `network` | ネットワーク系全般 | ```bash # ファイルアクセスだけ追跡 strace -e trace=file command # ネットワーク接続だけ追跡 strace -e trace=network command ``` ## strace の出力をどう読み解くか? {#reading-output} 基本フォーマット: ``` システムコール名(引数...) = 戻り値 ``` 成功例: ```output openat(AT_FDCWD, "/etc/hosts", O_RDONLY) = 3 ``` 失敗例(ENOENT — ファイルなし): ```output openat(AT_FDCWD, "/etc/myapp.conf", O_RDONLY) = -1 ENOENT (No such file or directory) ``` 失敗例(EACCES — 権限なし): ```output openat(AT_FDCWD, "/root/secret", O_RDONLY) = -1 EACCES (Permission denied) ``` ::: tip **戻り値の読み方** - `= 0` 以上: 成功(ファイルディスクリプタ番号や処理済みバイト数) - `= -1`: 失敗。続く `ENOENT` 等が具体的な理由 ::: ## 定番の障害切り分けパターン {#patterns} ### パターン1:設定ファイルが読み込まれない ```bash strace -e trace=openat myapp 2>&1 | grep "ENOENT" ``` ```output openat(AT_FDCWD, "/etc/myapp/config.yaml", O_RDONLY) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/home/user/.myapp/config.yaml", O_RDONLY) = -1 ENOENT (No such file or directory) ``` アプリが探しているパスを直接確認できる。 ### パターン2:Permission denied の原因を特定する ```bash strace -e trace=openat myapp 2>&1 | grep "EACCES" ``` ```output openat(AT_FDCWD, "/var/run/myapp.pid", O_WRONLY|O_CREAT) = -1 EACCES (Permission denied) ``` どのパスに書き込もうとして拒否されたかが判明する。 ### パターン3:接続失敗の原因を確認する ```bash strace -e trace=network myapp 2>&1 | grep -E "connect|ECONNREFUSED" ``` ```output connect(3, {sa_family=AF_INET, sin_port=htons(5432), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ECONNREFUSED (Connection refused) ``` ポート番号やアドレスが意図通りか確認できる。 ### パターン4:出力をファイルに保存して後で解析する ```bash strace -o /tmp/trace.log -f myapp grep ENOENT /tmp/trace.log ``` ::: tip `-o` でファイル保存すると `stderr` が汚れず、後から `grep` や `less` で調査しやすい。 ::: ## 実務で役立つオプション一覧 {#options} | オプション | 効果 | | ------------- | ----------------------------------------- | | `-p PID` | 実行中プロセスにアタッチ | | `-f` | fork/clone した子プロセスも追跡 | | `-e trace=` | 追跡するシステムコールを絞る | | `-o ファイル` | 出力を stderr ではなくファイルへ | | `-t` | 各行の先頭に時刻を表示 | | `-tt` | マイクロ秒精度のタイムスタンプ | | `-T` | 各システムコールの所要時間を表示 | | `-c` | 統計サマリを表示(回数・時間・エラー) | | `-s 256` | 文字列の最大表示長を変更(デフォルト 32) | | `-q` | "Attaching..." 等のメッセージを抑制 | ### `-c` での統計確認 ```bash strace -c ls /tmp ``` ```output % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 35.14 0.000147 14 10 mmap 18.42 0.000077 11 7 openat ... ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000418 48 2 total ``` どのシステムコールに時間がかかっているか一覧で把握できる。 ## 次に読む {#next} - [journalctl でログを調査する](/articles/tutorials/journalctl-basics) - [プロセス管理入門 — ps・top・kill の使い方](/articles/tutorials/process-management-basics) - [プロセス管理の実践 — ジョブコントロールと pkill・nice](/articles/tutorials/process-management-practical) # sudo/suの使い分け - 権限昇格の安全な方法 Source: https://penguin-gym-linux.com/articles/tutorials/sudo-su-switching ## この記事で身につくこと {#intro} - `sudo` と `su` を、目的に応じて**使い分けられる**ようになります。 - なぜ `su` より `sudo` が推奨されるのかを、**セキュリティ観点**で説明できるようになります。 - `sudoers` の基本設定を、自分で安全に書けるようになります。 - よくある**事故パターンとその回避策**が分かるようになります。 **想定読者**:Ubuntu でサーバを触りはじめ、`sudo` を「おまじない」として打っている人。 ### 先に用語を整理する {#terms} 初めて出てくる言葉を、ここで一度だけ定義します。 - **権限昇格**とは、今のユーザーでは許されていない操作を、より強い権限で実行することです。「特権昇格」「privilege escalation」とも呼びます。 - **root**(ルート)とは、システム上で何でもできる管理者ユーザーの名前です。「スーパーユーザー」とも呼びます。 - **ログインシェル**とは、ログイン時に読み込まれる設定(`.profile` や `.bashrc`)を反映した状態のシェルです。同じユーザーでも、ログインシェルかどうかで `PATH` などの環境変数が変わります。 - **環境変数**とは、シェルが持っている設定値です。`PATH`(コマンドを探す場所の一覧)や `HOME`(ホームディレクトリ)が代表例です。 - **sudoers** とは、誰にどのコマンドを許可するかを書いた設定ファイル `/etc/sudoers` のことです。 - **visudo** とは、`sudoers` を構文チェック付きで編集する専用コマンドです。 - **NOPASSWD** とは、sudoers に書ける指定で、パスワード入力を省略して実行を許可する設定です。 ::: tip **結論(実務の型)** - **1コマンドだけ root 権限が必要** → `sudo command` - **root として複数操作する(やむを得ない場合のみ)** → `sudo -i` - **別ユーザーとして1コマンド実行** → `sudo -u username command` - `su -` は root パスワードが必要。Ubuntu のデフォルトでは root パスワードが無効なため使えない ::: ::: warning **前提(対象環境)** - OS:Ubuntu(または Debian 系) - 作業ユーザーが `sudo` グループに追加済み ::: ## 1. sudo と su の違いは何か? {#difference} `sudo`(substitute user do)と `su`(switch user)は、どちらも権限昇格のためのコマンドだが、仕組みが根本的に異なる。 | 項目 | sudo | su | | ----------------- | --------------------------- | ----------------------------- | | 認証 | **自分のパスワード** | **切り替え先のパスワード** | | root パスワード | 不要 | 必要(`su -` の場合) | | 操作ログ | `/var/log/auth.log` に記録 | 記録が弱い | | 権限範囲 | `/etc/sudoers` で細かく制御 | root に全権限を渡す | | Ubuntu デフォルト | 使用可能 | root パスワード無効で使えない | `sudo` は**ユーザーごとに許可コマンドを限定できる**点がセキュリティ上の強みだ。root パスワードをチームで共有する必要もない。 ## 2. sudo の使い方 {#sudo-usage} ### 2-1. 基本形:1コマンドだけ実行 ```bash $ sudo command ``` 例: ```bash $ sudo apt update $ sudo systemctl restart nginx ``` ### 2-2. root のログインシェルを起動する ```bash $ sudo -i ``` `sudo -i` は root の環境変数と `.profile` を読み込んだ状態でシェルを起動する。長時間の root 作業が必要な場合に使う。 ::: warning root シェルを起動したままにしないこと。作業が終わったら `exit` で抜ける。 ::: ### 2-3. 別ユーザーとして実行する ```bash $ sudo -u username command ``` 例:www-data ユーザーとしてコマンドを実行: ```bash $ sudo -u www-data php /var/www/html/artisan cache:clear ``` ### 2-4. 権限昇格の持続時間 `sudo` は初回認証後、デフォルト 15 分間はパスワードなしで再実行できる。タイムアウトを即座にリセットする場合: ```bash $ sudo -k ``` 現在の自分の sudo 権限を確認する: ```bash $ sudo -l ``` ```output User alice may run the following commands on hostname: (ALL : ALL) ALL ``` `(ALL : ALL) ALL` は「どのユーザーとしても、どのコマンドでも実行できる」という意味。特定コマンドだけ許可されている場合は、そのコマンドのパスが列挙される。 ## 3. su の使い方 {#su-usage} ### 3-1. su - でログインシェルを起動 ```bash $ su - [username] ``` `-` オプション(`-l` / `--login` と同義)は、**切り替え先ユーザーのログイン環境**(ホームディレクトリ・環境変数・PATH)を再現する。 ```bash $ su - deploy # deploy ユーザーのログインシェルを起動 ``` ### 3-2. su と su - の違い ```bash $ su username # NG:現在の環境変数をそのまま引き継ぐ $ su - username # OK:ログイン時と同じ環境を再現 ``` ::: warning `su username`(`-` なし)は現在の環境変数を引き継ぐため、切り替え先のユーザー環境が正しく再現されない。特に PATH が混在して `command not found` になる事故が多い。 ::: ### 3-3. Ubuntu で su が使えない理由 Ubuntu のデフォルトでは `root` アカウントのパスワードが無効化されている。`su -` で root に切り替えようとすると認証に失敗する。 ```bash $ su - Password: su: Authentication failure # root パスワードが無効 ``` Ubuntu で root シェルが必要な場合は `sudo -i` を使う。 ## 4. なぜ sudo が推奨されるのか? {#why-sudo} `sudo` が推奨される理由はセキュリティモデルの違いにある。 **操作ログが残る**: `sudo` を実行するたびに `/var/log/auth.log` に「誰が・何時・何のコマンドを実行したか」が記録される。 ```bash # rsyslog がある環境(Ubuntu 22.04 など) $ sudo grep sudo /var/log/auth.log | tail -3 # journald のみの環境(Ubuntu 24.04 の既定インストールなど) $ sudo journalctl -t sudo -n 3 ``` ```output May 31 10:30:01 hostname sudo: alice : TTY=pts/0 ; PWD=/home/alice ; USER=root ; COMMAND=/usr/bin/apt update ``` Ubuntu 24.04 は既定インストールで rsyslog を含まないため、`/var/log/auth.log` が存在しない構成がある。その場合はログが journald に集約されているため `journalctl` 側を使う。 「誰が」「どのディレクトリで」「どのコマンドを」実行したかが 1 行に残る。障害時に操作を追跡できるのは、この記録があるからだ。 **最小権限の原則**: sudoers でコマンドを個別に許可できるため、特定ユーザーに必要最低限の権限だけを付与できる。 **root パスワード不要**: チームメンバーに root パスワードを教える必要がない。個人の資格情報で認証が完結する。 ## 5. sudoers の設定(visudo) {#sudoers} ### 5-1. visudo で安全に編集する `/etc/sudoers` は**必ず `visudo` コマンドで編集**する。`visudo` はファイルを保存する前に構文チェックを行い、設定ミスでシステムにアクセスできなくなる事故を防ぐ。 ```bash $ sudo visudo ``` ::: danger **`/etc/sudoers` の直接編集は禁止** `/etc/sudoers` を `vi` や `nano` で直接編集しないこと。**失敗するとどうなるか**は次のとおり。 - 構文を 1 文字ミスすると `sudo` 自体が動作不能になる。 - Ubuntu は root パスワードが無効なため、`su -` での復旧もできない。物理コンソールからリカバリモードで起動する必要が生じる。 - SSH 越しの作業でこれが起きた場合、そのサーバに管理者として入れなくなる。 **安全に試す方法**は次の 3 つ。 - 編集は必ず `sudo visudo` で行う。保存時に構文チェックが走り、エラーがあれば書き込みを拒否してくれる。 - 練習は本番サーバではなく、手元の仮想マシンやコンテナで行う。 - 作業前に別のターミナルで `sudo -v` が通る状態のセッションを開いたままにしておく。万一 sudoers を壊しても、そのセッションから修正できる。 ::: ### 5-2. 基本的な書式 ``` # ユーザー ホスト=(実行ユーザー) コマンド alice ALL=(ALL) ALL # NOPASSWD:パスワードなしで特定コマンドを許可 deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx ``` ### 5-3. グループに対して許可する ``` # %グループ名 で指定 %admin ALL=(ALL) ALL %deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl ``` ### 5-4. drop-in ファイルで管理する(推奨) 大規模環境では `sudoers` 本体を直接編集せず、`/etc/sudoers.d/` 配下にファイルを置く方法が推奨される。 ```bash $ sudo visudo -f /etc/sudoers.d/deploy ``` ``` deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx ``` ## 6. よくある事故パターン {#errors} ### NOPASSWD を全コマンドに設定する ``` # 危険:何でもパスワードなしで実行できる alice ALL=(ALL) NOPASSWD: ALL ``` 開発環境の「楽さ」のために設定したまま本番に持ち込むと深刻なリスクになる。このユーザーのセッションを奪われた時点で、攻撃者はパスワードを知らずに root 相当の操作ができる。許可は特定コマンドのみに限定すること。 ### visudo を使わずに sudoers を編集する 直接 `vi /etc/sudoers` で編集して構文ミスを起こすと、sudo が動作不能になる。`visudo` は保存時に構文チェックを行うため、必ず使うこと。 ### `su username` で環境変数が混在する ```bash # NG:PATH が現在のシェルの設定を引き継ぐ $ su deploy # OK:deploy のログイン環境を再現 $ su - deploy ``` ## トラブルシューティング {#troubleshooting} ### 症状: `alice is not in the sudoers file. This incident will be reported.` **原因**: そのユーザーが `sudo` グループ(RHEL 系では `wheel`)に所属していない。 **確認**: ```bash id -nG ``` ```output alice ``` `sudo` が含まれていなければ権限がない。 **対処**: 別の管理者権限を持つユーザーから追加してもらう。 ```bash sudo usermod -aG sudo alice # 管理者側で実行 ``` 追加後は、対象ユーザーがログアウトして再ログインするまで反映されない。管理者が誰もいない場合は、物理コンソールからリカバリモードで起動して修正する。 ### 症状: `sudo: unable to resolve host ` **原因**: `/etc/hostname` の名前が `/etc/hosts` に登録されていない。`sudo` 自体は動くが、毎回この警告が出る。 **確認**: ```bash hostname grep "$(hostname)" /etc/hosts ``` **対処**: `/etc/hosts` の `127.0.0.1` の行に現在のホスト名を追記する。編集前にコピーを取っておくと戻せる。 ```bash sudo cp /etc/hosts /etc/hosts.bak sudo nano /etc/hosts ``` ```output 127.0.0.1 localhost myhost ``` ### 症状: `sudo: no tty present and no askpass program specified` **原因**: cron やスクリプトなど端末のない環境で、パスワード入力を求める `sudo` を実行した。 **確認**: 実行元が cron / systemd / CI などの非対話環境かどうかを確認する。 **対処**: そのコマンドだけを sudoers で `NOPASSWD` 指定する(対象は絶対パスで限定)。 ```bash sudo visudo -f /etc/sudoers.d/deploy ``` ``` deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx ``` ### 症状: `3 incorrect password attempts` **原因**: パスワードを 3 回間違えた。`sudo` は 3 回失敗で試行を打ち切る。 **確認**: 入力しているのが **root のパスワードではなく自分のパスワード** かを確認する。`sudo` は自分のパスワードを要求する。 **対処**: もう一度 `sudo` を実行して自分のパスワードを入力する。忘れた場合は別の管理者に `sudo passwd alice` でリセットしてもらう。 ## 作業完了チェックリスト {#checklist} - [ ] 1 コマンドだけなら `sudo command`、複数操作なら `sudo -i` を選べた - [ ] root シェルでの作業後、`exit` で抜けた - [ ] `sudoers` の編集は `visudo` 経由で行った - [ ] `NOPASSWD` を書く場合、対象コマンドを絶対パスで限定した - [ ] `sudo -l` で自分に許可されている操作を確認した ## 次に読む {#next} - [ユーザー管理入門 - useradd/usermod/userdelの基本操作](/articles/tutorials/user-management-basics) - [グループ管理入門 - groupadd/groupmodとアクセス制御](/articles/tutorials/group-management-basics) - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) # sudoers 設定入門 - 安全に権限を委譲する Source: https://penguin-gym-linux.com/articles/tutorials/sudoers-configuration ## sudoers とは何か? {#what-is-sudoers} `/etc/sudoers` は「誰が・誰として・何を実行できるか」を定義するファイルで、`sudo` コマンドの権限委譲ルールをすべて管理している。**直接編集は禁止**であり、必ず `visudo` を使う。 ::: tip **結論(実務の型)** - `visudo` 以外で sudoers を編集しない(シンタックスエラーで sudo が完全ロックされる) - 権限委譲はできる限り**特定コマンドに限定**する(`ALL` は最終手段) - NOPASSWD は CI/CD や特定の自動化にのみ使う ::: ::: warning **対象環境** - OS:Ubuntu 22.04 / 24.04(Debian 系全般に準拠) - sudo がインストール済みであること(デフォルトで入っている) ::: ## なぜ visudo を使うのか? {#why-visudo} `visudo` は構文検査付きのエディタラッパーで、**保存時にシンタックスエラーを検出し、エラーがあると上書きしない**。これにより sudo が壊れた状態で保存されるのを防ぐ。 ```bash $ sudo visudo ``` デフォルトエディタは `nano`(Ubuntu では `EDITOR` 環境変数で変更可能)。 ```bash $ sudo EDITOR=vim visudo ``` ::: danger `sudo vi /etc/sudoers` や `sudo nano /etc/sudoers` で直接編集した場合、構文ミスをそのまま保存できてしまう。エラーが入ると **sudo 自体が使えなくなり**、root パスワードがなければ復旧に root シェルが必要になる。 ::: ## sudoers の構文 {#syntax} ### 基本形 ``` ユーザー ホスト=(実行ユーザー) コマンド ``` 最も基本的な例: ``` alice ALL=(ALL:ALL) ALL ``` 各フィールドの意味: | フィールド | 値の例 | 意味 | | ------------ | ----------- | ------------------------------ | | ユーザー | `alice` | 適用対象ユーザー | | ホスト | `ALL` | 全ホストで有効 | | 実行ユーザー | `(ALL:ALL)` | root を含む全ユーザー/グループ | | コマンド | `ALL` | すべてのコマンド | ### グループへの委譲 行頭に `%` を付けるとグループを指定できる。 ``` %sudo ALL=(ALL:ALL) ALL %wheel ALL=(ALL) ALL ``` Ubuntu では `%sudo` グループ、CentOS/RHEL では `%wheel` グループが sudo 権限を持つのが慣例。 ## 特定コマンドへの権限委譲 {#specific-commands} `ALL` を使わず、必要なコマンドだけを列挙するのが**最も安全な設定**。 ``` # nginx の再起動だけ許可 bob ALL=(root) /usr/bin/systemctl restart nginx # 複数コマンドはカンマ区切り carol ALL=(root) /usr/bin/systemctl restart nginx, /usr/bin/systemctl status nginx ``` コマンドは**フルパス**で記述する(`which nginx` で確認)。 ::: warning `/usr/bin/vim` などのエディタを許可すると、vim から `:!sh` でシェルを呼び出せるため **事実上 root 権限全取得**になる。エディタは原則禁止。 ::: ## NOPASSWD 設定 {#nopasswd} パスワード入力なしで実行できる設定。CI/CD パイプラインやデプロイスクリプトで使う。 ``` # 特定コマンドのみ NOPASSWD deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp # グループ全体に NOPASSWD(非推奨・注意して使う) %automation ALL=(ALL) NOPASSWD: ALL ``` ::: warning **NOPASSWD の注意点** - スクリプトが悪用された場合、パスワードなしで root 権限が使われる - 特定コマンドのみに絞ること。`NOPASSWD: ALL` は最小限の用途に限定する - `/var/log/auth.log` で sudo の使用履歴を監視する ::: ## include ディレクトリ(推奨構成) {#include} 大規模環境や複数サービスの管理では、`/etc/sudoers.d/` 配下に個別ファイルを置く構成が推奨される。 ```bash # 専用ファイルを作成(visudo -f で構文検査付き) $ sudo visudo -f /etc/sudoers.d/deploy-user ``` ファイル内容例: ``` # /etc/sudoers.d/deploy-user deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp, /usr/bin/systemctl status myapp ``` ::: tip `/etc/sudoers.d/` 配下のファイルは `/etc/sudoers` 末尾の `#includedir /etc/sudoers.d` で自動読み込みされる。パーミッションは `0440`(`root:root`)でないと読み込まれない点に注意。 ```bash $ sudo chmod 0440 /etc/sudoers.d/deploy-user $ sudo chown root:root /etc/sudoers.d/deploy-user ``` ::: ## 権限の確認と動作テスト {#test} ### 自分の sudo 権限を確認 ```bash $ sudo -l User alice may run the following commands on server: (ALL : ALL) ALL ``` ### 別ユーザーとして実行 ```bash $ sudo -u www-data whoami www-data ``` ### 設定を確認してから本番実行 ```bash # コマンドを実行せずに権限確認 $ sudo -l -U bob ``` ## Defaults で動作を調整する {#defaults} `Defaults` エントリで sudo のデフォルト動作を変更できる。 ``` # パスワードキャッシュを無効化(毎回入力を要求) Defaults timestamp_timeout=0 # sudo 実行時に現在の PATH を引き継がない(デフォルト・安全) Defaults env_reset # 特定ユーザーにのみ適用 Defaults:bob timestamp_timeout=5 ``` | オプション | 意味 | | --------------------------- | --------------------------------------------- | | `timestamp_timeout=N` | sudo キャッシュの有効期間(分)。0 で毎回要求 | | `env_reset` | 環境変数をリセット(デフォルト有効) | | `logfile=/var/log/sudo.log` | ログの出力先を指定 | | `passwd_tries=N` | パスワード試行回数(デフォルト 3) | ## よくある設定ミスと対処 {#errors} ### sudo: /etc/sudoers is world writable が出る ```bash $ sudo chmod 0440 /etc/sudoers ``` `/etc/sudoers` のパーミッションは `0440`(読み取り専用)である必要がある。 ### visudo で編集後に変更が反映されない - `sudo -k` でキャッシュをクリアしてから再度実行する - グループ変更後は `newgrp sudo` またはログアウト/ログインが必要 ### sudo: command not found になる `env_reset` が有効で `/usr/local/bin` 等が PATH に含まれないケース。 ``` Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ``` ## 次に読む {#next} - [ユーザー管理入門 - useradd/usermod/userdelの基本操作](/articles/tutorials/user-management-basics) - [chmod・chownの使い方 - Linux権限管理の基本](/articles/tutorials/permissions-basics) - [ファイアウォール設定入門 - ufwとfirewalldの基礎](/articles/tutorials/firewall-basics) # SUID/SGID/Sticky bit - 特殊権限ビットを理解する Source: https://penguin-gym-linux.com/articles/tutorials/suid-sgid-sticky ## SUID/SGID/Sticky bit とは? {#overview} 特殊権限ビット(SUID・SGID・Sticky bit)は、通常の rwx 権限に加えて設定できる 3 種類の特殊フラグ。**passwd のような root 権限が必要なコマンドを一般ユーザーが実行できる仕組み**や、**共有ディレクトリで他のユーザーのファイルを削除させない仕組み**はこれで実現されている。 | ビット | 数値 | 設定対象 | 主な用途 | | ---------- | ---- | -------------------------- | -------------------------------- | | SUID | 4 | 実行ファイル | 所有者の権限(UID)で実行させる | | SGID | 2 | 実行ファイル・ディレクトリ | グループ権限の継承 | | Sticky bit | 1 | ディレクトリ | 自分のファイルだけ削除可能にする | ## SUID(Set User ID)とは? {#suid} SUID が設定された実行ファイルは、**実行したユーザーの権限ではなく、ファイル所有者の権限で動作する**。 `/usr/bin/passwd` が典型例。一般ユーザーが自分のパスワードを変更できるのは、passwd に SUID が設定されており root 権限で動作するためだ。 ```bash $ ls -l /usr/bin/passwd ``` ```output -rwsr-xr-x 1 root root 59976 Mar 22 2024 /usr/bin/passwd ``` 所有者の `x` 位置が `s` になっているのが SUID の印。 ### SUID を設定・解除する {#suid-set} ```bash # SUID を設定(シンボリック記法) $ chmod u+s /path/to/file # SUID を設定(数値記法: 4XXX の形) $ chmod 4755 /path/to/file # SUID を解除 $ chmod u-s /path/to/file ``` ::: warning SUID はセキュリティリスクを伴う。不必要なファイルへの設定は避け、定期的に棚卸しを行う。 ```bash # システム全体の SUID ファイルを検索 $ find / -perm -4000 -type f 2>/dev/null ``` ::: ## SGID(Set Group ID)とは? {#sgid} SGID の動作は設定対象によって異なる。**実行ファイルに設定した場合は所有グループの権限で動作**し、**ディレクトリに設定した場合は配下のファイルがそのグループを継承する**。 ### 実行ファイルへの SGID {#sgid-file} ```bash $ ls -l /usr/bin/write ``` ```output -rwxr-sr-x 1 root tty 14952 Mar 30 2023 /usr/bin/write ``` グループの `x` 位置が `s` になっているのが SGID の印。 ### ディレクトリへの SGID {#sgid-dir} チーム共有ディレクトリで特に有効。SGID を付けると、新規ファイルがディレクトリのグループを継承する。 ```bash # 共有ディレクトリに SGID を設定 $ chmod g+s /shared/project # 確認 $ ls -ld /shared/project ``` ```output drwxrwsr-x 2 user devteam 4096 Jun 1 12:00 /shared/project ``` このディレクトリ内に作成したファイルは、作成者のプライマリグループではなく `devteam` グループになる。 ```bash $ touch /shared/project/newfile.txt $ ls -l /shared/project/newfile.txt ``` ```output -rw-r--r-- 1 alice devteam 0 Jun 1 12:00 /shared/project/newfile.txt ``` ### SGID の設定コマンド {#sgid-set} ```bash # シンボリック記法 $ chmod g+s /path/to/dir # 数値記法(2XXX の形) $ chmod 2775 /path/to/dir ``` ## Sticky bit とは? {#sticky} Sticky bit はディレクトリに設定し、**そのディレクトリ内のファイルを所有者以外が削除・改名できなくする**。world-writable な共有ディレクトリで他ユーザーのファイルを保護するために使う。 `/tmp` が典型例。誰でも書き込めるが、自分のファイルしか削除できない。 ```bash $ ls -ld /tmp ``` ```output drwxrwxrwt 17 root root 4096 Jun 1 12:00 /tmp ``` その他ユーザー(others)の `x` 位置が `t` になっているのが Sticky bit の印。 ### Sticky bit を設定・解除する {#sticky-set} ```bash # Sticky bit を設定(シンボリック記法) $ chmod +t /shared/dir # Sticky bit を設定(数値記法: 1XXX の形) $ chmod 1777 /shared/dir # Sticky bit を解除 $ chmod -t /shared/dir ``` 動作確認: ```bash $ mkdir /tmp/testdir && chmod 1777 /tmp/testdir $ ls -ld /tmp/testdir ``` ```output drwxrwxrwt 2 user group 40 Jun 1 12:00 /tmp/testdir ``` ## 数値記法でまとめて設定する {#octal} 特殊ビットと通常権限を組み合わせる場合、4 桁の数値で指定する。先頭の桁が特殊ビット(SUID=4、SGID=2、Sticky=1 の合計)。 | 設定 | 数値 | コマンド例 | | --------------- | ---- | ----------------- | | SUID のみ | 4755 | `chmod 4755 file` | | SGID のみ | 2755 | `chmod 2755 dir` | | Sticky bit のみ | 1777 | `chmod 1777 dir` | | SUID + SGID | 6755 | `chmod 6755 file` | ```bash # SUID 付き(owner=rwx, group=rx, others=rx) $ chmod 4755 myprogram # SGID 付き共有ディレクトリ(owner=rwx, group=rwx, others=rx) $ chmod 2775 shared_dir ``` ## ls -l での表示の読み方 {#display} 特殊ビットは `ls -l` の出力で確認できる。`x` が置き換わる位置に注目する。 ```output -rwsr-xr-x → SUID あり(owner の x が s) -rwxr-sr-x → SGID あり(group の x が s) drwxrwxrwt → Sticky bit あり(others の x が t) -rwSr--r-- → SUID あり・owner に実行権限なし(大文字 S) -rwxr-Sr-- → SGID あり・group に実行権限なし(大文字 S) drwxrwxrwT → Sticky bit あり・others に実行権限なし(大文字 T) ``` ::: tip 小文字 `s` / `t` → 特殊ビットと実行権限の両方が設定されている 大文字 `S` / `T` → 特殊ビットはあるが実行権限がない(多くの場合は設定ミス) ::: `find` コマンドで特殊ビットが設定されたファイルを一覧できる。 ```bash # SUID が設定されたファイルを検索 $ find / -perm -4000 -type f 2>/dev/null # SGID が設定されたファイル・ディレクトリを検索 $ find / -perm -2000 2>/dev/null # Sticky bit が設定されたディレクトリを検索 $ find / -perm -1000 -type d 2>/dev/null ``` ## セキュリティ上の注意点 {#security} 特殊権限ビットは利便性と引き換えにセキュリティリスクをはらむ。 ::: danger **SUID/SGID の設定は最小限に** 不要な SUID/SGID ファイルは権限昇格攻撃に利用される。シェルスクリプトへの SUID 設定は Linux では効力を持たないが、意図しない誤解を招くため設定しないこと。 ::: ::: warning **SUID ファイルの定期監査** ```bash # SUID/SGID ファイルを一覧してベースラインを記録する $ find / -perm /6000 -type f 2>/dev/null > /tmp/suid_baseline.txt ``` 定期的に実行して差分を確認することで、不正に追加された SUID ファイルを検出できる。 ::: ## 次に読む {#next} - [chmod・chownの使い方](/articles/tutorials/permissions-basics) - [sudo/suの使い分け](/articles/tutorials/sudo-su-switching) - [sudoers設定入門](/articles/tutorials/sudoers-configuration) # swap の役割と運用 - メモリ不足を回避する Source: https://penguin-gym-linux.com/articles/tutorials/swap-management ## swap とは何か? {#what-is-swap} swap は物理 RAM が不足したときにディスクをメモリの代替として使う仮想メモリ領域。RAM が枯渇してもすぐにプロセスが強制終了されず、処理を継続できる保険機能として働く。 swap 領域の形式は 2 種類ある。 | 形式 | 説明 | | ---------------------- | -------------------------------------------------- | | スワップパーティション | ディスクの専用パーティション。高速だが変更が難しい | | スワップファイル | 通常ファイルとして作成。容量変更が容易で現在主流 | ::: warning swap はディスク I/O を伴うため RAM より数十倍遅い。恒常的にスワップが発生している状態(スラッシング)は障害の前兆。根本原因はメモリ不足であり、swap を増やしても解決はできない。 ::: ## swap の状態を確認するには? {#check-swap} `free -h` が最も手軽な確認手段。`Swap:` 行の `used` 列が増加していればスワッピングが発生している。 ```bash free -h ``` ```output total used free shared buff/cache available Mem: 3.8Gi 2.1Gi 408Mi 45Mi 1.3Gi 1.4Gi Swap: 2.0Gi 512Mi 1.5Gi ``` swap の詳細(ファイル・パーティション別)を確認するには `swapon --show`。 ```bash swapon --show ``` ```output NAME TYPE SIZE USED PRIO /swapfile file 2G 512M -2 ``` スワッピングの発生頻度(ページイン・ページアウト)を確認するには `vmstat`。 ```bash vmstat 1 5 ``` ```output procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 524288 418032 98304 1350656 0 0 2 5 45 80 2 1 97 0 0 0 1 524288 200000 98304 1350672 8 12 0 20 65 120 2 1 90 7 0 ``` `si`(swap in)と `so`(swap out)が継続的に非ゼロの場合、スラッシングが疑われる。 ## スワップファイルを作成するには? {#create-swapfile} RAM が不足している環境でスワップファイルを追加する手順。Ubuntu / Debian 系の標準的な方法。 ```bash sudo fallocate -l 2G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile ``` ::: tip `fallocate` が使えない環境(NFS や一部のファイルシステム)では `dd if=/dev/zero of=/swapfile bs=1M count=2048` で代替できる。 ::: 有効化後、`swapon --show` で確認。 ```bash swapon --show ``` ```output NAME TYPE SIZE USED PRIO /swapfile file 2G 0B -2 ``` ### 再起動後も自動で有効にする `/etc/fstab` に追記して永続化する。 ```bash echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab ``` ::: warning `/etc/fstab` 編集後は `sudo swapon -a` で構文エラーがないか確認すること。エラーがあると起動に失敗する。 ::: ### スワップファイルを削除する場合 ```bash sudo swapoff /swapfile sudo rm /swapfile ``` `/etc/fstab` からも該当行を削除すること。 ## swappiness を調整するには? {#swappiness} `swappiness` はカーネルが swap をどの程度積極的に使うかを制御するパラメータ(0〜100)。デフォルト値は 60。 | 値 | 動作 | | ------ | ------------------------------------------------ | | 0 | 枯渇時のみ swap を使用(完全に無効にはならない) | | 10〜30 | デスクトップ環境向け(RAM 優先) | | 60 | デフォルト | | 100 | 積極的に swap を使用 | 現在値の確認。 ```bash cat /proc/sys/vm/swappiness ``` 一時的に変更(再起動で元に戻る)。 ```bash sudo sysctl vm.swappiness=10 ``` 永続化する場合は `/etc/sysctl.d/` 配下に追記。 ```bash echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf sudo sysctl -p /etc/sysctl.d/99-swappiness.conf ``` ::: tip データベースサーバー(MySQL / PostgreSQL など)では swappiness を 1〜10 に下げる設定が一般的。スワッピングによるクエリレイテンシのスパイクを回避できる。 ::: ## OOM killer が発生したら? {#oom-killer} RAM と swap の両方が枯渇すると、カーネルの OOM killer(Out-Of-Memory killer)が動作してプロセスを強制終了する。突然プロセスが消える・システムが不安定になった場合は OOM kill の痕跡を確認する。 ```bash dmesg | grep -i 'oom\|killed process\|out of memory' ``` ```output [123456.789] Out of memory: Killed process 1234 (mysqld) total-vm:2097152kB, anon-rss:1048576kB, file-rss:0kB ``` systemd ジャーナルからも確認できる。 ```bash journalctl -k | grep -i 'oom\|killed' ``` ::: danger OOM kill は swap を増やしても根本解決にはならない。メモリリークや設定ミスが原因であることが多い。再発する場合は `top` / `htop` でプロセスのメモリ使用量を継続監視し、原因プロセスを特定すること。 ::: ### 特定プロセスを OOM kill から保護する(暫定措置) OOM killer のスコアリングを調整し、重要プロセスを保護する。値は -1000(kill されない)〜 1000(最優先で kill)の範囲。 ```bash echo -1000 | sudo tee /proc/$(pgrep mysqld)/oom_score_adj ``` 再起動すると設定は失われる。本番環境での適用はリスクを理解した上で行うこと。 ## 次に読む {#next} - [ディスク管理入門 - fdisk/lsblkでストレージを確認する](/articles/tutorials/disk-management-basics) - [du と df の違い - ディスク容量を正しく測る](/articles/tutorials/du-df-difference) - [top と htop 徹底活用 - ボトルネックを見抜く](/articles/tutorials/top-htop-mastery) # symbolic link(シンボリックリンク)とハードリンク - ファイル参照の仕組み Source: https://penguin-gym-linux.com/articles/tutorials/symbolic-links ## 結論:シンボリックリンクとハードリンクの違い {#summary} **シンボリックリンクはパスへの参照(ショートカット)、ハードリンクは inode への追加参照(同一ファイルの別名)**。迷ったらシンボリックリンクを使う。 ::: tip **使い分けの基準** - 別ファイルシステム間・ディレクトリへの参照 → シンボリックリンク - 同一パーティション内でファイル削除後もデータを保持したいバックアップ用途 → ハードリンク - どちらか迷ったら → シンボリックリンク(`ln -s`) ::: ## ln コマンドの基本構文 {#ln-syntax} `ln` コマンド一本でシンボリックリンク・ハードリンクの両方を作成できる。 ```bash # シンボリックリンク(-s オプションが必須) ln -s [対象ファイル/ディレクトリ] [リンク名] # ハードリンク(-s なし) ln [対象ファイル] [リンク名] ``` ::: warning `-s` を忘れるとハードリンクが作成される。ディレクトリを対象にした場合はエラーになるため気付けるが、通常ファイルを対象にした場合は意図せずハードリンクが作られる。 ::: ## シンボリックリンクとは? {#symlink} シンボリックリンクはファイルシステム上に「パス文字列を記録した特殊ファイル」として存在する。Windows のショートカットに相当するが、透過的に扱われる点が異なる。 ```bash $ ln -s /etc/nginx/nginx.conf ./nginx.conf $ ls -la lrwxrwxrwx 1 user user 22 Jan 1 00:00 nginx.conf -> /etc/nginx/nginx.conf ``` `ls -la` 出力の先頭 `l` はシンボリックリンクを示す。`->` の後ろに記録されたパスがリンク先。 ### シンボリックリンクの特性 | 特性 | 詳細 | | ---------------------- | ------------------------------------ | | ファイルシステム跨ぎ | 可(別マウントポイント間も OK) | | ディレクトリへのリンク | 可 | | リンク先削除後 | リンクは残るが「壊れたリンク」になる | | 参照解決 | アクセスのたびにパスを辿って解決 | ### リンク先の確認 ```bash # リンク先のパスを表示 $ readlink nginx.conf /etc/nginx/nginx.conf # 絶対パスで解決して表示 $ readlink -f nginx.conf /etc/nginx/nginx.conf ``` ## ハードリンクとは? {#hardlink} ハードリンクは同一の inode を指す追加のディレクトリエントリ。「同じファイルが 2 つの名前を持っている」状態であり、実体(inode とデータブロック)は 1 つ。 ```bash $ echo "hello" > original.txt $ ln original.txt hardlink.txt $ ls -lai 1234567 -rw-r--r-- 2 user user 6 Jan 1 00:00 hardlink.txt 1234567 -rw-r--r-- 2 user user 6 Jan 1 00:00 original.txt ``` 先頭の数値(`1234567`)が inode 番号。同じ番号 = 同一の実体。リンクカウント(`-rw-r--r-- 2` の `2`)がどちらのエントリからも同じ inode を参照していることを示す。 ### ハードリンクの特性 | 特性 | 詳細 | | ---------------------- | ------------------------------------------------------- | | ファイルシステム跨ぎ | 不可(同一パーティション内のみ) | | ディレクトリへのリンク | 不可(通常ユーザーは作成できない) | | 元ファイル削除後 | データは存続(参照カウントが 0 になるまで削除されない) | | 参照解決 | inode 直接参照(パス解決なし) | ## シンボリックリンクとハードリンクの違いまとめ {#difference} 両者の本質的な違いは「何を参照するか」にある。 | 項目 | シンボリックリンク | ハードリンク | | ---------------- | --------------------------------- | ------------------ | | 参照先 | ファイルパス(文字列) | inode | | FS 跨ぎ | 可 | 不可 | | ディレクトリ対象 | 可 | 不可 | | 元削除後 | 壊れたリンク(dangling link) | データ存続 | | `ls` の権限表示 | 先頭が `l` | 通常ファイルと同一 | | inode | リンクファイル自身の inode を持つ | 対象と同一 inode | ## 実務での使い分け {#use-cases} ### シンボリックリンクが適しているケース **dotfiles 管理(設定ファイルのリポジトリ管理)** ```bash $ ln -s ~/dotfiles/.bashrc ~/.bashrc $ ln -s ~/dotfiles/.vimrc ~/.vimrc ``` **バージョン管理(current ディレクトリパターン)** ```bash # /opt/app-1.2.0 を current としてリンク $ ln -s /opt/app-1.2.0 /opt/app/current # バージョンアップ時はリンクを張り替えるだけ $ ln -sfn /opt/app-1.3.0 /opt/app/current ``` `-f` で既存リンクを上書き、`-n` でリンク先がディレクトリの場合に内部ではなくリンク自体を置換する。 ### ハードリンクが適しているケース **増分バックアップ(rsync の --link-dest)** ```bash $ rsync -a --link-dest=/backup/prev/ /data/ /backup/today/ ``` 変更のないファイルはハードリンクで参照するため、ディスク消費を最小化しながら「各日付のスナップショット」を保持できる。 ## よくある落とし穴 {#pitfalls} ### 相対パスのシンボリックリンクは場所によって壊れる ```bash # /home/user で作成 $ ln -s ../etc/nginx/nginx.conf nginx.conf # /tmp に移動すると壊れる $ cd /tmp $ cat nginx.conf cat: nginx.conf: No such file or directory ``` リンクに記録されるのは指定したパス文字列そのもの。作成時のカレントディレクトリは関係しない。**移動しても機能するリンクには絶対パスを使う**。 ### リンク先がディレクトリのシンボリックリンクを張り替える ```bash # 誤: -f だけでは current がディレクトリの場合に内部にリンクが作成される $ ln -sf /opt/app-2.0.0 /opt/app/current # 正: -n を加えてリンク自体を置換する $ ln -sfn /opt/app-2.0.0 /opt/app/current ``` ### 壊れたシンボリックリンクの検出 ```bash $ find /path -maxdepth 1 -xtype l ``` `-xtype l` は「リンク先が存在しないシンボリックリンク」を検出するオプション(`-type l` だとリンク自体の型を見るため壊れたリンクも含んでしまう)。 ## 次に読む {#next} - [ファイル操作の基本(cp・mv・rm)](/articles/tutorials/file-operations-basics) - [権限管理の基本(chmod・chown)](/articles/tutorials/permissions-basics) - [find・grep・awk 入門](/articles/tutorials/find-grep-awk-basics) # sysctl 入門 - カーネルパラメータの確認と永続化 Source: https://penguin-gym-linux.com/articles/tutorials/sysctl-kernel-parameters ## この記事で解決できること {#intro} - `sysctl` で **カーネルパラメータを確認・変更** できる - `sysctl -w` の **一時変更** と `/etc/sysctl.d` による **永続化** を使い分けられる - 設定が反映されない原因(**優先順位・読込タイミング**)を切り分けられる ::: tip **結論(実務の型)** - **確認** → `sysctl `(一覧は `sysctl -a`) - **一時変更** → `sudo sysctl -w key=value`(再起動で消える) - **永続化** → `/etc/sysctl.d/99-custom.conf` に書いて `sudo sysctl --system` ::: ::: warning **前提(対象環境)** - Ubuntu 22.04 / 24.04、RHEL 系 9 など systemd 採用ディストリビューション - パッケージ: `procps`(`procps-ng`)に含まれる `sysctl(8)` - パラメータ変更には基本的に root 権限(`sudo`)が必要 ::: ## sysctl とは何か? {#what} > **結論**: sysctl は実行中カーネルの動作パラメータを `/proc/sys/` 経由で確認・変更するツール。キー名はディレクトリ階層に対応する。 `sysctl` はカーネルが公開する調整可能パラメータ(チューナブル)を読み書きするコマンドだ。実体は仮想ファイルシステム `/proc/sys/` 配下のファイルで、`sysctl` はそれを人間が扱いやすい `key = value` 形式で操作する薄いラッパーにあたる。 キー名のドットは、ディレクトリ区切りに 1 対 1 で対応する。 ```bash # この 2 つは同じ対象を指す sysctl net.ipv4.ip_forward cat /proc/sys/net/ipv4/ip_forward ``` ```output net.ipv4.ip_forward = 0 0 ``` 主な名前空間(先頭の階層)は次のとおり。 | プレフィックス | 管轄領域 | 代表的なパラメータ | | -------------- | ---------------- | --------------------------------------------- | | `net.` | ネットワーク | `net.ipv4.ip_forward` / `net.core.somaxconn` | | `vm.` | 仮想メモリ | `vm.swappiness` / `vm.overcommit_memory` | | `fs.` | ファイルシステム | `fs.file-max` / `fs.inotify.max_user_watches` | | `kernel.` | カーネル全般 | `kernel.hostname` / `kernel.pid_max` | ## 現在の値をどう確認するのか? {#read} > **結論**: 単一値は `sysctl `、全体は `sysctl -a`。スクリプトで値だけ欲しいときは `-n` で名前を省く。 ```bash # 単一パラメータ sysctl vm.swappiness ``` ```output vm.swappiness = 60 ``` ```bash # 全パラメータ(数千行。grep と組み合わせる) sysctl -a | grep somaxconn ``` ```output net.core.somaxconn = 4096 ``` 値のみを取り出したいときは `-n`(名前を表示しない)を使う。シェルスクリプトで条件分岐する際に有効だ。 ```bash sysctl -n vm.swappiness ``` ```output 60 ``` ::: tip `sysctl -a` は root でないと一部パラメータが「permission denied」で読めないことがある。完全な一覧が必要なら `sudo sysctl -a` を使う。 ::: ## 一時的に値を変更するには? {#write-runtime} > **結論**: `sudo sysctl -w key=value` で即時反映できるが再起動で失われる。動作確認・検証フェーズ向けの手段。 `-w`(write)で実行中カーネルの値を書き換える。反映は即時だが、**再起動すると初期値に戻る**。 ```bash # IP フォワーディングを一時的に有効化 sudo sysctl -w net.ipv4.ip_forward=1 ``` ```output net.ipv4.ip_forward = 1 ``` `/proc/sys/` へ直接書き込んでも同じ結果になる。 ```bash echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward ``` ::: warning `sysctl -w` はあくまで一時変更。「設定したのに再起動で消えた」は永続化漏れが原因。恒久的に適用するなら次の「永続化」を必ず実施する。 ::: ## 設定を永続化するには? {#persist} > **結論**: `/etc/sysctl.d/` にドロップインファイルを置き `sudo sysctl --system` で反映する。`/etc/sysctl.conf` 直編集より drop-in が推奨。 永続化は設定ファイルに `key = value` 形式で記述する。現代の systemd 系ディストリビューションでは、`/etc/sysctl.conf` を直接編集するより **`/etc/sysctl.d/` 配下にドロップインファイルを作る**のが推奨される(パッケージ管理・差分管理がしやすい)。 ファイル名は慣習的に `NN-name.conf`(`NN` は 2 桁の数字)の形式にする。数字が優先順位を決める(後述)。 ```bash # 例: スワップを抑え、IP フォワーディングを有効化 sudo tee /etc/sysctl.d/99-custom.conf <<'EOF' # カスタムカーネルパラメータ vm.swappiness = 10 net.ipv4.ip_forward = 1 EOF ``` 書いただけでは反映されない。明示的に読み込ませる。 ```bash # このファイルだけ読み込む sudo sysctl -p /etc/sysctl.d/99-custom.conf # あるいは全設定ファイルを標準ディレクトリから再読込 sudo sysctl --system ``` ```output * Applying /etc/sysctl.d/99-custom.conf ... vm.swappiness = 10 net.ipv4.ip_forward = 1 ``` ::: tip **反映の確認**を忘れない。書き込み後は必ず `sysctl ` で実値を読み戻し、期待値と一致するか確認する。 ```bash sysctl vm.swappiness net.ipv4.ip_forward ``` ::: 起動時は `systemd-sysctl.service` がこれらのファイルを自動的に適用するため、永続化さえしておけば再起動後も値は維持される。 ## 設定ファイルの優先順位は? {#precedence} > **結論**: 複数ディレクトリのファイルをファイル名の辞書順でマージし、同名キーは辞書順で後のファイルが勝つ。`/etc/` が同名ファイルでは最優先。 `sysctl --system` は次のディレクトリから `*.conf` を読み込む。 | 優先度 | ディレクトリ | 用途 | | ------ | -------------------- | -------------------------- | | 高 | `/etc/sysctl.d/` | 管理者のカスタム設定 | | 中 | `/run/sysctl.d/` | 実行時に生成される一時設定 | | 低 | `/usr/lib/sysctl.d/` | パッケージ同梱のデフォルト | ルールは 2 段階だ。 1. **ファイル名の辞書順**でマージされる(ディレクトリをまたいで比較)。同じキーが複数ファイルにあれば、**ファイル名が辞書順で後(大きい)の方が勝つ**。だから `99-custom.conf` のように大きい番号を付けると上書きしやすい。 2. **同名ファイル**が複数ディレクトリに存在する場合、`/etc/` のものが `/run/` や `/usr/lib/` のものを上書きする(無効化したいときは `/etc/sysctl.d/` に同名の空ファイルを置く)。 ```bash # どのファイルがどのキーを設定しているか追う grep -Rn swappiness /etc/sysctl.conf /etc/sysctl.d/ /usr/lib/sysctl.d/ /run/sysctl.d/ 2>/dev/null ``` ::: warning `/etc/sysctl.conf` も読み込み対象だが、これは歴史的な単一ファイル。新規設定は drop-in(`/etc/sysctl.d/*.conf`)に分けた方が、どの設定がどこ由来か追いやすい。 ::: ## よく使うパラメータの実例 {#examples} > **結論**: メモリ・ネットワーク・ファイルディスクリプタ系が実務頻出。意味を理解せず値だけコピペしない。 | パラメータ | 役割 | よく使う値の例 | | ----------------------------- | ------------------------------------------- | ------------------------ | | `vm.swappiness` | スワップの積極度(0〜100、低いほどRAM優先) | DB サーバで `10` | | `vm.overcommit_memory` | メモリオーバーコミット方針 | Redis 等で `1` | | `net.ipv4.ip_forward` | パケット転送(ルータ/コンテナで必須) | `1` | | `net.core.somaxconn` | accept キューの最大長 | 高負荷 Web で `4096` | | `fs.file-max` | システム全体のファイルハンドル上限 | 大量接続サーバで増やす | | `fs.inotify.max_user_watches` | inotify 監視数上限 | IDE/ビルドツールで増やす | ::: danger パラメータの意味を理解せずに「チューニング設定例」を丸ごとコピペするのは危険。本番に入れる前にステージングで `sysctl -w` の一時変更で挙動を確認し、問題なければ永続化する、という順序を守る。 ::: ## 反映されないときの切り分け {#troubleshooting} > **結論**: 「権限」「読込忘れ」「上書き」「キー不在」の 4 点を順に確認する。 - **権限不足**: `sysctl -w` には `sudo` が必要。`Operation not permitted` は権限かコンテナ制約(特権なしコンテナでは多くの `sysctl` が書込不可)。 - **読込忘れ**: ファイルを書いただけでは未反映。`sudo sysctl --system`(または `-p`)を実行したか確認する。 - **他ファイルの上書き**: 値が戻る場合、辞書順で後のファイルが上書きしている。`grep -R /etc/sysctl.d /usr/lib/sysctl.d` で犯人を特定する。 - **キー名の誤り / 不在**: `sysctl: cannot stat ...` はキー名のタイプミスか、対象モジュール未ロード。`sysctl -e` で未知キーのエラーを無視しつつ、正しい名前を `sysctl -a | grep` で探す。 ```bash # 起動時適用ログを確認(systemd 系) journalctl -u systemd-sysctl ``` ## まとめと次に読む {#next} `sysctl` は「確認は `sysctl `、一時変更は `-w`、永続化は `/etc/sysctl.d/` + `--system`」という 3 つの型を押さえれば実務は回る。一時変更で検証してから永続化する順序と、変更後の値の読み戻し確認を習慣にすれば事故は防げる。 - [dmesg 入門 - カーネルログを読む](/articles/tutorials/dmesg-kernel-messages) - [swap 管理の基本](/articles/tutorials/swap-management) - [メモリ不足のトラブルシュート](/articles/troubleshooting/memory-troubleshooting) - [systemctl の基本](/articles/tutorials/systemctl-basics) # systemctl の使い方 - status・start・restart・enable でサービス管理 Source: https://penguin-gym-linux.com/articles/tutorials/systemctl-basics ## この記事で解決できること {#intro} - `systemctl status` の出力から、サービスが動いているかどうかを読み取れます。 - `start` / `stop` / `restart` / `reload` を、影響を理解したうえで使い分けられます。 - `enable` と `start` の違いを理解し、自動起動を意図どおりに設定できます。 ::: tip **結論(最短)** まずはこれだけ覚えれば現場で困りません。 - **状態確認:** `systemctl status ` - **起動:** `systemctl start ` - **停止:** `systemctl stop ` - **再起動:** `systemctl restart ` - **設定を読み直して再起動:** `systemctl reload `(対応していれば) - **自動起動ON:** `systemctl enable ` - **自動起動OFF:** `systemctl disable ` Ubuntuでは多くの場合、管理者権限が必要なので `sudo` を付けます。`sudo` は「このコマンドだけ管理者として実行する」という仕組みです。 ::: ::: warning **前提(対象環境)** - OS:Ubuntu - systemd を使用している環境 - 権限:`sudo` が使える想定 ::: ## 1. systemctl とは? {#what} > **結論**: `systemctl` は systemd のサービス管理コマンドで、起動・停止・再起動・状態確認を一手に担う。 `systemctl` は systemd のサービス管理コマンドです。 サーバ上で動いているサービス(例:nginx, apache2, ssh, docker など)を、起動/停止/再起動/状態確認できます。 **先に用語を 4 つだけ整理します。** | 用語 | 一行の意味 | 補足 | | ------------------ | ---------------------------------------------------------------- | ---------------------------------------------------------- | | systemd | Linux の起動処理とサービスを管理する仕組み | 「init システム」と呼ばれることもあります | | デーモン(daemon) | 画面を持たず、裏で動き続けるプログラム | 「常駐プロセス」とも呼ばれます | | サービス(service)| systemd が管理する単位から見たデーモンの呼び名 | この記事では「サービス」で統一します | | ユニット(unit) | systemd が管理する対象の総称 | サービスはユニットの一種で、名前は `nginx.service` の形です | ::: tip 「デーモン」「サービス」「ユニット」は現場で混ざって使われます。 `systemctl` の操作対象はすべてユニットです。サービスはユニットの一種なので、`nginx` と `nginx.service` はどちらを書いても同じ意味になります。 ::: ## 2. まずは「状態確認」から(status) {#status} > **結論**: 障害対応は `systemctl status` から始め、active / inactive / failed のどれかを最初に見極める。 障害対応はまずここから始めます。 `status` は状態を読むだけのコマンドです。サービスを止めたり動かしたりはしないので、安全に何度でも実行できます。 ```bash $ sudo systemctl status nginx ``` ```output ● nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) Active: active (running) since Mon 2025-12-15 10:12:03 UTC; 2h 5min ago Main PID: 1234 (nginx) Tasks: 3 (limit: 4610) Memory: 5.6M CGroup: /system.slice/nginx.service ├─1234 nginx: master process /usr/sbin/nginx -g daemon on; └─1235 nginx: worker process ``` **よく見るポイント:** - `Active: active (running)` → 動いている - `Active: inactive (dead)` → 止まっている - `Active: failed` → 失敗している(原因調査が必要) - `Loaded: loaded (...; enabled; ...)` の `enabled` / `disabled` → 自動起動の設定(後述の §5) `Active:` は「今」の状態、`Loaded:` 行の `enabled` は「次回サーバを起動したとき」の設定です。 この 2 つは独立しているので、`status` 一発で両方を読み取ってください。 ::: tip `failed` の場合は、後述の `journalctl -u` でログを見るのが最短です。 ::: ## 3. 起動・停止・再起動(start/stop/restart) {#start-stop} > **結論**: start / stop / restart でサービスを操作し、設定変更後は基本 restart で反映する。 ここからはサーバの状態を実際に変えるコマンドです。 **何が変わるか**と**戻し方**を先に確認してください。 | コマンド | 実行すると何が変わるか | 戻し方 | 安全に試す方法 | | --------- | -------------------------------------------------- | ---------------------------- | ------------------------------------- | | `start` | 止まっていたサービスが動き出す | `stop` で止める | 先に `status` で現状を控える | | `stop` | そのサービスの機能が即座に止まる(Web なら閲覧不可)| `start` で戻す | 利用者がいない時間帯に実施する | | `restart` | 一瞬止まってから起動し直す(短時間の停止が発生) | 設定を戻して再度 `restart` | 先に構文チェック(後述)を通しておく | ### 3-1. 起動 ```bash $ sudo systemctl start nginx ``` ### 3-2. 停止 ```bash $ sudo systemctl stop nginx ``` ::: warning `stop` は実行した瞬間にサービスが止まります。 本番サーバでは「今このサービスを止めて誰が困るか」を確認してから実行してください。 止めてしまった場合は `sudo systemctl start ` で元に戻せます。 ::: ### 3-3. 再起動 ```bash $ sudo systemctl restart nginx ``` ::: tip 設定を直した後は基本 `restart`。 reload対応なら `reload` で"落とさずに"反映できる場合もあります。 ::: ## 4. reload / reload-or-restart(設定反映の扱い) {#reload} > **結論**: reload は無停止反映だが非対応のサービスもあり、迷うなら reload-or-restart が安全。 `reload` は「サービスを止めずに設定ファイルだけ読み直す」操作です。 すべてのサービスが対応しているわけではありません。 ### 4-1. reload(対応しているサービスのみ) ```bash $ sudo systemctl reload nginx ``` ### 4-2. reload-or-restart(迷うならこれが安全) ```bash $ sudo systemctl reload-or-restart nginx ``` - reload対応なら reload - 対応していなければ restart ### 4-3. reload と daemon-reload は別物 名前が近く、最も混同される組み合わせです。読み直す対象がまったく違います。 | コマンド | 読み直す対象 | 使う場面 | | ---------------------------- | --------------------------------------- | ------------------------------ | | `systemctl reload ` | サービス自身の設定(`nginx.conf` 等) | 設定ファイルを直した後 | | `systemctl daemon-reload` | systemd の unit ファイル(`*.service`) | unit ファイルを直した後 | unit ファイル(`/etc/systemd/system/*.service` 等)を編集したのに `restart` だけで反映されない場合は、これが原因です。 ```bash $ sudo systemctl daemon-reload $ sudo systemctl restart nginx ``` `daemon-reload` は systemd に定義を読み直させるだけで、サービスの再起動は行いません。反映には `restart` が別途必要です。 ## 5. 自動起動(enable/disable)※新人が詰まりやすいポイント {#enable} > **結論**: enable は次回起動時の自動起動設定であり、今すぐ動かす start とは別物なので両方必要。 「今は動いてるけど、再起動したら落ちる」問題はここです。 `enable` と `disable` が変えるのは「次回サーバを起動したときに自動で立ち上がるか」だけです。 今動いているサービスには影響しません。 ### 5-1. 自動起動をON ```bash $ sudo systemctl enable nginx ``` ### 5-2. 自動起動をOFF ```bash $ sudo systemctl disable nginx ``` ::: warning `disable` したことを忘れると、次回のサーバ再起動でサービスが上がらず障害になります。 戻すときは `sudo systemctl enable ` を実行してください。 現在の設定は `systemctl is-enabled ` で読み取り専用に確認できます。 ::: ### 5-3. 自動起動状態を確認 ```bash $ systemctl is-enabled nginx ``` ```output enabled ``` 出力例の意味: - `enabled`:自動起動ON - `disabled`:自動起動OFF ## 6. "動かない"ときの定番手順(障害対応の型) {#troubleshoot} > **結論**: status で確認し、failed ならログを読み、設定起因なら構文チェック後に restart する。 ### 6-1. statusで状態確認 ```bash $ sudo systemctl status nginx ``` ### 6-2. failedならログを見る(これが最短) ```bash $ sudo journalctl -u nginx -n 200 ``` `journalctl` は systemd が集めたログを読むコマンドです。 `-u` は「このユニットのログだけ」、`-n 200` は「直近 200 行だけ」という指定です。 リアルタイム追跡: ```bash $ sudo journalctl -u nginx -f ``` ### 6-3. 設定ミスの可能性が高いなら(例:nginx) サービス固有の構文チェックがある場合は先に実行: ```bash $ sudo nginx -t ``` `nginx -t` は設定ファイルを読むだけで、サービスの動作は変えません。 ::: warning 設定が壊れている状態で `restart` すると、止まったままになることがあります。 まずは構文チェック → OKなら restart が安全です。 ::: ## 7. サービス名(unit名)が分からない時 {#find-service} > **結論**: サービス名は環境差があるため `systemctl list-units --type=service` を grep で絞って特定する。 サービス名は環境で微妙に違います(例:Ubuntuだと Apache は `apache2` が多い)。 一覧から探す: ```bash $ systemctl list-units --type=service | grep -i apache ``` 全部表示(多いので注意): ```bash $ systemctl list-units --type=service ``` `list-units` は一覧を表示するだけのコマンドです。サービスの状態は変わりません。 ## 8. よくあるつまずき {#common-mistakes} > **結論**: 起動中でも接続不可ならポートや FW を疑い、enable しただけでは今すぐ起動しない点に注意する。 ### 8-1. statusは「起動してるのに」アクセスできない サービスが起動していても、ポート待受やFW、アプリ側エラーで繋がらないことがあります。 FW(ファイアウォール)は通信を許可・遮断する仕組みで、Ubuntu では `ufw` が使われることが多いです。 「ポート疎通(ss/lsof/nc/curl)」とセットで切り分けると早いです。 ### 8-2. enableしたのに起動してない `enable` は「次回起動時に自動起動する設定」です。 今すぐ起動したいなら `start` も必要です。 ```bash $ sudo systemctl enable nginx $ sudo systemctl start nginx ``` ## 9. 作業完了チェックリスト {#checklist} > **結論**: 操作後は状態・自動起動・ログの 3 点を読み取り専用コマンドで確認し、想定どおりかを突き合わせる。 - [ ] `systemctl status ` が `active (running)` になっている - [ ] `systemctl is-enabled ` が意図した値(`enabled` / `disabled`)になっている - [ ] `journalctl -u -n 50` に新しいエラーが出ていない - [ ] 停止・再起動した場合、利用者から見て機能が戻っている ::: tip **まとめ(コピペ用)** ```bash # 状態確認 sudo systemctl status # 起動/停止/再起動 sudo systemctl start sudo systemctl stop sudo systemctl restart # 設定反映(対応していれば) sudo systemctl reload sudo systemctl reload-or-restart # 自動起動 sudo systemctl enable sudo systemctl disable systemctl is-enabled # ログ sudo journalctl -u -n 200 sudo journalctl -u -f ``` ::: ## 次に読む {#next} - [サービスのログを詳しく調べる](/articles/tutorials/journalctl-basics) - [Webサーバのログの見方](/articles/troubleshooting/nginx-apache-log) - [定期タスクが動かないときの確認](/articles/tutorials/cron-basics) # systemd timer と cron の使い分け Source: https://penguin-gym-linux.com/articles/tutorials/systemd-timer-vs-cron ## どちらを使うべきか? {#conclusion} **迷ったら systemd timer を選ぶ**のが現代 Linux の基本方針。ジョブの実行ログが journalctl に統合され、依存関係制御・失敗時の再試行・実行漏れ補完も設定できる。ただし、既存の cron ジョブを維持する場合や、1 行で書ける単純なジョブは cron のままでも問題ない。 ::: tip **選択の目安** | 用途 | 推奨 | | --------------------------------- | ------------- | | 新規で定期実行を実装 | systemd timer | | journalctl でログを一元管理したい | systemd timer | | 実行条件・依存関係を設定したい | systemd timer | | 起動後の遅延実行が必要 | systemd timer | | 既存 cron ジョブの維持 | cron のまま | | シンプルな 1 行スクリプト | cron | ::: ## cron とは何か? {#cron} cron は UNIX 伝統のジョブスケジューラで、テキスト 1 行で定期実行を設定できる。設定は `crontab` コマンドで管理し、分・時・日・月・曜日の 5 フィールドで実行タイミングを指定する。 ### crontab の基本書式 ``` 分 時 日 月 曜日 コマンド ``` ```bash # 毎日 3:00 にバックアップスクリプトを実行 0 3 * * * /home/user/scripts/backup.sh # 5 分ごとにログを収集 */5 * * * * /usr/local/bin/collect-logs.sh # 毎週月曜 9:00 にレポート送信 0 9 * * 1 /usr/local/bin/send-report.sh ``` ### crontab の操作コマンド ```bash # 現在のユーザーの crontab を編集 crontab -e # crontab の内容を表示 crontab -l # crontab を削除(全ジョブが消えるので注意) crontab -r ``` ::: warning cron はデフォルトでジョブの成否をメールで通知する。メール設定がない環境では `/var/spool/mail` に出力が蓄積されるため、不要なら crontab 先頭に `MAILTO=""` を追加する。 ::: ## systemd timer とは何か? {#systemd-timer} systemd timer は、`.timer` ファイル(スケジュール定義)と `.service` ファイル(実行内容)の 2 ファイルで構成する定期実行の仕組み。両方を systemd が管理するため、ログ・依存関係・状態確認が統一インターフェースで行える。 ### 最小構成の例(5 分ごとに実行) **`/etc/systemd/system/collect-logs.service`** ```ini [Unit] Description=Collect system logs [Service] Type=oneshot ExecStart=/usr/local/bin/collect-logs.sh ``` **`/etc/systemd/system/collect-logs.timer`** ```ini [Unit] Description=Run collect-logs every 5 minutes [Timer] OnCalendar=*:0/5 Persistent=true [Install] WantedBy=timers.target ``` ### timer の有効化と状態確認 ```bash # ユニットファイルの変更を systemd に反映 sudo systemctl daemon-reload # timer を有効化して即座に起動 sudo systemctl enable --now collect-logs.timer # 動作中の timer 一覧と次回実行時刻を確認 systemctl list-timers # サービスの実行ログを確認 journalctl -u collect-logs.service ``` ## systemd timer が優れる点は何か? {#advantages} cron と比較したときの主な利点は **ログの一元管理** と **細かい実行制御** の 2 点に集約できる。 ### ログが journalctl に統合される ```bash # 直近 50 件の実行ログを確認 journalctl -u collect-logs.service -n 50 # 今日の実行ログだけ表示 journalctl -u collect-logs.service --since today # エラーのみ表示 journalctl -u collect-logs.service -p err ``` cron のジョブ出力はメールやファイルに分散するが、systemd timer は journalctl で全サービスのログと一元的に調査できる。障害時に時系列を追う際の手間が大幅に減る。 ### サービス依存関係を設定できる ```ini [Unit] Description=Database backup Requires=postgresql.service After=postgresql.service ``` PostgreSQL などの特定サービスが起動している場合のみ実行する、という条件を設定できる。cron にはこの仕組みがなく、スクリプト内で手動チェックが必要になる。 ### 起動後の遅延実行が可能 ```ini [Timer] # 起動後 10 分経過してから最初に実行 OnBootSec=10min # その後 1 時間ごとに繰り返す OnUnitActiveSec=1h ``` `OnCalendar` による絶対スケジュールと、`OnBootSec`/`OnUnitActiveSec` による相対スケジュールの 2 種類を使い分けられる。 ### Persistent=true で実行漏れを補完する ```ini [Timer] OnCalendar=daily Persistent=true ``` `Persistent=true` を設定すると、サーバがダウンしていた期間にスキップされたジョブを次回起動時に補完実行する。cron ではこの動作は標準では得られない(別途 anacron が必要)。 ## OnCalendar の書式はどう書くのか? {#oncalendar} `OnCalendar` は systemd 独自の日時書式を使用する。有効化前に `systemd-analyze calendar` で構文を検証するのが基本手順。 ```bash # 書式を検証し、次の実行時刻を表示 systemd-analyze calendar "Mon *-*-* 03:00:00" # よく使うパターン OnCalendar=daily # 毎日 00:00 OnCalendar=hourly # 毎時 00:00 OnCalendar=weekly # 毎週月曜 00:00 OnCalendar=*:0/5 # 5 分ごと OnCalendar=Mon 03:00 # 毎週月曜 3:00 OnCalendar=*-*-* 03:00:00 # 毎日 3:00(上と同じ) ``` ::: tip `systemd-analyze calendar` は timer を有効化する前の必須確認コマンド。次回と次々回の実行予定時刻も表示されるため、タイポや意図しないスケジュールをすぐ発見できる。 ::: ## cron を systemd timer に移行するには? {#migration} 既存の cron ジョブを systemd timer に移行する手順をステップごとに示す。 ### ステップ 1:既存 cron ジョブを確認 ```bash crontab -l # 0 3 * * * /home/user/scripts/backup.sh ``` ### ステップ 2:.service ファイルを作成 ```bash sudo nano /etc/systemd/system/backup.service ``` ```ini [Unit] Description=Daily backup [Service] Type=oneshot User=user ExecStart=/home/user/scripts/backup.sh ``` ### ステップ 3:.timer ファイルを作成 ```bash sudo nano /etc/systemd/system/backup.timer ``` ```ini [Unit] Description=Run backup daily at 3:00 [Timer] OnCalendar=*-*-* 03:00:00 Persistent=true [Install] WantedBy=timers.target ``` ### ステップ 4:有効化して動作確認 ```bash sudo systemctl daemon-reload sudo systemctl enable --now backup.timer # timer が登録されたことを確認 systemctl list-timers --all | grep backup # 実行後にログを確認 journalctl -u backup.service --since today ``` ### ステップ 5:cron ジョブを無効化 ```bash crontab -e # 移行済みの行を削除またはコメントアウト ``` ::: warning 移行後は `systemctl list-timers` と `journalctl -u backup.service` で実際に動作することを確認してから cron ジョブを削除する。両方を残しておくと二重実行になる。 ::: ## 比較まとめ {#comparison} | 項目 | cron | systemd timer | | ------------ | -------------------- | ------------------------------- | | 設定の手軽さ | 1 行で書ける | .timer + .service の 2 ファイル | | ログ確認 | メール or ファイル | journalctl | | 依存関係設定 | 不可 | 可能 | | 起動遅延実行 | 不可 | 可能(OnBootSec) | | 実行漏れ補完 | 不可(anacron は別) | Persistent=true | | ユーザー権限 | 各ユーザーの crontab | ユーザーサービスも対応 | | 確認コマンド | `crontab -l` | `systemctl list-timers` | ## 次に読む {#next} - [journalctl の使い方 - Linux ログ調査の基本と障害対応](/articles/tutorials/journalctl-basics) - [シェルスクリプトの書き方入門 - Bash 変数・条件分岐・ループ](/articles/tutorials/shell-scripting-basics) - [cron の基本](/articles/tutorials/cron-basics) # systemd ユニットファイルを書く - 自作サービスの登録 Source: https://penguin-gym-linux.com/articles/tutorials/systemd-unit-creation ## systemd ユニットファイルとは? {#what-is-unit-file} systemd ユニットファイルは、サービス・マウントポイント・タイマーなどをシステムに登録するための設定ファイルだ。`.service` 拡張子のファイルを `/etc/systemd/system/` に置くことで、自作スクリプトやアプリをデーモンとして管理できる。 ::: tip **結論** - ユニットファイルの置き場所: `/etc/systemd/system/myapp.service` - 3 セクション構成: `[Unit]` / `[Service]` / `[Install]` - 作成・変更後は `systemctl daemon-reload` が必須 ::: ## ユニットファイルの基本構造 {#structure} ユニットファイルは 3 つのセクションで構成される。 ```ini [Unit] Description=My Custom Service After=network.target [Service] ExecStart=/usr/local/bin/myapp Restart=on-failure User=myuser [Install] WantedBy=multi-user.target ``` | セクション | 役割 | | ----------- | -------------------------------------- | | `[Unit]` | サービスの説明・起動順序・依存関係 | | `[Service]` | 起動コマンド・再起動設定・実行ユーザー | | `[Install]` | `systemctl enable` の対象ターゲット | ## 1. ミニマル構成で自作サービスを登録する {#minimal} 最短手順でシェルスクリプトをサービス化する。 ### 1-1. スクリプトを準備する ```bash sudo tee /usr/local/bin/myapp.sh > /dev/null << 'EOF' #!/bin/bash while true; do echo "$(date) running" >> /var/log/myapp.log sleep 10 done EOF sudo chmod +x /usr/local/bin/myapp.sh ``` ### 1-2. ユニットファイルを作成する ```bash sudo tee /etc/systemd/system/myapp.service > /dev/null << 'EOF' [Unit] Description=My Application After=network.target [Service] ExecStart=/usr/local/bin/myapp.sh Restart=on-failure [Install] WantedBy=multi-user.target EOF ``` ### 1-3. デーモンをリロードして起動する ```bash sudo systemctl daemon-reload sudo systemctl enable --now myapp sudo systemctl status myapp ``` ```output ● myapp.service - My Application Loaded: loaded (/etc/systemd/system/myapp.service; enabled) Active: active (running) since Sun 2026-05-31 12:00:00 JST; 3s ago Main PID: 12345 (myapp.sh) ``` ::: warning `daemon-reload` を忘れると「Unit not found」や古い定義のままで起動してしまう。ユニットファイルを変更するたびに実行すること。 ::: ## 2. [Unit] セクション — 依存関係を正しく書く {#unit-section} `[Unit]` はサービスのメタ情報と起動順序を制御する。 | ディレクティブ | 意味 | | ---------------------- | ------------------------------------------------------ | | `Description=` | サービスの説明(`systemctl status` に表示) | | `After=` | 指定ユニットの**起動後**に自サービスを起動(順序のみ) | | `Requires=` | 指定ユニットが失敗すると自サービスも停止 | | `Wants=` | 指定ユニットを起動しようとするが、失敗しても続行 | | `ConditionPathExists=` | ファイル/ディレクトリが存在する場合のみ起動 | ```ini [Unit] Description=Web Application Backend After=network.target postgresql.service Wants=postgresql.service ``` ::: tip `After=network.target` はネットワーク利用可能後に起動する定番パターン。DB 依存なら `After=postgresql.service` を追加する。`After=` は起動**順序**の制御で、依存の強制ではないことに注意。 ::: ## 3. [Service] セクション — 起動コマンドと挙動を設定する {#service-section} 最も設定項目が多いセクション。主要ディレクティブを押さえる。 ### Type= の選択 `Type=` はプロセスの起動方式を決定する。 | Type | 用途 | | ---------------------- | ------------------------------------------------------- | | `simple`(デフォルト) | フォアグラウンドで常駐するプロセス | | `forking` | 自分でデーモン化(フォーク)するプロセス | | `oneshot` | 一度実行して終了するスクリプト | | `notify` | systemd に `READY=1` を送って起動完了を通知するプロセス | ほとんどのケースで `simple` または `oneshot` を選ぶ。 ### 主要ディレクティブ ```ini [Service] Type=simple ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml ExecStop=/bin/kill -TERM $MAINPID ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure RestartSec=5 User=www-data Group=www-data WorkingDirectory=/var/lib/myapp EnvironmentFile=/etc/myapp/env ``` | ディレクティブ | 意味 | | ------------------- | ------------------------------------------------ | | `ExecStart=` | 起動コマンド(フルパス必須) | | `ExecStop=` | 停止コマンド(省略時は SIGTERM) | | `ExecReload=` | リロードコマンド | | `Restart=` | 再起動ポリシー(`no` / `on-failure` / `always`) | | `RestartSec=` | 再起動待機秒数 | | `User=` / `Group=` | 実行ユーザー / グループ | | `WorkingDirectory=` | 作業ディレクトリ | | `EnvironmentFile=` | 環境変数ファイルのパス | ::: warning `ExecStart=` にはバイナリまたはシェルの**絶対パス**を指定する。`/usr/bin/bash /path/to/script.sh` のように書く。`./script.sh` など相対パスは動作しない。 ::: ### 環境変数を渡す ```ini [Service] Environment="NODE_ENV=production" Environment="PORT=3000" EnvironmentFile=/etc/myapp/env ``` `/etc/myapp/env` の記述例: ```ini DB_HOST=localhost DB_PORT=5432 SECRET_KEY=changeme ``` ::: danger `EnvironmentFile=` にパスワードを書く場合は、ファイル権限を `600` に設定してサービス実行ユーザーのみ読める状態にすること。 ```bash sudo chmod 600 /etc/myapp/env sudo chown root:root /etc/myapp/env ``` ::: ## 4. [Install] セクション — enable の挙動を決める {#install-section} `systemctl enable` で自動起動を設定する際に参照されるセクション。 ```ini [Install] WantedBy=multi-user.target ``` `WantedBy=multi-user.target` が定番の設定。`systemctl enable myapp` を実行すると `/etc/systemd/system/multi-user.target.wants/myapp.service` にシンボリックリンクが作成され、システム起動時に自動実行される。 | 値 | 用途 | | ----------------------- | ---------------------------------------------- | | `multi-user.target` | 通常のマルチユーザーモード(サーバー向け標準) | | `graphical.target` | グラフィカル環境が必要なサービス | | `network-online.target` | ネットワーク完全起動後に自動起動 | ## 5. サービスの操作コマンド {#commands} ユニットファイル作成後の操作まとめ。 ```bash # ユニット再読み込み(変更のたびに必要) sudo systemctl daemon-reload # 有効化(自動起動登録)+ 即時起動 sudo systemctl enable --now myapp # 状態確認 sudo systemctl status myapp # 再起動 sudo systemctl restart myapp # リロード(設定再読み込み、ExecReload が必要) sudo systemctl reload myapp # 停止 sudo systemctl stop myapp # 無効化(自動起動解除) sudo systemctl disable myapp # ログを確認(直近 50 行) journalctl -u myapp -n 50 --no-pager ``` ## 6. よくある失敗パターンと対処法 {#troubleshooting} ### ExecStart のパスが通っていない ```output myapp.service: control process exited with error code ExecStart=/usr/local/bin/myapp (code=exited, status=203/EXEC) ``` 対処: フルパスを確認する。 ```bash which myapp ls -la /usr/local/bin/myapp ``` ### daemon-reload を忘れた ユニットファイルを変更しても反映されない、または「Unit not found」が出る場合。 ```bash sudo systemctl daemon-reload sudo systemctl restart myapp ``` ### パーミッションエラー ```bash journalctl -u myapp -n 20 # myapp.sh: Permission denied ``` 対処: 実行権限を確認する。 ```bash ls -la /usr/local/bin/myapp.sh # 実行権限がない場合 sudo chmod +x /usr/local/bin/myapp.sh ``` `User=` 設定がある場合は、指定ユーザーがファイルを読める権限を持っているかも確認する。 ### EnvironmentFile が見つからない `EnvironmentFile=` に指定したファイルが存在しない場合、サービスが起動しない。ファイルが存在しなくても起動させたい場合は `-` プレフィックスを使う。 ```ini EnvironmentFile=-/etc/myapp/env ``` ### 手順の全体像(チートシート) ```bash # 1. スクリプト作成 sudo vim /usr/local/bin/myapp.sh sudo chmod +x /usr/local/bin/myapp.sh # 2. ユニットファイル作成 sudo vim /etc/systemd/system/myapp.service # 3. 登録・起動 sudo systemctl daemon-reload sudo systemctl enable --now myapp # 4. 確認 sudo systemctl status myapp journalctl -u myapp -f ``` ## 次に読む {#next} - [journalctl の使い方 - サービスのログ調査](/articles/tutorials/journalctl-basics) - [プロセス管理入門 - ps/top/kill でデーモンを監視する](/articles/tutorials/process-management-basics) - [シェルスクリプト入門 - サービス化する前のスクリプト作成](/articles/tutorials/shell-scripting-basics) # tac / rev 入門 - 行や文字を逆順にする Source: https://penguin-gym-linux.com/articles/tutorials/tac-rev-text ## この記事でわかること {#intro} - `tac` で **行の並び順** を逆さま(最後の行が先頭)にできる - `rev` で **各行の文字** を逆さま(abc → cba)にできる - 「`tac` と `rev` のどっちを使う?」の **使い分け** がはっきり分かる - ログを **新しい順** で読む、2 つを **組み合わせる** など実用テクが身につきます ::: tip **言葉の整理(先に取りちがえを防ぎます)** - **行**:ファイルの中の 1 行のことです。改行から改行までのひとかたまりを指します。 - **行の並び順**:どの行が何番目に来るか、という順番です。`tac` が変えるのはここだけです。 - **行の中の文字**:1 行を組み立てている文字そのものです。`rev` が変えるのはここだけです。 - **標準入力**:パイプ(`|`)で前のコマンドから流れてくるデータのことです。ファイルの代わりに使えます。 「行を逆にする」と「文字を逆にする」は別の作業です。この 2 つを分けて考えると、以降の話が一気にわかりやすくなります。 ::: ::: tip **結論(先に覚える違い)** - **行の順番** をひっくり返したい → `tac`("cat" の逆綴り) - **1 行の中の文字** をひっくり返したい → `rev`(reverse の略) - 名前も対象も「逆」。混同したら **tac=行 / rev=文字** と唱える ::: ::: warning **前提(対象環境)** - OS:Ubuntu / 一般的な Linux ディストリ - `tac` は GNU coreutils 同梱、`rev` は util-linux 同梱。どちらも標準でインストール済み ::: ## 1. tac とは? 何ができるのか {#tac} > **結論**: `tac` はファイルの行を最後から先頭へ、行単位で逆順に出力するコマンド。名前は `cat` を逆さに綴ったもの。 ::: dialogue @lina: 先輩、`cat` でファイルを表示すると上から順に出ますよね。**下から** 表示したいときはどうするのですか。 @linny: そんなときは `tac` だよ。`cat` を逆からつづった名前なんだ。 @linny: やることも名前のとおりで、行を逆順に表示する。 @lina: 名前まで逆さまなんですね。 @linny: そう。まずはふつうの `cat` を見てみよう。 ::: ```bash $ cat fruits.txt ``` ```output apple banana cherry ``` ```bash $ tac fruits.txt ``` ```output cherry banana apple ``` ::: dialogue @lina: 行がまるごと下からひっくり返りました。文字はそのままなんですね。 @linny: そこが大事なポイント。`tac` が動かすのは **行の並び順** だけなんだ。 @linny: `apple` という単語の中身にはさわらない。だから文字はそのまま残るよ。 ::: ::: tip 区切りは行(改行)が基本です。`-s` を使えば区切り文字を変えられます。 ただし区切り文字の付き方が直感とちがうため、初心者のうちは無理に使わなくて構いません。くわしくは `man tac` を読んでください。 ::: ## 2. なぜ「行の逆順」が役立つのか {#tac-use} > **結論**: ログは古い行が上・新しい行が下に追記されるため、`tac` で逆順にすると最新のできごとから読める。 ::: dialogue @lina: 行を逆にすると、何の役に立つのですか。 @linny: いちばんの出番は **ログ** だね。ログファイルは、新しい記録が下にどんどん足されていく。 @linny: つまり最新のできごとは、いつも一番下にあるんだ。 @lina: 最新を見たいのに、毎回ファイルの末尾までスクロールするのは大変です。 @linny: そこで `tac` を使う。逆順にすれば **最新の行が先頭** に来るよ。 @linny: `head` と組み合わせれば、直近 5 行を新しい順で読むこともできる。 ::: ```bash $ tac access.log | head -n 5 ``` ```output 192.168.0.9 - GET /login 200 192.168.0.4 - GET /api 500 192.168.0.4 - GET /api 500 192.168.0.7 - GET / 200 192.168.0.2 - GET /about 200 ``` ::: tip `tail` は末尾の行を **元の順番のまま** 表示します。「末尾を新しい順に並べ直したい」ときは `tac` を使います。目的がちがうので使い分けてください。 ::: ## 3. rev とは? tac と何が違うのか {#rev} > **結論**: `rev` は各行の中の文字を左右反転する。行の並び順は変えない。`tac`(行を逆順)とは対象がまったく違う。 ::: dialogue @lina: `tac` は行を逆にする。では `rev` は何を逆にするのですか。 @linny: `rev` は **1 行の中の文字** を逆さまにするんだ。reverse(リバース)の略だよ。 @linny: 変えるのは行の中身だけ。行の並び順はそのままなんだ。 @lina: つまり `abc` が `cba` になるということですか。 @linny: そのとおり。同じファイルで見くらべてみよう。 ::: ```bash $ rev fruits.txt ``` ```output elppa ananab yrrehc ``` ::: highlight **tac と rev の違い(ここが核心)** | コマンド | 逆にする対象 | apple → | 行の順番 | | -------- | -------------- | ------- | ---------- | | `tac` | 行の並び順 | apple | 変わる | | `rev` | 各行の中の文字 | elppa | 変わらない | ::: ::: dialogue @lina: 名前が逆なだけでなく、逆にする中身もちがうんですね。tac は行、rev は文字、と。 @linny: それを覚えれば完璧だよ。ちなみに `rev` は入っているパッケージも別なんだ。 @linny: `tac` は coreutils、`rev` は util-linux に入っている。どちらも最初から使えるけどね。 ::: ## 4. tac と rev を組み合わせると? {#combine} > **結論**: `tac file | rev` で「行の順番」も「各行の文字」も両方逆になる。パイプでつなぐだけ。 ::: dialogue @lina: 行も文字も、両方ひっくり返したいときはどうしますか。 @linny: パイプでつなげばいい。`tac` の出力をそのまま `rev` に流すんだ。 ::: ```bash $ tac fruits.txt | rev ``` ```output yrrehc ananab elppa ``` ::: dialogue @lina: cherry が先頭に来ました。行が逆になっています。しかも yrrehc と、文字まで逆になっています。 @linny: そう、2 段階で効いているんだ。`tac` が行を逆にして、そのあと `rev` が文字を逆にした。 @linny: `rev | tac` と順番を入れかえても結果は同じだよ。2 つは別々の場所を書きかえるからね。 ::: ::: tip `rev` は標準入力も読めます。`echo "Linux" | rev` を実行すると `xuniL` が返ります。ファイルを用意しなくても、パイプで気軽に試せます。 ::: ## 5. リナの失敗:tac のつもりで rev を使う {#pitfall-mixup} > **結論**: 行を逆にしたいのに `rev` を使うと、文字化けのように見える。原因は文字が反転しているだけ。 練習用に小さなログを作ります。 ```bash $ cat << 'EOF' > mini.log 2026-06-01 start 2026-06-02 error 2026-06-03 done EOF ``` ::: dialogue @lina: 先輩、このログを新しい順で見ようとしたら、画面がめちゃくちゃになりました。 ::: ```bash $ rev mini.log ``` ```output trats 10-60-6202 rorre 20-60-6202 enod 30-60-6202 ``` ::: dialogue @lina: 文字化けでしょうか。ファイルが壊れたのかと思って焦りました。 @linny: 落ち着いて。文字化けではないよ。`rev` を使ったから、**1 行の中の文字が左右反転している** だけなんだ。 @lina: えっ、これは正しい動きなんですか。 @linny: そう。よく見て。`2026-06-01` が `10-60-6202` になっている。右から読めば、もとの内容が読めるはずだよ。 @lina: 本当だ、逆から読めました。ファイルは壊れていなかったんですね。安心しました。 @linny: 行の順番を変えたいときは `tac` を使う。こうすれば新しい順になるよ。 ::: ```bash $ tac mini.log ``` ```output 2026-06-03 done 2026-06-02 error 2026-06-01 start ``` ::: dialogue @lina: これが欲しかった形です。`tac` と `rev` の取りちがえには気をつけます。 @linny: いちばん多い事故だからね。対象がちがう、と覚えておけば大丈夫だよ。 ::: ::: tip どちらのコマンドも **元のファイルは変えません**。画面に結果を出すだけです。だから取りちがえてもファイルは壊れません。 なお `tac` と `rev` は、本サイトの仮想ターミナルにはまだありません。この記事の例は、手元の Linux やターミナルで試してください。 ::: ## 6. よくある落とし穴と使い分けまとめ {#summary} > **結論**: 行を逆順にするのが `tac`、行内の文字を逆順にするのが `rev`。混同が一番の事故。迷ったら小さなファイルで試す。 | やりたいこと | コマンド | | ---------------------------- | ----------------- | | 行を下から上へ並べ替える | `tac file` | | ログを新しい順で先頭から読む | `tac log` | | 各行の文字を左右反転する | `rev file` | | 行も文字も両方逆にする | `tac file \| rev` | ::: warning **やってはいけない・間違えやすいこと** - `tac` と `rev` を取りちがえます。行を逆にしたいのに `rev` を使うと、文字化けのように見えます - `tail` と `tac` を混同します。`tail` は順番そのまま、`tac` は逆順です - 大きなファイルでいきなり実行しません。まず `head` を付けるか、小さな例で確かめます ::: ## 7. ミニ課題:実際にやってみよう {#exercise} > **結論**: 行の逆順・文字の逆順・両方の組み合わせの 3 問で、`tac` と `rev` の対象のちがいを手で確かめる。 ::: dialogue @lina: ちがいはわかりました。手を動かして確かめたいです。 @linny: いいね、3 問用意したよ。まず練習用のファイルを作ってから挑戦してみて。 ::: ```bash $ cat << 'EOF' > animals.txt penguin tiger whale EOF ``` **課題 1**:`animals.txt` の行を、下から順に表示しよう。 :::details ヒント 1(方向づけ)を見る 変えたいのは行の並び順です。行の中の文字はそのままにします。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `tac` です。オプションは要りません。 ::: :::details 答えを見る ```bash $ tac animals.txt ``` ```output whale tiger penguin ``` 行の順番だけが変わりました。単語のつづりはそのままです。 ::: **課題 2**:`animals.txt` の各行の文字を、左右反転して表示しよう。 :::details ヒント 1(方向づけ)を見る 今度は行の並び順を変えません。1 行の中の文字だけを逆にします。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `rev` です。 ::: :::details 答えを見る ```bash $ rev animals.txt ``` ```output niugnep regit elahw ``` 行の順番は変わりません。文字だけが逆になりました。 ::: **課題 3**:行の順番も、行の中の文字も、両方逆にして表示しよう。 :::details ヒント 1(方向づけ)を見る 2 つの作業を続けて行います。1 つ目の結果を、そのまま 2 つ目に渡します。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `tac` と `rev` の 2 つです。つなぐ記号は `|` です。 ::: :::details 答えを見る ```bash $ tac animals.txt | rev ``` ```output elahw regit niugnep ``` `rev animals.txt | tac` と書いても同じ結果になります。 ::: ## 8. 振り返り {#review} ::: dialogue @lina: 整理します。`tac` が変えるのは行の並び順で、`rev` が変えるのは行の中の文字ですね。 @linny: そのとおり。名前が似ているぶん、対象で覚えるのが確実だよ。 @lina: ログを新しい順で読みたいときは `tac`。`tail` は順番そのままでしたね。 @linny: 完璧だね。どちらも元のファイルは変えないから、迷ったら小さなファイルで試すといいよ。 ::: ## 今日の 3 行まとめ {#three-lines} 1. `tac` は行の並び順を逆にする。行の中の文字は変えない 2. `rev` は行の中の文字を逆にする。行の並び順は変えない 3. ログを新しい順で読むなら `tac`。`tail` は末尾を元の順番のまま出す ## 次に読む {#next} - [cat でファイルを表示する基本(file-operations-basics)](/articles/tutorials/file-operations-basics) - [sort / uniq で並べ替え・重複排除を学ぶ](/articles/tutorials/sort-uniq-basics) - [cut / paste / tr で列や文字を加工する](/articles/tutorials/cut-paste-tr-basics) # tarコマンドの使い方 - 圧縮・解凍・よくある事故の防ぎ方 Source: https://penguin-gym-linux.com/articles/tutorials/tar-basics ## この記事で解決できること {#intro} - `tar` で「圧縮」「解凍」「中身確認」ができるようになります - `.tar.gz` / `.tgz` / `.tar` の違いと基本コマンドが分かります - ありがちな事故(変な場所に展開、上書き、パス崩れ)を避けられます ::: tip **結論(最短)** 最初はこれだけ覚えると困りません。 - **作る(gzip圧縮)**:`tar -czf archive.tar.gz DIR/` - **解凍**:`tar -xzf archive.tar.gz` - **中身を見る**:`tar -tzf archive.tar.gz` - **展開先を指定**:`tar -xzf archive.tar.gz -C /path/to/dir` ::: ::: warning **前提(対象環境)** - OS:Ubuntu - シェル:bash - 権限:通常ユーザー(必要なら `sudo` を付けます) ::: ## 1. tar のファイル形式の整理(よく出るやつだけ) {#format} > **結論**: 圧縮なしの`.tar`、gzip圧縮の`.tar.gz`/`.tgz`が基本。まずは`.tar.gz`だけ覚えればよい。 - `.tar`:ただまとめただけ(圧縮なし) - `.tar.gz` / `.tgz`:tar でまとめて gzip で圧縮 - `.tar.bz2`:tar でまとめて bzip2 で圧縮 - `.tar.xz`:tar でまとめて xz で圧縮 ::: tip よく見るのは `.tar.gz` / `.tgz` です。まずはそこからでOKです。 ::: ## 2. 圧縮(アーカイブ作成) {#create} > **結論**: `tar -czf archive.tar.gz DIR/`でディレクトリをgzip圧縮できる。複数指定もまとめて可能。 ### 2-1. ディレクトリを .tar.gz にする(基本) ```bash $ tar -czf archive.tar.gz DIR/ ``` - `c`:create(作る) - `z`:gzip圧縮 - `f`:ファイル名を指定(これがないと意図しない動きになることがあります) ### 2-2. 複数のファイル/ディレクトリをまとめる ```bash $ tar -czf archive.tar.gz DIR1/ DIR2/ file.txt ``` ## 3. 解凍(展開) {#extract} > **結論**: `tar -xzf archive.tar.gz`で展開。`-C`で展開先を指定すれば事故を防げる。 ### 3-1. カレントディレクトリに展開(基本) ```bash $ tar -xzf archive.tar.gz ``` - `x`:extract(展開) - `z`:gzip - `f`:ファイル指定 ### 3-2. 展開先ディレクトリを指定(事故防止で推奨) ```bash $ tar -xzf archive.tar.gz -C /path/to/dir ``` ::: tip どこに展開されるか分からないと事故るので、慣れるまでは `-C` を付けるのがおすすめです。 ::: ## 4. 中身を確認する(展開前に必ずやると安全) {#list} > **結論**: `tar -tzf archive.tar.gz`で展開前に中身を確認できる。事故防止の基本動作。 ### 4-1. 一覧を見る(.tar.gz) ```bash $ tar -tzf archive.tar.gz ``` ### 4-2. 先頭だけ見る(長すぎる場合) ```bash $ tar -tzf archive.tar.gz | head ``` ## 5. よくある事故と防ぎ方(ここが一番大事) {#accidents} > **結論**: 展開先誤り・絶対パス・上書き・散らばりが主な事故。`pwd`確認と`-C`指定、事前の中身確認で防げる。 ::: warning **事故A:変な場所に展開してしまう** **原因:** - どこで実行したか分からない - `-C` を付けていない **対策:** 1. 展開前にカレントディレクトリを確認:`pwd` 2. 展開先は `-C` で固定する ::: ```bash $ pwd $ tar -xzf archive.tar.gz -C /tmp ``` ::: warning **事故B:展開すると変なパスが出てくる(先頭に / が付いている等)** **危険な例:** - アーカイブ内のパスが `/etc/...` のように絶対パスになっている - 展開で上書き事故のリスクが上がります **対策:** - 展開前に `tar -tzf` でパスを確認する - 不安ならまず `/tmp` など安全な場所に展開する ::: ```bash $ mkdir -p /tmp/tar-test $ tar -xzf archive.tar.gz -C /tmp/tar-test ``` ::: warning **事故C:同名ファイルがあって上書きしてしまう** **対策:** - いきなり本番ディレクトリに展開しない - まず空の作業ディレクトリに展開して内容を確認する ::: ```bash $ mkdir -p ~/work/extract-test $ tar -xzf archive.tar.gz -C ~/work/extract-test ``` ::: warning **事故D:展開したら「1個のディレクトリにまとまっていない」** アーカイブの作り方によっては、展開するとファイルが散らばることがあります。 **対策(作る側の基本):** - まとめたいディレクトリの"親"に移動してから作る - 例:`DIR/` という1つのディレクトリ配下に収めたい ::: ```bash $ cd /path/to $ tar -czf DIR.tar.gz DIR/ ``` ## 6. 追加:.tar / .tar.bz2 / .tar.xz のコマンド {#other} > **結論**: 圧縮なしは`-cf`/`-xf`、bzip2は`-xjf`、xzは`-xJf`。形式ごとにオプション文字が変わる。 必要になった時だけ使います。 ### 6-1. .tar(圧縮なし) 作る: ```bash $ tar -cf archive.tar DIR/ ``` 展開: ```bash $ tar -xf archive.tar ``` ### 6-2. .tar.bz2 展開: ```bash $ tar -xjf archive.tar.bz2 ``` ### 6-3. .tar.xz 展開: ```bash $ tar -xJf archive.tar.xz ``` ## 確認(直ったかチェック) {#check} > **結論**: 展開後は`ls -la`で、想定どおりの場所にファイルが展開されたかを必ず確認する。 展開後は、想定どおりの場所に展開されたかを確認します。 ```bash $ ls -la ``` ## 次に読む {#next} - [安全なファイル検索と削除](/articles/troubleshooting/find-safe-delete) - [アーカイブをリモートへ転送する](/articles/tutorials/scp-rsync-basics) - [容量不足でアーカイブできない場合](/articles/troubleshooting/no-space-left-on-device) # tar 実践 - 増分バックアップと除外・展開先指定 Source: https://penguin-gym-linux.com/articles/tutorials/tar-incremental-backup ## tar の増分バックアップとは? {#intro} > **結論**: 前回からの変更分だけをアーカイブする方式。`--listed-incremental` でスナップショットを記録し、差分を判定する。 [tar の基本](/articles/tutorials/tar-basics)で圧縮・解凍ができるようになったら、次は実務で必須の3点を押さえる。 - **増分(差分)バックアップ**: 毎回フルコピーせず、変更分だけ取る - **除外**: `node_modules` やキャッシュなど不要なものを外す - **展開先・パス制御**: どこに、どの階層で展開するかを固定する ::: tip **この記事の前提** - OS: Ubuntu(GNU tar) - `tar --version` で `GNU tar` と表示されること(増分オプション `--listed-incremental` は GNU tar 固有) - BSD tar(macOS 標準)では一部オプションが異なる ::: ## なぜ --listed-incremental を使うのか? {#why} > **結論**: `--listed-incremental` は削除・移動も追跡できる。`--newer`(mtime 比較)は削除を検知できず復元時に取りこぼす。 増分バックアップには2つの方式がある。 | 方式 | 判定基準 | 削除の追跡 | 推奨 | | ----------------------------- | -------------------- | ---------- | ---- | | `--listed-incremental` (`-g`) | スナップショット記録 | できる | ◎ | | `--newer` / `--after-date` | mtime(更新時刻) | できない | △ | `--newer` は「指定時刻より新しいファイル」を拾うだけなので、**前回以降に削除されたファイル**を記録できない。復元すると消したはずのファイルが復活する。`--listed-incremental` はディレクトリ状態をスナップショットファイル(慣習的に `.snar` 拡張子)に記録するため、削除も含めて正しく差分を再現できる。 ::: warning mtime ベースの `--newer` は「とりあえず差分」には使えるが、世代管理・正確な復元を要する用途では `--listed-incremental` を使う。 ::: ## 増分バックアップの作り方 {#create} > **結論**: 同じスナップショットファイルを `-g` で指定し続ける。初回は自動でレベル0(フル)、2回目以降はレベル1以降(差分)になる。 ### レベル0(フルバックアップ) スナップショットファイルが存在しない初回は、自動的にフルバックアップになる。 ```bash $ tar -czg /backup/home.snar -f /backup/home-full.tar.gz /home/user/ ``` - `-g /backup/home.snar`: スナップショット(`--listed-incremental` の短縮形) - `-c`: 作成 / `-z`: gzip / `-f`: 出力ファイル - 実行後、`home.snar` にディレクトリ状態が記録される ### レベル1以降(増分) **同じスナップショットファイル**を指定して再実行すると、前回からの変更分だけがアーカイブされる。 ```bash $ tar -czg /backup/home.snar -f /backup/home-inc1.tar.gz /home/user/ ``` `home.snar` は実行のたびに更新され、次回の差分判定に使われる。アーカイブ名(`home-inc1`, `home-inc2`...)は世代ごとに変える。 ::: tip **毎回フルを取りたい場合** `-g /dev/null` を指定すると、スナップショットが保存されない(`/dev/null` は書き込んでも残らない)ため、常にレベル0フルバックアップになる。 ```bash $ tar -czg /dev/null -f /backup/full.tar.gz /home/user/ ``` ::: ::: warning スナップショットファイルは**バックアップ本体とは別に保全**する。`.snar` を失うと差分の連鎖が切れ、次回が意図せずフル相当になったり復元手順が崩れたりする。 ::: ## 増分バックアップからどう復元するのか? {#restore} > **結論**: レベル0から順に、世代順で展開する。展開時も `--listed-incremental` を付けると削除も再現される。 復元は**作成した順番どおり**に展開するのが鉄則。 ```bash $ mkdir -p /restore $ tar -xzg /dev/null -f /backup/home-full.tar.gz -C /restore $ tar -xzg /dev/null -f /backup/home-inc1.tar.gz -C /restore $ tar -xzg /dev/null -f /backup/home-inc2.tar.gz -C /restore ``` - 展開時の `-g` には `/dev/null` を渡す(スナップショットの中身は復元時には参照されないが、`--listed-incremental` モードを有効にするために指定する) - このモードでは、増分で**削除されたファイルは展開先からも削除**される(正しい世代再現) ::: danger 展開時に `-g`(`--listed-incremental`)を付け忘れると、削除の再現が行われず、古い世代で消したファイルが残ったままになる。世代を正確に再現したい場合は必ず付ける。 ::: ::: details 中身を先に確認したいとき 展開前に各アーカイブの中身を一覧で確認する。 ```bash $ tar -tzf /backup/home-inc1.tar.gz | head ``` 増分アーカイブには、変更されたファイルに加えてディレクトリのエントリも含まれる。 ::: ## 除外パターンの指定方法 {#exclude} > **結論**: `--exclude=PATTERN` で個別除外、`--exclude-from=FILE` で一覧指定。オプションはソースパスより前に置く。 ### 個別に除外する ```bash $ tar --exclude='*.log' --exclude='node_modules' \ -czf project.tar.gz project/ ``` - パターンは glob(`*` `?` `[...]`)で、アーカイブ内のメンバー名に対して照合される - `--exclude` は**ソースパス(`project/`)より前**に置く(後ろに置くと効かない場合がある) ### 除外リストをファイルにまとめる 除外対象が多いときは1行1パターンのファイルにする。 ```bash $ cat > exclude.txt <<'EOF' *.log *.tmp node_modules .cache EOF $ tar --exclude-from=exclude.txt -czf project.tar.gz project/ ``` `--exclude-from` の短縮形は `-X`。 ### よく使う専用オプション | オプション | 除外対象 | | ------------------ | ------------------------------------------------ | | `--exclude-vcs` | `.git` / `.svn` / `CVS` などのバージョン管理情報 | | `--exclude-caches` | `CACHEDIR.TAG` を含むキャッシュディレクトリ | ```bash $ tar --exclude-vcs --exclude='*.log' -czf src.tar.gz src/ ``` ## 展開先とパス階層をどう制御するのか? {#extract-control} > **結論**: `-C` で展開先ディレクトリを固定、`--strip-components=N` で先頭階層を除去、`--one-top-level` で1つの親に閉じ込める。 ### 展開先を固定する(-C) ```bash $ tar -xzf project.tar.gz -C /opt/app ``` カレントディレクトリに散らばる事故を防ぐ基本。展開先は事前に作っておく。 ### 先頭の階層を除去する(--strip-components) アーカイブが `project-1.0/src/...` のように1階層深い場合、その階層を剥がして展開できる。 ```bash $ tar -xzf project-1.0.tar.gz --strip-components=1 -C /opt/app ``` `project-1.0/` を取り除き、`/opt/app/src/...` に直接展開される。GitHub のソース tarball を所定の場所へ展開するときの定番。 ### 1つの親ディレクトリに閉じ込める(--one-top-level) 複数のファイルがトップレベルに散らばるアーカイブを、1つのディレクトリ配下に強制的にまとめて展開する。 ```bash $ tar -xzf messy.tar.gz --one-top-level=extracted ``` `extracted/` が作られ、その中に展開される(散らかり防止)。 ## 実践:日次バックアップの型 {#practice} > **結論**: 週次でレベル0、日次でレベル1を回す。除外リストと展開先固定をセットにすれば事故が減る。 ::: tip **コピペ用テンプレ** ```bash # 週次フル(スナップショットを初期化したい場合は .snar を消してから) tar --exclude-from=/etc/backup/exclude.txt \ -czg /backup/home.snar \ -f /backup/home-$(date +%Y%m%d)-full.tar.gz \ /home/user/ # 日次増分(同じ .snar を使い続ける) tar --exclude-from=/etc/backup/exclude.txt \ -czg /backup/home.snar \ -f /backup/home-$(date +%Y%m%d)-inc.tar.gz \ /home/user/ # 復元(フル → 増分の順、削除も再現) tar -xzg /dev/null -f /backup/home-...-full.tar.gz -C /restore tar -xzg /dev/null -f /backup/home-...-inc.tar.gz -C /restore ``` ::: ::: warning **やってはいけないこと** - 復元を増分から先に展開する(必ずフルが先) - スナップショット `.snar` をアーカイブと同じ場所だけに置く(同時消失リスク) - 展開先を `-C` で固定せず本番ディレクトリで直接展開 ::: 定期実行は [cron の基本](/articles/tutorials/cron-basics)、容量設計は[ディスクがいっぱいになったとき](/articles/troubleshooting/no-space-left-on-device)も参照。 ## 次に読む {#next} - [tar の基本(圧縮・解凍・事故防止)](/articles/tutorials/tar-basics) - [圧縮形式の選び方(gzip/bzip2/xz/zstd)](/articles/tutorials/xz-gzip-bzip2-compare) - [cron で定期バックアップを回す](/articles/tutorials/cron-basics) # taskset 入門 - プロセスをCPUコアに固定する Source: https://penguin-gym-linux.com/articles/tutorials/taskset-cpu-affinity ## この記事で解決できること {#intro} - `taskset` で **プロセスを特定の CPU コアに固定** する方法が分かる - **起動時固定(`-c`)** と **実行中プロセスの再割り当て(`-p`)** を使い分けられる - 性能検証・コア隔離・NUMA で **CPU アフィニティを実務で使う型** が身につく ::: tip **結論(実務の型)** - **起動時に固定** → `taskset -c 0,1 ./program` - **実行中プロセスを固定** → `taskset -cp 0-3 ` - **現状確認** → `taskset -cp ` - コア番号は `-c`(リスト形式)が読みやすい。16 進ビットマスクは自動化向け ::: ::: warning **前提(対象環境)** - `taskset` は `util-linux` パッケージに含まれる(多くのディストリで標準導入済み) - コア番号は 0 始まり。総数は `nproc` で確認する - 実行中プロセスのアフィニティ変更には対象プロセスの権限(または `root`)が必要 ::: ## CPU アフィニティとは? {#what} > **結論**: CPU アフィニティとは、プロセスやスレッドを実行できる CPU コアを制限する設定。`taskset` はこの設定を確認・変更するコマンド。 通常、Linux のスケジューラはプロセスを空いているコアへ自由に動かす。CPU アフィニティを設定すると、そのプロセスは**指定したコアの上でしか動かなくなる**。 固定する主な目的は次の 3 つ。 - **キャッシュ局所性の維持**: コア移動で L1/L2 キャッシュが無効化されるのを防ぐ - **コア隔離**: レイテンシ重視のプロセスを専有コアに置き、他プロセスの干渉を避ける - **再現性のある性能測定**: 毎回同じコアで動かしてベンチマークのブレを減らす ::: tip アフィニティは「**動けるコアの集合**」を指定するもの。1 コアに絞れば固定、複数コアを許可すればその範囲内でスケジューラが選ぶ。 ::: ## コア数とコア番号を確認する {#nproc} > **結論**: 固定先を決める前に `nproc` で利用可能コア数を確認する。コア番号は `0` から始まる。 ```bash $ nproc ``` ```output 8 ``` 8 と表示されたら、利用可能なコア番号は `0`〜`7`。`taskset -c 8 ...` のように存在しないコアを指定するとエラーになる。 物理/論理構成まで見たい場合は `lscpu` を併用する。 ```bash $ lscpu ``` ## 起動時にコアを固定する(-c) {#launch} > **結論**: コマンド起動と同時に固定するなら `taskset -c <コアリスト> <コマンド>`。リスト形式は `0,2`(個別)や `0-3`(範囲)で書ける。 新しくプロセスを起動して、最初から特定コアに固定する基本形。 ```bash # コア0と1だけで program を起動 $ taskset -c 0,1 ./program # コア0〜3の範囲で起動 $ taskset -c 0-3 ./program # 単一コア(コア2)に完全固定 $ taskset -c 2 ./program ``` `-c`(`--cpu-list`)はコア番号をそのまま列挙できるので読みやすい。`,` で個別指定、`-` で範囲指定、両者の併用も可能(例: `0,2,4-7`)。 ::: tip 起動するコマンドにオプションを渡す場合は、そのまま後ろに続ける。 `taskset -c 0,1 stress-ng --cpu 2` のように `--` で区切る必要はない。 ::: ## 実行中プロセスのアフィニティを確認・変更する(-p) {#running} > **結論**: すでに動いているプロセスは `-p`(`--pid`)で操作する。`taskset -cp ` で確認、`taskset -cp <コアリスト> ` で変更。 ### 現在のアフィニティを確認する ```bash $ taskset -cp 1234 ``` ```output pid 1234's current affinity list: 0-7 ``` `0-7` は「全コアで動ける」状態(デフォルト)。`-c` を付けるとコアリスト形式、付けないと 16 進マスクで表示される。 ### 実行中プロセスを再割り当てする ```bash # PID 1234 をコア0,1に再割り当て $ taskset -cp 0,1 1234 ``` ```output pid 1234's current affinity list: 0-7 pid 1234's new affinity list: 0,1 ``` PID は `ps` や `pgrep` で調べる。 ```bash $ pgrep -f program ``` ::: warning **引数の順序に注意** `-p` モードでは **コアリストが先、PID が後**。`taskset -cp 0,1 1234` であって、`taskset -cp 1234 0,1` ではない。順序を逆にすると PID をマスクとして解釈し失敗する。 ::: ## 16 進ビットマスクとリスト形式の違い {#mask} > **結論**: `-c` を付けるとコア番号リスト、付けないと 16 進ビットマスク。マスクは各ビットが 1 コアに対応する(bit0=コア0)。 `taskset` は本来 16 進のビットマスクでコアを指定する。`-c` はそれを人間が読みやすいリスト形式に置き換えるオプション。 | 指定したいコア | リスト形式(-c) | 16 進マスク | | -------------- | ---------------- | ----------- | | コア0 | `0` | `0x1` | | コア0,1 | `0,1` | `0x3` | | コア1のみ | `1` | `0x2` | | コア0〜3 | `0-3` | `0xf` | | コア3のみ | `3` | `0x8` | マスクは 2 進で考えると分かりやすい。最下位ビット(右端)がコア0。`0x3` は `0b0011` でコア0とコア1、`0x8` は `0b1000` でコア3を表す。 ```bash # マスク形式での起動(コア0,1 = 0x3) $ taskset 0x3 ./program # マスク形式での確認(-c なし) $ taskset -p 1234 ``` ```output pid 1234's current affinity mask: ff ``` ::: tip 手で書くなら **リスト形式(`-c`)が安全**。マスクは桁を間違えると別のコアを指してしまう。スクリプトで機械生成する場合のみマスクが有利。 ::: ## 全スレッドに適用する(-a) {#all-tasks} > **結論**: マルチスレッドプロセスで全スレッドを固定するには `-a`(`--all-tasks`)を併用する。省略すると主スレッドのみが対象。 `-p` で実行中プロセスを操作する際、デフォルトでは指定 PID(主スレッド)だけにアフィニティが適用され、既存の子スレッドには波及しない。プロセス内の全スレッドをまとめて固定するには `-a` を付ける。 ```bash # PID 1234 の全スレッドをコア0-3に固定 $ taskset -acp 0-3 1234 ``` ::: warning `-a` を付けても、**変更後に新しく生成されたスレッド** はそのプロセスのアフィニティを継承する。既存スレッドへの一括適用が `-a` の役割であり、将来のスレッドは元々継承される点を混同しないこと。 ::: ## 実務での使いどころ {#usecase} > **結論**: 性能測定の再現性確保、レイテンシ重視プロセスのコア隔離、NUMA でのローカルメモリ最適化が代表的なユースケース。 ### ベンチマークの再現性を上げる 毎回同じコアで測ることで、コア間移動によるキャッシュミスのばらつきを排除する。 ```bash $ taskset -c 2,3 ./benchmark ``` ### レイテンシ重視プロセスを隔離する カーネル起動パラメータ `isolcpus` で OS スケジューラから隔離したコアに、`taskset` で目的プロセスだけを載せる運用と組み合わせる。 ### NUMA でローカルメモリに寄せる NUMA 環境では、メモリと同じノードのコアに固定するとリモートメモリアクセスを避けられる。ただしメモリ割り当ても制御したい場合は `numactl` の方が適している(`taskset` は CPU 配置のみ)。 ::: tip **過剰固定の落とし穴** 固定しすぎると、空いている他コアを使えず**かえって遅くなる**ことがある。固定は「測定して効果を確認してから」。やみくもに固定しない。 ::: ## よくあるエラーと対処 {#errors} > **結論**: 多くは「存在しないコア番号」「権限不足」「引数順の誤り」のいずれか。 ### sched_setaffinity: Invalid argument 存在しないコア番号を指定している。`nproc` で範囲を確認する。 ```bash # コアが8個(0-7)しかないのにコア8を指定 → エラー $ taskset -c 8 ./program ``` ### Operation not permitted 他ユーザーのプロセスを変更しようとしている。対象プロセスの所有者で実行するか `sudo` を使う。 ```bash $ sudo taskset -cp 0,1 1234 ``` ### command not found: taskset `util-linux` が入っていない(最小構成のコンテナ等)。Debian/Ubuntu なら以下で導入する。 ```bash $ sudo apt install util-linux ``` ## 次に読む {#next} - [nice/renice 入門 - プロセス優先度を制御する](/articles/tutorials/nice-renice-basics) - [ps・top・killの使い方 - Linuxプロセス管理入門](/articles/tutorials/process-management-basics) - [ジョブ制御入門 - jobs/fg/bg/Ctrl+Z](/articles/tutorials/job-control-basics) # tcpdump 入門 - パケットキャプチャで通信を可視化する Source: https://penguin-gym-linux.com/articles/tutorials/tcpdump-basics ## この記事で解決できること {#intro} - `tcpdump` で **パケットを取り始める最短手順** が分かる - BPF フィルタで **見たい通信だけに絞り込む** 方法が身につく - pcap 保存 → **Wireshark で読む** 連携の型が分かる - 「接続できない」「応答が遅い」を **パケットレベルで切り分け** できる ::: tip **結論(実務の型)** - まず `sudo tcpdump -i any -nn` で生きている通信を眺める - 騒がしいときは `host` / `port` フィルタで対象を絞る - 後で解析するなら `-w cap.pcap` で保存し Wireshark へ - 原則 `-nn` を付ける(名前解決の待ちで固まらない) ::: ::: warning **前提(対象環境)** - OS:Ubuntu / 一般的な Linux - `tcpdump` はパケットキャプチャに root 権限(`CAP_NET_RAW`)が必要 → `sudo` 前提 - 未インストールなら `sudo apt install tcpdump` ::: ## tcpdump とは何か? {#what} > **結論**: tcpdump は CLI のパケットキャプチャツール。NIC を流れる生のパケットを取得・表示し、通信の有無や中身を直接確認できる。 `tcpdump` は、ネットワークインターフェイス(NIC)を通過するパケットをそのまま取得して表示する CLI ツール。Web の応答が返らない、TCP が確立しない、といった事象を「サーバまでパケットが届いているか」「応答が返っているか」という事実ベースで切り分けられる。 GUI の Wireshark と内部のキャプチャ基盤(libpcap / BPF)は共通で、`tcpdump` はその CLI 版にあたる。サーバ上で SSH 越しに即実行できるのが強み。 ::: warning パケットキャプチャは通信内容(場合によっては認証情報)を観測する行為。**自分が管理する環境・許可された環境でのみ**実行すること。 ::: ## 最短でキャプチャを始めるには? {#start} > **結論**: `sudo tcpdump -i any -nn` が出発点。インターフェイス指定 `-i`、名前解決抑止 `-nn`、件数制限 `-c` の3つを押さえれば十分動かせる。 ### まず流れている通信を眺める ```bash $ sudo tcpdump -i any -nn ``` - `-i any`:すべてのインターフェイスを対象(特定なら `-i eth0`) - `-nn`:ホスト名もポート名も解決しない(DNS 逆引き待ちで固まらない / 数値のまま速く読める) ### 取得可能なインターフェイスを確認する どの IF を指定すべきか分からないときは一覧を出す。 ```bash $ tcpdump -D ``` ```output 1.eth0 [Up, Running, Connected] 2.any (Pseudo-device that captures on all interfaces) [Up, Running] 3.lo [Up, Running, Loopback] ``` ### 件数を区切って止める `tcpdump` は止めるまで流れ続ける。`-c` で件数を区切ると扱いやすい。 ```bash $ sudo tcpdump -i any -nn -c 20 ``` ::: tip 手動で止めるときは `Ctrl + C`。終了時に「received / dropped by kernel」の統計が表示される。`dropped` が多い場合は取りこぼしが発生している。 ::: ## 出力の1行をどう読むのか? {#read} > **結論**: 各行は「時刻 送信元 > 宛先 フラグ・シーケンス・長さ」の順。`>` の向きと TCP フラグ(S/F/P/R/.)で通信の流れと状態を読む。 典型的な TCP の1行は次の形。 ```output 10:11:12.345678 IP 192.168.1.10.54322 > 93.184.216.34.443: Flags [S], seq 123456, win 64240, length 0 ``` | 要素 | 意味 | | -------------------- | ---------------------------------- | | `10:11:12.345678` | タイムスタンプ | | `192.168.1.10.54322` | 送信元 IP とポート(末尾がポート) | | `>` | 通信方向(左 → 右) | | `93.184.216.34.443` | 宛先 IP とポート(443 = HTTPS) | | `Flags [S]` | TCP フラグ(後述) | | `length 0` | ペイロード長 | ### TCP フラグの読み方 | 表記 | フラグ | 意味 | | ---- | ------- | ------------------ | | `S` | SYN | 接続開始要求 | | `S.` | SYN/ACK | 接続要求への応答 | | `.` | ACK | 確認応答のみ | | `P` | PSH | データ送出(push) | | `F` | FIN | 接続終了 | | `R` | RST | 接続の強制リセット | 3ウェイハンドシェイクが成立していれば `[S]` → `[S.]` → `[.]` が並ぶ。`[S]` を送って `[R]`(RST)が返る、あるいは何も返らない場合は、ポートが閉じている / 経路で遮断されている可能性が高い。 ## 見たい通信だけに絞るには?(BPFフィルタ) {#filter} > **結論**: コマンド末尾に BPF 式を書くと対象を絞れる。`host` / `port` / `src` / `dst` と `and` / `or` / `not` の組み合わせがほぼすべて。 `tcpdump` の引数末尾に書く条件式を BPF(Berkeley Packet Filter)という。代表的なものだけ覚えれば実務は回る。 ```bash # 特定ホストとの通信だけ $ sudo tcpdump -i any -nn host 93.184.216.34 # 特定ポートだけ(HTTPS) $ sudo tcpdump -i any -nn port 443 # 送信元 / 宛先を限定 $ sudo tcpdump -i any -nn src 192.168.1.10 $ sudo tcpdump -i any -nn dst port 53 # プロトコルを限定 $ sudo tcpdump -i any -nn udp port 53 ``` ### 条件を組み合わせる ```bash # host A かつ port 443 $ sudo tcpdump -i any -nn host 192.168.1.10 and port 443 # 22番(SSH)以外を見たい $ sudo tcpdump -i any -nn not port 22 # DNS か HTTPS $ sudo tcpdump -i any -nn 'port 53 or port 443' ``` ::: warning `and` / `or` / `not` を含む式や括弧を使う場合は、シェルの解釈を避けるため **式全体をシングルクォート** で囲む(例: `'tcp port 80 and host 10.0.0.1'`)。 ::: ::: tip よく使う語彙: `host` / `net 192.168.1.0/24` / `port` / `portrange 8000-8100` / `src` / `dst` / `tcp` / `udp` / `icmp` / `arp`。 ::: ## 中身(ペイロード)まで見るには? {#payload} > **結論**: `-A` で ASCII、`-X` で16進+ASCII を表示。平文の HTTP ヘッダや DNS クエリの確認に有効。詳細度は `-v` / `-vv` で調整する。 ヘッダだけでなくパケットの中身を確認したいときに使う。 ```bash # ASCII でペイロード表示(HTTPヘッダ等の確認に) $ sudo tcpdump -i any -nn -A port 80 # 16進数 + ASCII(バイナリ解析向け) $ sudo tcpdump -i any -nn -X port 80 # 詳細度を上げる(TTL・オプション等) $ sudo tcpdump -i any -nn -vv host 192.168.1.10 ``` ::: warning HTTPS(443)はペイロードが暗号化されているため `-A` / `-X` で中身は読めない(TLS のハンドシェイクや SNI の一部は見える)。平文が見えるのは HTTP・DNS など暗号化されていない通信。 ::: ::: tip 古い情報で `-s 0`(スナップ長を無制限に)が必要とされることがあるが、近年の `tcpdump` は既定のスナップ長が十分大きく(262144 バイト)、通常は指定不要。 ::: ## キャプチャを保存して後で解析するには? {#save} > **結論**: `-w file.pcap` でバイナリ保存、`-r file.pcap` で読み戻す。保存した pcap は Wireshark でそのまま開けるので、取得はサーバ・解析は手元という分業ができる。 ### 保存と読み戻し ```bash # pcap 形式で保存(画面には出さず書き出す) $ sudo tcpdump -i any -nn -w capture.pcap # 保存したファイルを読む(読むだけなら sudo 不要) $ tcpdump -nn -r capture.pcap # 読み込み時にもフィルタを後がけできる $ tcpdump -nn -r capture.pcap port 443 ``` ::: tip **取得はサーバ、解析は手元** 1. サーバで `sudo tcpdump -i any -nn -w /tmp/cap.pcap port 443 -c 1000` 2. 手元へ転送(`scp user@server:/tmp/cap.pcap .`) 3. Wireshark で `cap.pcap` を開く GUI のフィルタ・追跡機能で深く追えるので、込み入った解析はこの型が速い。 ::: ### ファイルを回して溜めすぎない 長時間キャプチャはディスクを圧迫する。サイズやファイル数でローテーションできる。 ```bash # 100MB ごとにファイルを分割、最大10ファイルで循環 $ sudo tcpdump -i any -nn -w cap.pcap -C 100 -W 10 ``` - `-C 100`:1ファイル約100MBで切り替え - `-W 10`:保持ファイル数の上限(超えると古いものから上書き) ## 切り分けの実例:接続できない {#case} > **結論**: 「SYN を送って応答が無い / RST が返る」かを見れば、到達性とポート開放を一気に切り分けられる。 ある相手の443番に繋がらないときの確認。 ```bash $ sudo tcpdump -i any -nn host 93.184.216.34 and port 443 ``` 判断の目安: - `[S]` を送って `[S.]` が返る → 到達・ポート開放はOK(問題はアプリ層) - `[S]` を送って `[R]`(RST)が返る → 相手まで届いているがポートが閉じている - `[S]` を送るが**何も返らない** → 経路の途中(FW・経路設定)で落ちている可能性 - 自分発の `[S]` すら出ない → ローカルの経路・名前解決・アプリ側を疑う ::: tip `ping` / `traceroute` での到達性確認と併用すると切り分けが速い。詳細は関連記事を参照。 ::: ## よく使うオプション早見表 {#cheatsheet} > **結論**: `-i` / `-nn` / `-c` / `-w` / `-r` / `-A` / `-v` の7つを押さえれば日常のトラブルシュートは十分カバーできる。 | オプション | 役割 | | ---------- | ------------------------------------------ | | `-i IF` | キャプチャするインターフェイス(`any` 可) | | `-nn` | ホスト名・ポート名を解決しない | | `-c N` | N 件で停止 | | `-w FILE` | pcap 形式で保存 | | `-r FILE` | pcap を読み込む | | `-A` | ペイロードを ASCII 表示 | | `-X` | 16進数 + ASCII 表示 | | `-v/-vv` | 詳細度を上げる | | `-e` | リンク層(MAC アドレス)も表示 | | `-D` | 利用可能なインターフェイス一覧 | ::: tip **コピペ用:安全テンプレ** ```bash # まず眺める sudo tcpdump -i any -nn -c 50 # 対象を絞る sudo tcpdump -i any -nn host 192.168.1.10 and port 443 # 保存して後で解析 sudo tcpdump -i any -nn -w /tmp/cap.pcap port 443 -c 1000 tcpdump -nn -r /tmp/cap.pcap ``` ::: ## 次に読む {#next} - [nc (netcat) でポート疎通を確認する](/articles/tutorials/netcat-basics) - [ping/traceroute/dig で障害を切り分ける](/articles/tutorials/network-troubleshooting-practical) - [ファイアウォールで通信が落ちていないか確認する](/articles/tutorials/firewall-basics) # tee コマンド入門 - 出力を分岐させる Source: https://penguin-gym-linux.com/articles/tutorials/tee-basics ## この記事でわかること {#what-you-will-learn} - tee コマンドの役割と通常のリダイレクト(`>`)との違い - `command | tee ファイル名` の基本的な使い方 - `-a` オプションで追記する方法 - 複数ファイルへの同時保存 - `sudo tee` で root 権限が必要なファイルに書き込む方法 ## tee って何? {#what-is-tee} > **結論**: teeはパイプの途中で出力をファイルに保存しながら次のコマンドへも流せるT字型の分岐コマンドだ。 ::: dialogue @lina: パイプ(`|`)でコマンドをつなぐのは覚えたんですけど、途中の結果も手元に残したいなあって思うんです。 @linny: それが `tee` の出番だよ!「T字型の水道管」みたいに、出力を分岐させるコマンドなんだ。 ::: パイプでコマンドをつなぐとき、出力は次のコマンドへ流れていくだけです。でも `tee` を使うと、**流しながらファイルにも保存**できます。 ``` 通常のパイプ: コマンドA → コマンドB(途中は見えない) tee を使うと: コマンドA → tee ─→ ファイルに保存 ↓ コマンドBへも流れる ``` ::: tip **tee の名前の由来** 英字の大文字「T」の形に似ているから。入力が 1 本で、出力が 2 方向に分岐します。 ::: ## 基本的な使い方 {#basic} > **結論**: command | tee ファイル名でファイルに保存しながら画面にも表示できる。>リダイレクトと異なり画面表示が止まらない。 ::: dialogue @lina: どう使うんですか? @linny: パイプの途中に `tee ファイル名` を挟むだけ! ::: ```bash command | tee ファイル名 ``` **例**:`ls -la` の結果を画面に表示しながら `list.txt` に保存 ```bash ls -la | tee list.txt ``` 実行すると、画面に結果が表示されつつ、`list.txt` にも同じ内容が書き込まれます。 ::: highlight **ポイント** - 画面への表示:止まらずそのまま出る - ファイルへの保存:同時に書き込まれる ::: ### `>` リダイレクトとの違い {#vs-redirect} ::: dialogue @lina: じゃあ `ls -la > list.txt` とどう違うんですか? @linny: いい質問!`>` は画面には表示されないけど、`tee` は画面にも出るんだよ。 ::: | 方法 | 画面への表示 | ファイルへの保存 | | --------------------- | ------------ | ---------------- | | `command` | あり | なし | | `command > file` | なし | あり | | `command \| tee file` | あり | あり | ::: tip どちらを使うかは目的次第。「確認しながら保存したい」なら `tee`、「とにかくファイルに保存するだけ」なら `>` で十分です。 ::: ## 実行ログを残しながら確認する {#log} > **結論**: make install 2>&1 | tee install.logのように2>&1を組み合わせるとエラー出力もログに残せる。 ::: dialogue @lina: 使いどころってどんなときですか? @linny: たとえばインストールや make コマンドを実行するとき、ログを残しながら進捗も確認したいよね。 ::: ```bash make install 2>&1 | tee install.log ``` `2>&1` は「エラー出力も一緒に流す」という指定です。これでエラーメッセージも含めてログに残せます。 ::: tip **`2>&1` とは** - `1` = 標準出力(通常の出力) - `2` = 標準エラー出力(エラーメッセージ) - `2>&1` = エラーも標準出力にまとめる これを書かないと、エラーメッセージはファイルに入らず画面にしか出ません。 ::: ## -a オプション:追記する {#append} > **結論**: デフォルトは上書きのため、ログを蓄積したい場合はtee -aで追記モードにする必要がある。 ::: dialogue @lina: `tee` でファイルに保存するとき、毎回上書きされちゃうんですよね? @linny: デフォルトはそう。追記したいときは `-a` オプションをつけよう。 ::: ```bash command | tee -a ファイル名 ``` **例**:毎回の実行結果を同じログファイルに追加していく ```bash date | tee -a run.log date | tee -a run.log cat run.log ``` ```output Mon Jun 1 03:00:00 UTC 2026 Mon Jun 1 03:00:05 UTC 2026 ``` ::: warning `-a` なしで tee を使うと、毎回ファイルが**上書き**されます。ログを蓄積したいときは必ず `-a` をつけましょう。 ::: ## 複数ファイルに同時保存 {#multiple-files} > **結論**: command | tee ファイル1 ファイル2のようにファイル名を並べると全ファイルに同じ内容を書き込める。 ::: dialogue @lina: 2 つのファイルに同時に保存できたりしますか? @linny: できるよ!ファイル名を並べるだけ。 ::: ```bash command | tee ファイル1 ファイル2 ``` **例**:`/tmp/result.txt` と `~/backup.txt` の両方に保存 ```bash ls -la | tee /tmp/result.txt ~/backup.txt ``` ::: tip ファイルはいくつでも並べられます。全部に同じ内容が書き込まれます。 ::: ## sudo tee で root ファイルに書き込む {#sudo-tee} > **結論**: echo "..." | sudo tee -a /etc/hostsの形でsudoをtee側に付けるとroot権限でファイルに書き込める。 ::: dialogue @lina: root 権限が必要な設定ファイルを変えたいとき、どうするんですか? @linny: `sudo tee` の出番!`sudo` と組み合わせると、root 権限が必要なファイルにも書き込めるんだ。 ::: root 権限が必要なファイルを書き換えたいとき、`sudo echo > file` はうまくいきません。 ```bash # これは失敗する(リダイレクトはシェルの処理なので sudo の効果が届かない) sudo echo "127.0.0.1 example.local" >> /etc/hosts ``` ::: warning `sudo echo > file` が失敗する理由:`sudo` がかかるのは `echo` コマンドだけです。`>>` によるリダイレクトはシェル自身が処理するため、一般ユーザー権限のままになります。 ::: `sudo tee` を使うと正しく書き込めます。 ```bash echo "127.0.0.1 example.local" | sudo tee -a /etc/hosts ``` これは `echo` でテキストを生成し、それを `sudo tee` に流すことで、root 権限で `/etc/hosts` に追記しています。 ::: tip 画面に内容が出力されて邪魔な場合は、末尾に `> /dev/null` をつけると抑制できます。 ```bash echo "127.0.0.1 example.local" | sudo tee -a /etc/hosts > /dev/null ``` ::: ::: warning `sudo tee` は強力なコマンドです。`/etc/` 配下の重要なファイルを書き換えるときは、必ず事前にバックアップを取りましょう。 ```bash sudo cp /etc/hosts /etc/hosts.bak ``` ::: ## パイプの途中で結果を確認する {#debug} > **結論**: パイプの途中にteeを挟むと中間結果をファイルに保存でき、デバッグ時に各ステップの出力を確認できる。 ::: dialogue @lina: パイプが長くなると、途中で何が流れてるか分からなくなります。 @linny: `tee` をデバッグに使うといいよ!途中経過をファイルに保存しながら次のコマンドに流せる。 ::: ```bash cat access.log | grep "ERROR" | tee /tmp/errors.txt | sort | uniq -c ``` 上のコマンドでは: 1. `grep "ERROR"` で絞り込んだ結果を `tee` で `/tmp/errors.txt` に保存 2. その後 `sort | uniq -c` で集計 途中結果を `cat /tmp/errors.txt` で確認できるのでデバッグに役立ちます。 ::: tip パイプが複数段ある場合は、各ステップで `tee` を使って途中結果を保存しながら進めると原因特定がしやすくなります。 ::: ## まとめ {#summary} ::: dialogue @lina: tee、便利ですね!パイプの途中でファイルに保存できるって、すごく使えそう。 @linny: そう!特に `sudo tee` は実務でよく出てくるパターンだから覚えておくと便利だよ。ログを残しながら作業するのも大事な習慣だしね。 ::: | 用途 | コマンド例 | | ------------------------ | -------------------------------------- | | 画面表示しながら保存 | `command \| tee file.txt` | | 追記保存(上書きしない) | `command \| tee -a file.txt` | | 複数ファイルに保存 | `command \| tee file1 file2` | | root ファイルに書き込む | `echo "..." \| sudo tee -a /etc/hosts` | | パイプ途中の出力を確認 | `cmd1 \| tee /tmp/debug.txt \| cmd2` | ## 次に読む {#next} - [head・tail・パイプの使い方](/articles/tutorials/file-operations-advanced) - [sort と uniq の使い方](/articles/tutorials/sort-uniq-basics) - [xargs 実践活用 - 標準入力をコマンド引数に変換する](/articles/tutorials/xargs-practical) # test / [[ ]] 入門 - シェルスクリプトの条件判定 Source: https://penguin-gym-linux.com/articles/tutorials/test-conditionals ## この記事で解決できること {#intro} - `test` / `[ ]` / `[[ ]]` の **違いと使い分け** が分かる - 文字列・数値・ファイルの **比較演算子を正しく選べる** ようになる - `[: too many arguments` のような **クォート事故を構造的に回避** できる ::: tip **結論(実務の型)** - 迷ったら **`[[ ]]` を使う**(bash 前提なら安全側に倒れる) - POSIX `sh` への移植性が必要なときだけ **`[ ]`(= `test`)** を使う - 事故の原因はほぼ **①クォート漏れ ②演算子の取り違え(`-eq` と `=`)** の 2 つ ::: ::: warning **前提(対象環境)** - シェル: bash(`[[ ]]` は bash / ksh / zsh の拡張で、POSIX `sh` には無い) - スクリプト先頭が `#!/bin/bash` であることを想定 ::: ## test・[ ]・[[]] は何が違うのか? {#difference} > **結論**: `[ ]` は `test` コマンドそのもの。`[[ ]]` はシェルの予約語で、クォート・演算子・パターンマッチが強化されている。 3 つの関係を一言で整理すると次のとおり。 | 記法 | 正体 | 移植性 | 主な強み | | ------------ | ---------------------------------- | --------------- | ---------------------------------- | | `test EXPR` | 外部にも実体がある組み込みコマンド | POSIX(高い) | 最も基本的 | | `[ EXPR ]` | `test` の別名(`]` は引数) | POSIX(高い) | `if` と相性が良い見た目 | | `[[ EXPR ]]` | シェルの予約語(keyword) | bash 等(限定) | クォート安全・`=~`・`&&`/`\|\|` 可 | `[ ]` は **コマンド** なので、`[` と `]` の前後、各演算子の前後には必ずスペースが要る。`test` と `[` は同じ動作をする。 ```bash $ test 1 -lt 2; echo $? 0 $ [ 1 -lt 2 ]; echo $? 0 ``` 一方 `[[ ]]` はシェルが構文として解釈するため、後述のクォート事故やパターンマッチで有利になる。 ::: tip **`[` の正体を確認する** ```bash $ type [ [ is a shell builtin $ ls -l /usr/bin/[ -rwxr-xr-x 1 root root ... /usr/bin/[ ``` 組み込み版が優先されるが、`test`/`[` は外部コマンドとしても存在する歴史的経緯がある。 ::: ## 文字列はどう比較するのか? {#string} > **結論**: 文字列一致は `=`(`[[ ]]` では `==` も可)、空判定は `-z`/非空は `-n`。変数は必ずダブルクォートで囲む。 ### 基本の演算子 - `=` … 等しい(`[[ ]]` 内では `==` も同義) - `!=` … 等しくない - `-z "$s"` … 文字列が空(zero length) - `-n "$s"` … 文字列が空でない ```bash name="penguin" if [[ "$name" = "penguin" ]]; then echo "match" fi if [[ -z "$name" ]]; then echo "empty" else echo "not empty" fi ``` ### `=` と `==` の使い分け `==` は bash の拡張。POSIX `sh` への移植を考えるなら `[ ]` では `=` を使うのが安全。`[[ ]]` 内ではどちらでも動く。 ```bash [ "$a" = "$b" ] # POSIX 安全 [[ "$a" == "$b" ]] # bash([[ ]] 内では == も =も可) ``` ## 数値はどう比較するのか? {#numeric} > **結論**: 数値比較は `-eq -ne -lt -le -gt -ge` を使う。`=` や `>` は文字列比較になり別物。 | 演算子 | 意味 | 数学記号 | | ------ | ---------- | -------- | | `-eq` | 等しい | `==` | | `-ne` | 等しくない | `!=` | | `-lt` | より小さい | `<` | | `-le` | 以下 | `<=` | | `-gt` | より大きい | `>` | | `-ge` | 以上 | `>=` | ```bash count=42 if [[ "$count" -ge 10 ]]; then echo "10 以上" fi ``` ::: warning **`=` と `-eq` を取り違えない** ```bash [[ "08" = "8" ]] # false(文字列として違う) [[ "08" -eq "8" ]] # true(数値として等しい) ``` 数値のつもりで `=` を使うと、ゼロ埋めや空白で意図しない結果になる。 ::: 算術評価が必要なら `(( ))` を使う手もある。`(( count >= 10 ))` のように数学記号がそのまま書ける。 ## ファイルの存在や種類はどう調べるのか? {#file} > **結論**: ファイルテスト演算子で存在・種類・権限を判定する。よく使うのは `-e -f -d -r -w -x -s`。 | 演算子 | 意味 | | --------- | -------------------------- | | `-e file` | 存在する(種類を問わない) | | `-f file` | 通常ファイルである | | `-d file` | ディレクトリである | | `-r file` | 読み取り可能 | | `-w file` | 書き込み可能 | | `-x file` | 実行可能 | | `-s file` | サイズが 0 より大きい | | `-L file` | シンボリックリンクである | | `a -nt b` | a が b より新しい | ```bash config="/etc/myapp.conf" if [[ -f "$config" && -r "$config" ]]; then echo "読み込める設定ファイルがある" else echo "設定ファイルが無いか読めない" fi ``` ::: tip **「存在しない」を判定する** `!` で否定する。スクリプトの早期リターンで頻出する型。 ```bash if [[ ! -d "$dir" ]]; then echo "ディレクトリがありません: $dir" >&2 exit 1 fi ``` ::: ## なぜ [[]] のほうが事故りにくいのか? {#why-bracket} > **結論**: `[[ ]]` は変数を展開しても単語分割・グロブ展開をしないため、クォート漏れによる構文崩壊が起きない。 `[ ]` はコマンドなので、変数が空や空白を含むと **引数の数が変わって** 構文が壊れる。 ```bash v="" [ $v = "x" ] # → [ = "x" ] と解釈され: too many arguments / 構文エラー [ "$v" = "x" ] # → [ "" = "x" ](正しく false)クォートで回避 v="a b" [ $v = "x" ] # → [ a b = "x" ](引数 4 個でエラー) ``` `[[ ]]` 内では、変数を展開しても単語分割・グロブが起きない。クォートを忘れても壊れにくい。 ```bash v="a b" [[ $v = "x" ]] # 壊れない(false) ``` ::: warning それでも **クォートする習慣** は残すこと。`[ ]` でも `[[ ]]` でも `"$var"` と書いておけば、どちらに移植しても安全。 ::: ## [[]] だけのパターンマッチと正規表現とは? {#pattern} > **結論**: `[[ ]]` の `==` 右辺はグロブパターン、`=~` は正規表現として評価される。`[ ]` には無い機能。 ### グロブによるパターンマッチ(`==`) 右辺をクォートしないとパターン、クォートすると文字列として扱う。 ```bash file="report.txt" if [[ "$file" == *.txt ]]; then echo "テキストファイル" fi if [[ "$file" == "*.txt" ]]; then echo "これはリテラルの *.txt と一致したときだけ" fi ``` ### 正規表現マッチ(`=~`) 右辺は **クォートしない**。マッチ部分は `BASH_REMATCH` 配列に入る。 ```bash input="port=8080" if [[ "$input" =~ ^port=([0-9]+)$ ]]; then echo "ポート番号: ${BASH_REMATCH[1]}" fi ``` ::: danger `=~` の右辺をダブルクォートで囲むと、正規表現ではなく **リテラル文字列** として扱われる(bash 3.2 以降)。パターンは変数に入れて `[[ "$s" =~ $re ]]` とするのが安全。 ::: ## 複数条件はどうつなぐのか? {#logic} > **結論**: `[[ ]]` 内なら `&&` `||` で連結できる。`[ ]` では `[ ... ] && [ ... ]` とコマンド単位でつなぐ。 ```bash # [[ ]] の中で連結(読みやすい) if [[ "$age" -ge 18 && "$age" -lt 65 ]]; then echo "現役世代" fi # [ ] はコマンドを && でつなぐ if [ "$age" -ge 18 ] && [ "$age" -lt 65 ]; then echo "現役世代" fi ``` ::: warning `[ ]` 内の `-a`(AND)・`-o`(OR)は **非推奨**。引数の解釈が曖昧になり事故の温床になる。条件をつなぐときは `[ ]` を分けて `&&` / `||` でつなぐこと。 ::: ## まとめ:test と [[]] の使い分け {#summary} > **結論**: bash スクリプトなら `[[ ]]` を基本にし、POSIX `sh` 互換が要る箇所だけ `[ ]` を使う。クォートと演算子選択を外さなければ事故はほぼ防げる。 - **`[[ ]]`** … bash 前提。クォート安全・`=~`・`&&`/`||` が使えて読みやすい - **`[ ]` / `test`** … `#!/bin/sh` や POSIX 互換が必要な場面 - 文字列は `=` / `-z` / `-n`、数値は `-eq` 系、ファイルは `-f` / `-d` 系 - 変数は常に `"$var"` でクォートする ::: tip **コピペ用テンプレ** ```bash # 引数チェック if [[ $# -lt 1 ]]; then echo "usage: $0 " >&2 exit 1 fi target="$1" # ファイル存在と種類 if [[ ! -f "$target" ]]; then echo "通常ファイルではありません: $target" >&2 exit 1 fi # 拡張子で分岐 if [[ "$target" == *.log ]]; then echo "ログファイルを処理します" fi ``` ::: ## 次に読む {#next} - [globbing(ワイルドカード)入門 - パターンマッチの基礎](/articles/tutorials/globbing-wildcards) - [コマンド置換 入門 - $(...) で結果を変数に取り込む](/articles/tutorials/command-substitution) # timeout コマンド入門 - 実行時間を制限してハングを防ぐ Source: https://penguin-gym-linux.com/articles/tutorials/timeout-command ## この記事で学べること {#intro} - `timeout` で **コマンドの実行時間に制限** をかけられるようになる - 「固まって戻ってこない」コマンドを **自動で打ち切る** 方法が分かる - タイムアウトしたかどうかを **終了ステータス `124`** で判定できる - SIGTERM を無視するしぶといプロセスを **`-k` で強制終了** できる ::: tip **結論(先に覚えるべき型)** - 時間を区切って実行 → `timeout 10 コマンド`(10 秒で打ち切り) - 単位を付けてもよい → `timeout 30s` / `timeout 5m` / `timeout 1h` - 打ち切られたかの判定 → 終了ステータスが **`124`** ならタイムアウト - それでも止まらないとき → `timeout -k 5 10 コマンド`(5 秒後に強制終了) ::: ::: warning **先に覚える「固まったように見えるとき」の抜け方** コマンドが戻ってこないと、画面が壊れたように感じる。しかし、たいていは次の順で抜けられる。 | 見えている状態 | 抜け方 | | ------------------------------------ | ----------------------------------------------------------- | | コマンドが終わらずプロンプトが出ない | `Ctrl+C` を押して中止する | | `Ctrl+C` でも止まらない | `Ctrl+Z` で一時停止し、`jobs` で番号を見て `kill %1` | | 毎回この状態になるのを避けたい | 最初から `timeout 10 コマンド` のように上限を付けて実行する | `timeout` は「そもそも固まったまま放置しない」ための予防策。だから毎回 `Ctrl+C` を押す必要がなくなる。 ::: ## 1. timeout コマンドとは何か? {#what} > **結論**: `timeout` は「指定した時間を超えたらコマンドを自動で終了させる」コマンド。ハング対策の定番。 ::: dialogue @lina: 先輩、ダウンロードのコマンドを実行しました。でも、いつまでも終わらなくて画面が固まっちゃったんです...。`Ctrl+C` で止めるしかないんですか? @linny: そういうとき便利なのが `timeout` だよ。**「このコマンドは最大 N 秒だけ動かす。それを超えたら自動で止める」** という時間制限をかけられるんだ。 @lina: 自分で `Ctrl+C` を押さなくても、勝手に止めてくれるんですか? @linny: そう。だから「固まったまま放置」を防げる。とくに **cron**(クーロン。決めた時刻に自動でコマンドを動かすしくみ。「定期実行」「スケジューラ」とも呼ぶ)で動かす処理では効果が大きい。 @linny: 固まったコマンドが残り続けると、あとの処理まで止まってしまうからね。 @lina: なるほど、`timeout` はその保険なんですね。 ::: ::: highlight **ハング(hang)とは** ハングは「コマンドが終わらないまま止まって見える状態」のこと。「フリーズ」「固まる」「応答がない」も、ほぼ同じ意味で使われる。プログラムがこわれているとはかぎらない。相手の返事をずっと待っているだけのこともある。 ::: ::: highlight **timeout の基本イメージ** ``` timeout 10 コマンド ↑ ↑ | 実行したいコマンド 制限時間(秒) ``` 「10 秒以内に終われば普通に終了、超えたら強制的に打ち切り」。 ::: ## 2. なぜ timeout が必要なのか? {#why} > **結論**: ネットワーク待ちや無限ループで固まるコマンドを放置すると、スクリプト全体が止まる。timeout がそれを防ぐ。 ::: dialogue @lina: でも、ちゃんと動くコマンドなら時間制限はいらないですよね? @linny: 普段はね。問題は **「いつ終わるか分からない」コマンド** なんだ。応答しないサーバーへの接続、消えた相手への `ping`、書き方を間違えた無限ループ。こういうのは **永遠に戻ってこない** ことがある。 @lina: たしかに、さっきのダウンロードがまさにそれでした...。 @linny: 人間が見ていれば `Ctrl+C` で止められる。でも cron や自動化スクリプトには **見ている人がいない**。 @linny: 固まったプロセスが居座ると、次の処理も始まらない。`timeout` で上限を決めておけば、最悪でも「N 秒で諦めて次へ進む」ようにできるんだ。 ::: ::: warning 固まったコマンドを放置すると、メモリやプロセス枠を食いつぶす。cron の多重起動を招くこともある。**「いつ終わるか分からない処理には時間の上限を付ける」** のが安全な運用。 ::: ## 3. 基本の使い方(時間の指定) {#basic} > **結論**: `timeout 時間 コマンド` の形。数字だけなら秒、`s`/`m`/`h`/`d` で単位も付けられる。 いちばん基本の形は次の通り。 ```bash $ timeout 10 sleep 30 ``` `sleep 30` は本来 30 秒待つコマンド。だが `timeout 10` を付けると **10 秒で打ち切られる**。 ::: dialogue @lina: `sleep 30` なのに 10 秒で終わった! 約束の 30 秒より早く止まりましたね。 @linny: そう、それが時間制限の効果。数字だけ書くと **秒** という意味になる。単位を付けて分かりやすく書くこともできるよ。 ::: 時間には単位(サフィックス)を付けられる。 ```bash $ timeout 30s コマンド # 30秒 $ timeout 5m コマンド # 5分 $ timeout 1h コマンド # 1時間 $ timeout 2d コマンド # 2日 ``` ::: highlight **時間の単位(サフィックス)** | 書き方 | 意味 | | ------ | -------------------- | | `10` | 10秒(単位なし=秒) | | `30s` | 30秒 | | `5m` | 5分 | | `1h` | 1時間 | | `2d` | 2日 | `0.5` のような小数も使える(`timeout 0.5 コマンド` で0.5秒)。 ::: ## 4. 終了ステータス 124 でタイムアウトを判定する {#exit-status} > **結論**: timeout で打ち切られたときの終了ステータスは `124`。`echo $?` が `124` ならタイムアウトと判断できる。 ::: highlight **終了ステータス(exit status)とは** 終了ステータスは、コマンドが終わるときに残す番号のこと。「終了コード」「リターンコード」「exit code」も同じものを指す呼び方だよ。`0` は成功。`0` 以外は「何か普通ではないことが起きた」という意味になる。直前のコマンドの値は `$?` で見られる。 ::: `timeout` が時間切れでコマンドを止めたとき、**終了ステータスは `124`** になる。これを見れば、タイムアウトしたかどうかを自動で見分けられる。 ```bash $ timeout 1 sleep 10 $ echo $? ``` ```output 124 ``` ::: dialogue @lina: `124` という数字が出ました。これがタイムアウトの目印なんですね。 @linny: その通り。逆に、コマンドが時間内にちゃんと終わった場合は **そのコマンド自身の終了ステータス** が返る。成功なら `0` だね。 @linny: だから `124` かどうかを見れば「途中で打ち切られたのか、ちゃんと終わったのか」が分かる。 ::: 時間内に終わった場合は、コマンド本来のステータスが返る。 ```bash $ timeout 10 sleep 1 $ echo $? ``` ```output 0 ``` ::: tip **覚えておきたい終了ステータス** | 値 | 意味 | | ----- | ------------------------------------------------ | | `124` | タイムアウトで打ち切られた | | `125` | `timeout` コマンド自体の失敗 | | `126` | コマンドは見つかったが実行できない | | `127` | コマンドが見つからない | | `137` | KILL シグナル(`-k`)で強制終了された(128 + 9) | まずは **「124 = タイムアウト」** だけ覚えれば十分。 まれに、コマンド自身が `124` を返すこともある。そこまで区別したいときは `--preserve-status` を使う。 ::: ## 5. -k でしぶといプロセスを強制終了する {#kill} > **結論**: timeout はデフォルトで SIGTERM を送る。無視されるときは `-k 時間` で猶予後に SIGKILL を送って確実に止める。 ::: highlight **プロセス(process)とは** プロセスは「今このコンピュータの中で動いている、プログラム 1 つ 1 つ」のこと。あなたが打ったコマンドも、動き始めるとプロセスになる。 ::: ::: highlight **シグナル(signal)とは** シグナルは、プロセスに送る短い合図のこと。「終わってください」「今すぐ止まれ」といった内容を番号と名前で送る。よく使うのは 3 つだけ。 - **SIGTERM**: 終了のお願い。プログラム側はあとしまつをしてから終われる - **SIGINT**: `Ctrl+C` を押したときに送られる合図 - **SIGKILL**: 強制終了。プログラム側は拒否できない ::: ::: dialogue @lina: `timeout` で止めたはずなのに、たまにコマンドが止まらないと聞きました。どうしてですか? @linny: いい質問。`timeout` は時間が来ると、まず **SIGTERM(終了のお願い)** を送る。でも、このお願いを **無視するプログラム** もあるんだ。 @lina: 「止まってね」とお願いしても聞いてくれないんですね...。じゃあどうすれば? @linny: そこで `-k`(kill-after)を使う。「最初のお願いから N 秒待っても止まらないなら、今度は強制終了する」という二段構えにできるんだ。 ::: ```bash $ timeout -k 5 10 コマンド ``` これは次の意味になる。 - まず **10 秒** でコマンドに SIGTERM(終了のお願い)を送る - それでも **さらに 5 秒** 止まらなければ SIGKILL(強制終了)を送る ::: highlight **-k(kill-after)の動き** ``` timeout -k 5 10 コマンド ↑ ↑ | 本来の制限時間(10秒)→ SIGTERM 追加の猶予(5秒)→ それでも残れば SIGKILL ``` SIGKILL はプログラム側が拒否できない。どうしても止めたいときの、いちばん最後の手だて。 ::: ::: warning SIGKILL(強制終了)は **あとしまつをする間を与えずに** プロセスを止める。書きかけのファイルが壊れる可能性もある。まずは普通の `timeout`(SIGTERM)で試そう。止まらないときの保険として `-k` を使うのがよい。 ::: ## 6. シグナルを指定する(-s) {#signal} > **結論**: `-s` で送るシグナルを変えられる。終了時にコマンド自身のステータスを返したいなら `--preserve-status`。 デフォルトでは SIGTERM が送られる。`-s`(`--signal`)を使えば、別のシグナルを指定できる。 ```bash $ timeout -s SIGINT 10 コマンド ``` これは `Ctrl+C` と同じ **SIGINT** を送る。コマンドによっては、SIGTERM より行儀よく終われることがある。 ::: dialogue @lina: シグナルって、さっきの SIGTERM とか SIGKILL のことですよね。種類があるんですか? @linny: たくさんあるよ。`timeout` でよく使うのは 3 つ。お願い系の **SIGTERM**(デフォルト)と **SIGINT**(`Ctrl+C` 相当)、いちばん最後の手だてになる **SIGKILL** だね。 @linny: シグナルそのものは少し奥が深い。別記事(trap 入門)でじっくり扱うよ。 ::: タイムアウトしたときに `124` へ書き換えず、**コマンドが最期に返した値をそのまま受け取りたい** ときは `--preserve-status` を使う。 ```bash $ timeout --preserve-status 10 コマンド ``` ::: tip `--preserve-status` を付けると、SIGTERM で止められた場合は `143`(128 + 15)が返る。「コマンドが正常に終わったときの値」が返るわけではない。「シグナルで止められた」という事実まで含めて知りたい場面で使う。通常は付けない。`124` で判定する方がシンプル。 ::: ## 7. よくある初心者のつまずき {#pitfalls} > **結論**: 時間とコマンドの順番、`124` の意味、`-k` の引数の並びを取り違えやすい。 ### 7-1. 時間とコマンドの順番を逆にする ```bash # NG: コマンドが先になっている $ timeout sleep 30 10 # OK: 時間 → コマンドの順 $ timeout 10 sleep 30 ``` ::: warning `timeout` のすぐあとは **かならず時間**。その後に実行したいコマンドを書く。順番を逆にすると `timeout` がエラーになる。 ::: ### 7-2. 124 を「エラー」と勘違いする ::: dialogue @lina: 先輩、大変です!さっきのスクリプトが `124` を返しました。私、コマンドを壊しちゃったんでしょうか...? @linny: 落ち着いて。`124` は **「timeout が時間どおり仕事をした」証拠** なんだ。 @lina: えっ、失敗の合図じゃないんですか? @linny: 違うよ。コマンドの不具合ではない。「時間内に終わらなかったので、予定どおり打ち切った」という意味なんだ。 @lina: なんだ、そういうことか...!じゃあ `124` を見たら「上限に達した」と読めばいいんですね。安心しました。 ::: ### 7-3. パイプ全体にかけたつもりで一部しか制限されない ```bash # これは「timeout 10 で grep」だけにかかる書き方ではない $ timeout 10 grep pattern file | sort ``` パイプ(`|`。左のコマンドの結果を、右のコマンドへ渡す書き方)でつないでも、制限がかかるのは `timeout 10 grep pattern file` の部分だけ。`| sort` は別扱いになる。 パイプ全体を一括で囲みたいときは、`bash -c 'コマンド列'`(まとめてひとつのコマンドとして扱わせる書き方)を使う。 ```bash $ timeout 10 bash -c 'grep pattern file | sort' ``` ## 8. ミニ課題:実際にやってみよう {#exercise} > **結論**: 打ち切り・終了ステータス確認・強制終了の 3 問で timeout を手で確かめる。 ::: dialogue @lina: 知識は入りました! 手を動かしてみたいです。 @linny: いいね、3 問用意したよ。題材は `sleep` にしてある。消えて困る中身が無いので、失敗しても何も壊れない。 ::: ### 課題 1: sleep 30 を 3 秒で打ち切る {#exercise-1} **やること**: 本来 30 秒かかるコマンドを、3 秒で終わらせよう。 :::details ヒント1(方向づけ)を見る 制限時間を先に書き、そのあとに実行したいコマンドを書く。数字だけを書いた場合の単位は何になるか思い出そう。 ::: :::details ヒント2(コマンド名)を見る 使うのは `timeout`。形は `timeout 時間 コマンド`。数字だけなら秒の意味になる。 ::: :::details 答えを見る ```bash $ timeout 3 sleep 30 ``` 3 秒でプロンプトが戻る。`sleep` の 30 秒を待たされない。 ::: ### 課題 2: タイムアウトしたか確かめる {#exercise-2} **やること**: 課題 1 のすぐあとに終了ステータスを表示して、`124` になることを確かめよう。 :::details ヒント1(方向づけ)を見る 直前のコマンドの結果は、特別な変数に入っている。それを表示するコマンドを使おう。 ::: :::details ヒント2(コマンド名)を見る 直前の結果は `$?` に入っている。表示は `echo $?`。 ::: :::details 答えを見る ```bash $ timeout 3 sleep 30 $ echo $? ``` `124` が表示されればタイムアウト成功。 ::: ### 課題 3: 止まらないときに強制終了する {#exercise-3} **やること**: `sleep 30` を「5 秒で SIGTERM、さらに 2 秒待っても止まらなければ SIGKILL」で止める指定を書こう。 :::details ヒント1(方向づけ)を見る 二段構えにするオプションがある。猶予の秒数と、本来の制限時間の 2 つを並べて書く。 ::: :::details ヒント2(コマンド名)を見る 使うのは `-k`。並び順は `-k 猶予秒 制限秒 コマンド`。 ::: :::details 答えを見る ```bash $ timeout -k 2 5 sleep 30 ``` `sleep` は SIGTERM で素直に止まる。だから 5 秒で終わり、SIGKILL までは進まない。 ::: ::: dialogue @lina: できました!「時間が先、コマンドが後」で、結果は `124` を見る。この 2 つが軸ですね。 @linny: そのとおり。あとは止まらない相手に `-k` を足すだけ。この 3 つで、固まったまま放置される事故はほぼ防げるよ。 ::: ## 9. コピペ用テンプレート {#templates} > **結論**: 基本・単位付き・判定・強制終了・パイプのよく使う型をまとめて手元に置いておく。 ::: tip **よく使う型をまとめておく** ```bash # 基本:10秒で打ち切る timeout 10 コマンド # 単位を付ける(秒 / 分 / 時) timeout 30s コマンド timeout 5m コマンド # タイムアウト判定(124 ならタイムアウト) timeout 10 コマンド echo $? # 止まらないときは強制終了(10秒 + 猶予5秒で SIGKILL) timeout -k 5 10 コマンド # パイプ全体に制限をかける timeout 10 bash -c 'コマンドA | コマンドB' # Ctrl+C 相当(SIGINT)で止める timeout -s SIGINT 10 コマンド ``` ::: ## 今日の 3 行まとめ {#summary} - `timeout 時間 コマンド` で実行時間に上限を付けられる。順番は **時間が先、コマンドが後** - 終了ステータス **`124` はタイムアウトの合図**。エラーではなく「予定どおり打ち切った」という意味 - SIGTERM を無視するプロセスには **`-k 猶予秒 制限秒`** で二段構えにする。SIGKILL はいちばん最後の手だて ## 次に読む {#next} - [終了ステータス(exit status)入門 - $? と && || で処理を分岐する](/articles/tutorials/exit-codes-and-status) - [trap 入門 - シグナルを捕捉してクリーンアップ処理を書く](/articles/tutorials/trap-signal-handling) - [watch コマンド入門 - コマンドを定期実行して変化を監視する](/articles/tutorials/watch-command-basics) - [仮想ターミナルで練習する](/terminal)(`timeout` は手元の Linux や WSL のターミナルで試そう) # /tmp の自動削除設定 - tmpfiles.d と systemd-tmpfiles の書き方 Source: https://penguin-gym-linux.com/articles/tutorials/tmp-cleanup ## この記事で解決できること {#intro} - `/tmp` の掃除が自動で行われる仕組みを理解できる - `tmpfiles.d` のルールファイルを自作できる - `systemd-tmpfiles` コマンドを手動・自動の両方で使える ::: tip **結論(実務の型)** - `/tmp` の自動削除は `systemd-tmpfiles-clean.timer` が担う - カスタムクリーンアップは `/etc/tmpfiles.d/` にルールファイルを置く - ルールの書式は `タイプ パス モード UID GID 年齢 引数` ::: ::: warning **前提(対象環境)** - OS: Ubuntu / systemd 搭載 Linux - root 権限または sudo 実行が必要 ::: ## /tmp とは何か — なぜ掃除が必要なのか? {#tmp} `/tmp` はアプリケーションが一時ファイルを書き込む場所で、**再起動後は内容が保証されない**。長時間稼働するサーバでは放置するとディスクを圧迫し、PID ファイルやソケットファイルの誤参照が起きる。 - **典型的な用途**: 圧縮・解凍の中間ファイル、セッションデータ、ソケットファイル - **Ubuntu のデフォルト**: `/tmp` はメモリ上の `tmpfs` にマウントされており、再起動で自動消去 - **`/var/tmp`**: 再起動後も消えない永続 tmp。意図的な管理が必要 ```bash $ mount | grep /tmp tmpfs on /tmp type tmpfs (rw,nosuid,nodev) ``` ::: tip `tmpfs` の場合は再起動で自動消去されるが、長時間稼働システムでは蓄積するため定期削除が必要。`df -h /tmp` でディスク使用量を確認しておく。 ::: ## systemd-tmpfiles とは何か? {#systemd-tmpfiles} `systemd-tmpfiles` は、`/tmp` や `/run` といった一時ディレクトリの**作成・削除・パーミッション管理を設定ファイルで宣言的に制御する**ツール。 3 つの動作モードを持つ: | モード | コマンド | 動作 | | -------- | ---------- | ---------------------------------------------- | | 作成 | `--create` | ルールに従ってファイル・ディレクトリを作成 | | 削除 | `--clean` | 年齢基準を超えた一時ファイルを削除 | | 完全削除 | `--remove` | ルール対象のファイル・ディレクトリをすべて削除 | ```bash # 手動で実行する場合 $ sudo systemd-tmpfiles --create $ sudo systemd-tmpfiles --clean $ sudo systemd-tmpfiles --remove ``` systemd の起動シーケンスに統合されており、2 つのユニットが自動動作する: - `systemd-tmpfiles-setup.service` — 起動時に `--create` を実行 - `systemd-tmpfiles-clean.timer` — 定期的(デフォルト: 起動後 15 分 + 1 日ごと)に `--clean` を実行 ## tmpfiles.d の設定ファイルの書き方 {#config} 設定ファイルは `/etc/tmpfiles.d/*.conf` に置く。システム提供のルールは `/usr/lib/tmpfiles.d/` にある(直接編集禁止、パッケージ更新で上書きされる)。 ### 書式 ``` タイプ パス モード UID GID 年齢 引数 ``` 各フィールドの意味: | フィールド | 説明 | 例 | | ---------- | -------------------------------------------- | ------------------------------------------------------------- | | タイプ | 操作の種類 | `d`(ディレクトリ作成)/ `f`(ファイル作成)/ `x`(削除除外) | | パス | 対象パス | `/tmp/myapp` | | モード | パーミッション(8進数) | `0755` | | UID / GID | 所有者(`-` で現状維持) | `root` / `-` | | 年齢 | この経過時間を超えたら削除(`--clean` 時) | `7d`(7日)/ `1h`(1時間)/ `-`(削除しない) | | 引数 | ファイルタイプの場合の初期内容(オプション) | `-` | ### よく使うタイプ一覧 | タイプ | 意味 | | ------ | ------------------------------------------------------------------- | | `d` | ディレクトリを作成(なければ)、年齢超過ファイルを `--clean` で削除 | | `D` | `d` と同じだが `--remove` で中身ごと削除 | | `f` | ファイルを作成(なければ) | | `f+` | ファイルを作成または上書き | | `x` | `--clean` / `--remove` の対象から除外 | | `e` | 既存ファイル・ディレクトリのパーミッションを設定 | | `z` | パーミッション・SELinux ラベルを設定 | ## 定期クリーンアップをどう動かすか? {#timer} `systemd-tmpfiles-clean.timer` が自動的に周期実行される。状態確認: ```bash $ systemctl status systemd-tmpfiles-clean.timer ``` ```output ● systemd-tmpfiles-clean.timer - Daily Cleanup of Temporary Directories Loaded: loaded (/lib/systemd/system/systemd-tmpfiles-clean.timer; static) Active: active (waiting) since Mon 2024-01-01 00:00:00 UTC; 3h ago Trigger: Tue 2024-01-02 00:15:30 UTC; 20h left ``` 手動でクリーンアップを即時実行: ```bash $ sudo systemctl start systemd-tmpfiles-clean ``` ::: warning `--clean` は年齢フィールドが設定されているエントリのみ削除対象とする。年齢フィールドを `-` にしたエントリは手動・自動いずれの削除も受けない。 ::: ## カスタムルールの実践例 {#examples} ### 例 1: アプリ専用一時ディレクトリを作成・管理する ```bash # /etc/tmpfiles.d/myapp.conf d /tmp/myapp 0750 myapp myapp 1d ``` `/tmp/myapp` を `myapp:myapp` 所有・`0750` で作成し、1日経過したファイルを削除。 ### 例 2: /var/tmp 配下のログを7日で削除する ```bash # /etc/tmpfiles.d/clean-var-tmp.conf d /var/tmp/app-logs 0755 root root 7d ``` ### 例 3: /run 配下に PID ディレクトリを起動時に作成する(削除なし) ```bash # /etc/tmpfiles.d/myapp-run.conf d /run/myapp 0755 myapp myapp - ``` 年齢 `-` なので削除されない。起動時に必ず作成される。 ### 例 4: 特定パスを削除対象から除外する ```bash # /etc/tmpfiles.d/exclude.conf x /tmp/persistent-cache ``` `--clean` 時に `/tmp/persistent-cache` をスキップする。 ### 設定を即時反映・確認する ```bash # 作成ルールを即時実行(新しいディレクトリを生成) $ sudo systemd-tmpfiles --create /etc/tmpfiles.d/myapp.conf # ドライランで削除対象を確認 $ sudo systemd-tmpfiles --clean --dry-run /etc/tmpfiles.d/myapp.conf ``` ::: tip `--dry-run` は実際には削除せず、何が削除対象になるかを出力する。本番実行前に必ず確認する。 ::: ## よくあるトラブルと対処 {#troubleshooting} ### 削除が起きない 年齢フィールドが `-` になっていないか確認。また `--clean` は**ファイルの最終アクセス時刻(atime)**を基準にする。書き込みタイムスタンプ(mtime)ではない。 ```bash # ファイルの atime を確認 $ stat /tmp/myfile | grep Access ``` ::: tip `noatime` マウントオプション使用時は atime が更新されず、実際より古く見える。意図的に atime を更新してテストするには `touch -a /tmp/myfile` を使う。 ::: ### 作成したルールが反映されない 設定ファイルの構文を直接検証する: ```bash $ sudo systemd-tmpfiles --create /etc/tmpfiles.d/myapp.conf ``` エラーが出なければ正常。journalctl でエラーログも確認: ```bash $ journalctl -u systemd-tmpfiles-setup ``` ### /usr/lib/tmpfiles.d/ のルールを上書きしたい `/etc/tmpfiles.d/` に同名ファイルを置くと上書きされる(優先順位: `/etc` > `/run` > `/usr/lib`)。 ```bash # システム提供ルールを確認 $ cat /usr/lib/tmpfiles.d/tmp.conf # 上書き用ファイルを作成して編集 $ sudo cp /usr/lib/tmpfiles.d/tmp.conf /etc/tmpfiles.d/tmp.conf ``` ::: warning **やってはいけないこと** - `/usr/lib/tmpfiles.d/` 内のファイルを直接編集(パッケージ更新で上書きされる) - `D` タイプを重要なディレクトリ配下に対して年齢なしで設定(`--remove` で中身が全消去) ::: ## 次に読む {#next} - [systemd timer と cron の使い分け](/articles/tutorials/systemd-timer-vs-cron) - [journalctl の使い方](/articles/tutorials/journalctl-basics) - [ディスク管理入門](/articles/tutorials/disk-management-basics) # tmux 入門 - ターミナル多重化の基本 Source: https://penguin-gym-linux.com/articles/tutorials/tmux-basics ## この記事でできるようになること {#intro-list} - `tmux` を使って **SSH 接続が切れても処理を続けられる** - セッション・ウィンドウ・ペインの **3 階層** が分かる - `Ctrl+b` から始まる **prefix キー操作** に慣れる - デタッチ(席を離れる)とアタッチ(戻る)を自由にできる - 1 つのターミナル画面を分けて、複数の作業を並べられる **対象読者**: SSH でサーバ作業をする方。回線が切れるとログ取得が止まって困っている方。ターミナルを 1 つしか開けないのは不便だと感じている方。 ## 導入:リナが SSH 切断で泣いた日 {#intro} ::: dialogue @lina: ライニー先輩、聞いてください!リモートサーバで長いログ取得を回していました。でも Wi-Fi が一瞬切れて、ターミナルが落ちたんです。最初からやり直しになっちゃいました... @linny: SSH で作業していたんだね。SSH(エスエスエイチ)は「手元のパソコンから、離れた場所のコンピュータへ安全につなぐしくみ」のこと。「リモート接続」とも呼ぶよ。 @linny: そして、その事故は `tmux`(ティーマックス)で防げる。 @lina: ティーマックス?テックの新作ですか? @linny: ターミナル多重化ツールのことだよ。ターミナル多重化(マルチプレクサ、multiplexer とも呼ぶ)は「1 つの画面の中に、いくつもの画面を持たせるしくみ」なんだ。 @lina: いくつもの画面、ですか? @linny: そう。しかも、その画面はサーバ側に置きっぱなしにできる。回線が切れても画面はサーバに残るんだ。だからつなぎ直せば、そのまま続きから作業できる。 @lina: それすごい!どんなしくみなんですか? @linny: tmux サーバという別のプログラムが、SSH とは切り離されて動いている。画面の中身を抱えてくれるのは、この tmux サーバだよ。SSH が切れても tmux サーバは平気で動き続ける。 @linny: 今日は 3 つの言葉を順に覚えよう。①セッション ②ウィンドウ ③ペイン、の 3 階層だよ。 ::: ::: tip **結論(実務の型)** - 長い処理を流す前に **`tmux` で入る**(これだけで SSH 事故が大きく減る) - 席を離れるとき **`Ctrl+b` → `d`** でデタッチ - 戻ったら **`tmux attach`** で再開 - 画面を増やしたい → **ウィンドウ**(`Ctrl+b c`) - 画面を分けたい → **ペイン**(`Ctrl+b %` / `Ctrl+b "`) ::: ::: warning **先に覚える「脱出のしかた」** tmux は画面の見た目が変わる。だから「戻れなくなった」と感じる人が多い。迷ったら次の 3 つで抜けられる。 | 困った状態 | 脱出のしかた | | ------------------------------ | ------------------------------------------------ | | tmux の中で迷子になった | `Ctrl+b` を押して離す → `d`(デタッチ) | | デタッチしたあと画面に戻りたい | `tmux attach` | | そもそも中にいるか分からない | `echo $TMUX` と打つ。何か表示されれば tmux の中 | `Ctrl+b` → `d` はいつでも使える。この 2 打で必ず元のターミナルに戻れる。 なお、セッションを消してしまっても、消えるのは画面だけ。保存済みのファイルは残るので、落ち着いて試してほしい。 ::: ## 1. tmux のインストール {#install} > **結論**: tmux は入っていないこともある。まず `tmux -V` で確認し、なければ apt / dnf で入れる。 ::: dialogue @lina: tmux ってデフォルトで入ってないんですか? @linny: ディストリ(ディストリビューション。Ubuntu や CentOS のような Linux の種類のこと)によって違うんだ。最初に入っているか確認するクセをつけよう。 ::: ### バージョン確認 {#check-version} ```bash $ tmux -V ``` ```output tmux 3.4 ``` 「command not found」と出たらインストールが必要。 ### Ubuntu / Debian {#install-debian} ```bash $ sudo apt update $ sudo apt install tmux ``` ### CentOS / RHEL / Rocky Linux {#install-rhel} ```bash $ sudo dnf install tmux ``` ::: warning **インストールできない場合**: 共用サーバでは `sudo`(管理者としてコマンドを実行するしくみ)が使えないことがある。その場合は自分で入れようとせず、サーバの管理者に依頼しよう。 ::: ## 2. はじめての tmux セッション {#first-session} > **結論**: `tmux` と打つだけでセッションが始まる。画面の下に出る緑の帯が、tmux の中にいる証拠。 ::: dialogue @linny: インストールできたら、まず `tmux` と打ってみよう。 @lina: いきなり打つのは、ちょっと怖いです。 @linny: 大丈夫。`Ctrl+b` → `d` でいつでも抜けられる。これだけ覚えておけば迷子にならないよ。 ::: ::: highlight **セッション(session)とは** セッションは「tmux が預かってくれる作業場所ひとつ」のこと。喫茶店の席を 1 つ取るイメージだよ。席には荷物(実行中のコマンド)を置いたままにできる。あなたが席を離れても、荷物はそのまま残る。 ::: ### セッションを開く {#open-session} ```bash $ tmux ``` 実行すると画面の下に緑の帯が出る。これが tmux の中にいる証拠。 ```output [0] 0:bash* "hostname" 14:00 23-May-26 ``` ### 何か作業してみる {#do-something} 中ではいつものシェルが動いている。シェルは「打ったコマンドを受け取って実行してくれるプログラム」のこと。bash や zsh がその代表だよ。試しに何か実行してみよう。 ```bash $ echo "tmux の中で動いてる" $ pwd ``` ::: dialogue @lina: 普通のターミナルと変わらないですね? @linny: 見た目はね。違うのは「この画面がサーバ側で生きている」こと。次はデタッチして、それを確かめよう。 ::: ## 3. デタッチとアタッチ - tmux 最大の武器 {#detach-attach} > **結論**: `Ctrl+b → d` で席を離れ、`tmux attach` で戻る。SSH が切れてもセッションはサーバ側で生き続ける。 ::: dialogue @linny: いよいよ tmux の本題だよ。デタッチ(detach、切り離し)とアタッチ(attach、つなぎ直し)だね。 @lina: 「デタッチ」って怖い響きですね... @linny: 言葉だけ怖いんだ。デタッチは「席を離れる」、アタッチは「席に戻る」。それだけだよ。 @linny: 例えるなら、家を出るのに近い。部屋の照明を消しても、部屋そのものは残っているよね。デタッチも同じで、画面はサーバに残る。 ::: ### デタッチ:Ctrl+b → d {#detach} tmux の中で次のキーを順に押す。 ```bash Ctrl+b d ``` すると緑の帯が消える。通常のターミナルに戻る。 ```output [detached (from session 0)] $ ``` ::: tip **大事な感覚**: 「閉じた」のではない。「席を離れた」だけ。サーバ側で tmux セッションは生き続けている。 ::: ### セッション一覧を確認 {#list-sessions} ```bash $ tmux ls ``` ```output 0: 1 windows (created Fri May 23 14:00:00 2026) ``` `0` がセッションの名前。ちゃんと生きている。 ### アタッチ:戻る {#attach} ```bash $ tmux attach ``` さっきの画面がそのまま戻る。コマンド履歴も、開いていたファイルも、実行中の処理も残っている。 ::: dialogue @lina: えっ、これ感動なんですけど!SSH が切れても怖くないって、こういうことですか? @linny: そう。実務の流れはこうだよ。SSH でログインしたら、**最初に `tmux` で入る**。あとは普通に作業する。 @linny: Wi-Fi が切れてもいい。ノート PC を閉じてもいい。tmux セッションは生きている。SSH でつなぎ直したら `tmux attach` で復帰、そのまま作業を続けられる。 ::: ### リナの失敗:exit で消してしまった {#lina-mistake} ::: dialogue @lina: 先輩、大変です!席を離れるつもりで `exit` と打ったら、`tmux attach` が「no sessions」って言うんです。さっきの作業はどこですか? @linny: それは残念だけど、消えてしまった。`exit` は「セッションを閉じる」命令なんだ。デタッチとは違うんだよ。 @lina: えっ、同じように見えたのに...! @linny: 見分け方はかんたん。**席を離れるのは `Ctrl+b` → `d`**、**部屋ごとたたむのが `exit`**。今日から `exit` は「もう戻らないとき」だけに使おう。 @lina: なるほど、「離れる」と「閉じる」は別なんですね。`Ctrl+b` → `d` を体に覚えさせます! ::: ::: warning **よくある勘違い**: `exit` でシェルを抜けるとセッションが消える。消えたセッションはアタッチで戻せない。「席を離れる」のは `Ctrl+b → d`(デタッチ)だけ。 ::: ## 4. prefix キーって何? {#prefix} > **結論**: prefix キー(既定は `Ctrl+b`)は「次の入力は tmux への命令」と知らせる合図。押して離してから命令キーを押す。 ::: dialogue @lina: さっきから「Ctrl+b」が何度も出てきますけど、これは何ですか? @linny: tmux への合図キーだよ。prefix(プレフィックス、前置きの意味)キーと呼ぶ。「これから tmux 自身に命令するよ」と伝える役目なんだ。既定は `Ctrl+b`。 @lina: 普通のキー入力は中のシェルに届いて、prefix のあとのキーは tmux に届くんですね。 @linny: そう、その理解で完璧。`Ctrl+b` の直後の `d` は「デタッチして」という命令。`c` なら「新しいウィンドウを作って」という命令だよ。 ::: ### prefix キーの基本パターン {#prefix-pattern} ```output Ctrl+b → 何かのキー ``` `Ctrl+b` は **押しっぱなしにしない**。`Ctrl+b` を離してから、次のキーを押す。 ::: tip **覚えておきたい prefix コマンド(最初の 5 つだけでいい)** | キー | 動作 | | ------------ | ---------------- | | `Ctrl+b` `d` | デタッチ | | `Ctrl+b` `c` | 新規ウィンドウ | | `Ctrl+b` `n` | 次のウィンドウへ | | `Ctrl+b` `%` | ペイン縦分割 | | `Ctrl+b` `"` | ペイン横分割 | ::: ## 5. ウィンドウ:画面を切り替える {#windows} > **結論**: ウィンドウはブラウザのタブに当たる。`Ctrl+b c` で作り、`n` / `p` / 数字キーで切り替える。 ::: dialogue @linny: tmux にはセッションの下に「ウィンドウ」がある。ウィンドウ(window)は、ブラウザのタブに当たるものだよ。 @lina: 1 つのセッションの中に、複数のタブを持てるってことですか? @linny: 正解。「タブ 1 でログを見る、タブ 2 でファイルを編集、タブ 3 で別のコマンド」と分けられる。頭の中も整理しやすくなるよ。 ::: ### 新規ウィンドウを作る {#new-window} ```bash Ctrl+b c ``` `c` は create の c。新しいウィンドウが開いて、そちらに移動する。 緑の帯を見るとこうなる。 ```output [0] 0:bash- 1:bash* ``` `0` と `1` の 2 つのウィンドウがある。`*` が今いる場所。`-` が直前にいた場所。 ### ウィンドウを切り替える {#switch-window} ```bash Ctrl+b n # 次のウィンドウへ(next) Ctrl+b p # 前のウィンドウへ(previous) Ctrl+b 0 # 0 番のウィンドウへ直接 Ctrl+b 1 # 1 番のウィンドウへ直接 ``` ### ウィンドウを閉じる {#close-window} そのウィンドウのシェルで `exit` と打つ。または `Ctrl+b &` でも閉じられる(確認プロンプトが出る)。 ## 6. ペイン:1 つの画面を分ける {#panes} > **結論**: ペインは 1 つの画面を縦横に分ける機能。`Ctrl+b %` で左右、`Ctrl+b "` で上下に分け、矢印キーで移動する。 ::: dialogue @lina: ウィンドウは分かりました!でも「画面を分ける」のは別の話ですか? @linny: そう、それが「ペイン」。ペイン(pane)は窓ガラスの一区画という意味だよ。同じ画面を縦や横に分けて、並べて見られる。 @lina: 左でログを見ながら、右でファイルを編集する、みたいな? @linny: その通り。並べて見られると、確認の手間がぐっと減るよ。 ::: ### 縦に分ける(左右に分割) {#split-vertical} ```bash Ctrl+b % ``` 画面が縦線で割れる。左右 2 つのペインになる。 ### 横に分ける(上下に分割) {#split-horizontal} ```bash Ctrl+b " ``` 画面が横線で割れる。上下 2 つのペインになる。 ::: tip **覚え方**: - `%` は縦棒っぽい → 縦分割(左右) - `"` は横向きの引用符 → 横分割(上下) 直感と逆に感じる人が多い。「キーの形」で覚えるのが楽。 ::: ### ペイン間の移動 {#move-pane} ```bash Ctrl+b ← # 左のペインへ Ctrl+b → # 右のペインへ Ctrl+b ↑ # 上のペインへ Ctrl+b ↓ # 下のペインへ Ctrl+b o # 次のペインへ順送り ``` ### ペインを閉じる {#close-pane} そのペインで `exit` と打つ。または `Ctrl+b x`(確認プロンプトあり)。 ## 7. つまずきポイント集 {#pitfalls} > **結論**: prefix の押しっぱなし、tmux 内での二重起動、`exit` でのセッション消去が初心者の三大つまずき。 ::: dialogue @lina: 先輩、よくあるつまずきってありますか? @linny: 3 つだけ覚えておこう。これで初心者のうちのミスは、だいたいカバーできる。 ::: ### ピンチ 1: Ctrl+b を押しっぱなしにする {#pitfall-1} ::: warning **症状**: prefix のあとのキーが効かない。または変な動きになる。 **原因**: `Ctrl+b` を **押しっぱなし** で次のキーを押している。 **対処**: `Ctrl+b` は一度押して **離す**。それから次のキーを押す。 ::: ### ピンチ 2: tmux の中で tmux を起動してしまう {#pitfall-2} ::: warning **症状**: `tmux` と打ったのに新しい画面が開かない。次のメッセージだけが出る。 ```output sessions should be nested with care, unset $TMUX to force ``` **原因**: すでに tmux の中にいる。tmux は入れ子(tmux の中の tmux)をデフォルトで止めてくれる。 **対処**: 何も壊れていない。そのまま今の tmux を使えばよい。今どこにいるか確かめたいときは `echo $TMUX` と打つ。何か表示されれば tmux の中にいる。 ::: ### ピンチ 3: exit でセッションごと消した {#pitfall-3} ::: warning **症状**: アタッチで戻れない。「no sessions」と言われる。 **原因**: 「席を離れる」つもりで `exit` を打ち、セッションを閉じてしまった。 **対処**: 消えたセッションは戻せない。次回からは **`Ctrl+b → d`(デタッチ)** を使うこと。 ::: ## 8. 実用テンプレ:SSH 作業を tmux で守る {#template} > **結論**: SSH 直後に `tmux attach || tmux` で入る。離席は `Ctrl+b → d`、復帰は `tmux attach`。 ::: tip **コピペ用:事故らない型** ```bash # 1. サーバへ SSH ssh user@server # 2. 入った直後に tmux で入る(または既存セッションへ復帰) tmux attach || tmux # 3. 普通に作業(長いログ取得・ビルド・バックアップ等) tail -f /var/log/syslog # ... # 4. 席を離れる:Ctrl+b → d でデタッチ # 5. SSH が切れても OK、サーバ側で生き続ける # 6. 戻るときは再 SSH してから tmux attach ``` このパターン 1 つで、SSH 事故の大半が消える。 ::: ## ミニ課題で手を動かそう {#practice} > **結論**: デタッチとアタッチ、ウィンドウ切り替え、ペイン分割の 3 課題を手で動かして覚える。 ::: dialogue @linny: 知識だけ入れても定着しない。実際に手を動かしてみよう。 ::: ### 課題 1: セッションを開いてデタッチし、アタッチで戻る {#challenge-1} **やること**: tmux のセッションを 1 つ開こう。いったん席を離れて、また戻ってこよう。 :::details ヒント1(方向づけ)を見る 「閉じる」ではなく「席を離れる」操作を使う。離れたあとは、セッションが残っているかを一覧で確かめよう。 ::: :::details ヒント2(コマンド名)を見る 開くのは `tmux`。席を離れるのは `Ctrl+b` → `d`。一覧は `tmux ls`。戻るのは `tmux attach`。 ::: :::details 答えを見る ```bash $ tmux # → tmux に入る $ echo "テスト" # Ctrl+b → d でデタッチ $ tmux ls # → セッションが残っていることを確認 $ tmux attach # → さっきの画面が戻る ``` ::: ### 課題 2: ウィンドウを 2 つ作って切り替える {#challenge-2} **やること**: tmux の中でウィンドウをもう 1 つ作ろう。2 つのウィンドウを行き来してみよう。 :::details ヒント1(方向づけ)を見る ウィンドウはブラウザのタブに当たる。まず「作る」操作をして、次に「番号で選ぶ」操作を試そう。 ::: :::details ヒント2(コマンド名)を見る 作るのは `Ctrl+b` → `c`。番号で選ぶのは `Ctrl+b` → `0` や `Ctrl+b` → `1`。順送りは `Ctrl+b` → `n`。 ::: :::details 答えを見る ```bash $ tmux # Ctrl+b c で 1 番ウィンドウ作成 # Ctrl+b 0 で 0 番に戻る # Ctrl+b 1 で 1 番に戻る # Ctrl+b n で次へ ``` ::: ### 課題 3: 1 つのウィンドウを縦横に分ける {#challenge-3} **やること**: 画面を左右に分けよう。さらに上下にも分けて、それぞれで違うコマンドを動かしてみよう。 :::details ヒント1(方向づけ)を見る 分割の記号は 2 つある。キーの形と分割の向きを結びつけて考えよう。移動には矢印キーを使う。 ::: :::details ヒント2(コマンド名)を見る 左右に分けるのは `Ctrl+b` → `%`。上下に分けるのは `Ctrl+b` → `"`。移動は `Ctrl+b` → 矢印キー。 ::: :::details 答えを見る ```bash $ tmux # Ctrl+b % で縦分割 # Ctrl+b " で横分割 # Ctrl+b 矢印キー で移動 # 各ペインで違うコマンドを動かしてみる(例: top と df -h と tail -f /var/log/syslog) ``` ::: ::: dialogue @lina: できました!分けた画面で同時にいくつも見られるの、便利すぎます! @linny: そう、これが見えると tmux が手放せなくなるよ。次は名前付きセッションや設定のカスタマイズに進むといい。 ::: ## 今日の 3 行まとめ {#summary} - **`tmux`** で入って、**`Ctrl+b → d`** で離れ、**`tmux attach`** で戻る。これだけで SSH 切断の事故が消える - **prefix キー(`Ctrl+b`)** のあとに命令キーを押すのが、tmux 操作の基本リズム - **セッション > ウィンドウ > ペイン** の 3 階層を意識すると、並行作業が一気に楽になる ## 次に学ぶこと {#next} ::: dialogue @lina: tmux の基本は分かりました!次は何を学べばいいですか? @linny: 3 方向あるよ。長い処理の監視なら [プロセス管理入門](/articles/tutorials/process-management-basics) で `top` や `kill` を覚えよう。サーバ間のファイル運搬なら [scp / rsync 入門](/articles/tutorials/scp-rsync-basics) だね。ターミナルの基礎が不安なら [pwd・cd・ls の使い方](/articles/tutorials/basic-commands) に戻ってもいいよ。 @lina: tmux のおかげで、作業が消える怖さから解放されました! @linny: SSH を使う仕事をする限り、一生役に立つ道具だよ。毎日触って体に覚えさせよう。 ::: # top と htop 徹底活用 - ボトルネックを見抜く Source: https://penguin-gym-linux.com/articles/tutorials/top-htop-mastery ## この記事で解決できること {#intro} - `top` のサマリ行から **CPU・メモリ・I/O どこが詰まっているか** を 30 秒で判断できる - `%us` `%sy` `%wa` `%si` の **意味と切り分け基準** が分かる - `htop` のツリー表示・フィルタ・F キー操作で **対話的に犯人プロセスを追える** - 「load average が高い」だけで終わらせず、**根本原因の層まで掘れる** ::: tip **結論(30 秒診断の型)** 1. `top` の 3 行目を見る → `%us` 高なら CPU バウンド、`%wa` 高なら I/O 待ち、`%sy` 高ならカーネル/コンテキストスイッチ 2. `top` の 4 行目を見る → `free` が極小かつ `swap used` 増加中ならメモリ不足 3. 犯人特定は `htop` でツリー表示 + ソート(P / M / T キー) ::: ::: warning **前提(対象環境)** - OS:Ubuntu / RHEL 系 Linux - `htop` は標準では未インストールの場合あり(`sudo apt install htop` / `sudo dnf install htop`) - 本記事の表示は procps-ng の `top` を前提(BSD 系 `top` は表示が異なる) ::: ## なぜ top と htop を使い分けるのか? {#why} `top` は **どの環境にも必ずある**(procps-ng 由来でほぼ全 Linux に同梱)。一方 `htop` は **対話性とビジュアル** に優れ、ツリー表示・カラー・マウス操作・複数列ソートを備える。前者は障害現場の最終防衛線、後者は日常の調査効率化、と役割が違う。 ::: highlight **使い分けの基準** - **緊急時 / 最小環境 / SSH のみ** → `top`(バイナリ存在を期待できる) - **日常監視 / 子プロセス含む解析 / 複数プロセス一括 kill** → `htop` ::: ## top の画面はどう読むのか? {#top-screen} `top` 起動直後に表示される 5 行のサマリが最重要。プロセス一覧は二の次。 ```text top - 14:32:11 up 12 days, 3:45, 2 users, load average: 4.21, 3.85, 2.10 Tasks: 234 total, 2 running, 232 sleeping, 0 stopped, 0 zombie %Cpu(s): 12.3 us, 3.2 sy, 0.0 ni, 78.5 id, 5.8 wa, 0.0 hi, 0.2 si, 0.0 st MiB Mem : 16004.0 total, 412.5 free, 12890.3 used, 2701.2 buff/cache MiB Swap: 2048.0 total, 102.3 free, 1945.7 used, 1893.4 avail Mem ``` ### 1 行目: uptime + load average `load average: 4.21, 3.85, 2.10` は **過去 1 / 5 / 15 分の実行待ち + R/D 状態のプロセス数の平均**。CPU コア数と比較する。 - 例: 4 コア機で `4.21` → ほぼフル稼働 - 例: 4 コア機で `8.50` → 慢性的に過負荷 - **1 分 > 15 分** → 負荷上昇中、**1 分 < 15 分** → 沈静化中 ::: warning load average は **R(実行可能)+ D(uninterruptible sleep)** を含む。`%wa` が高い場合 load も上がるため、「load 高 = CPU 不足」とは限らない。 ::: ### 3 行目: CPU 内訳(最も重要) | 列 | 意味 | 異常基準 | | ---- | ---------------------------- | ----------------------------- | | `us` | ユーザープロセス CPU 使用率 | 持続的に 80%+ で CPU バウンド | | `sy` | カーネル CPU 使用率 | 30%+ なら syscall 過多 | | `ni` | nice 値変更されたプロセス | 通常は気にしない | | `id` | アイドル | 低いほど忙しい | | `wa` | I/O 待ち(ディスク・ネット) | 20%+ なら I/O ボトルネック | | `hi` | ハード割込み | 通常 < 1% | | `si` | ソフト割込み | 5%+ ならネット/タイマ多発 | | `st` | 仮想化環境で奪われた時間 | 10%+ なら隣人 VM の影響 | ### 4-5 行目: メモリと Swap - `free` が極小 + `buff/cache` 大 → 健全(カーネルが活用中) - `free` が極小 + `buff/cache` 小 + `swap used` 増加中 → **メモリ不足** - `avail Mem` は「実際に追加プロセスが使える量」の現実的な指標。`free` より参照価値が高い ## CPU ボトルネックはどこで判断するのか? {#cpu} 3 行目 `%us` が **持続的に 80% 以上** で、特定プロセスに集中している状態が CPU バウンド。 ### 切り分け手順 ```bash # 1. top で全体把握 $ top # 2. CPU でソート(top 起動中に Shift + P) # プロセス一覧の %CPU 列が降順になる # 3. 単一プロセスが 100%(コア 1 つ分)超え → そのプロセスが原因 # 複数プロセスに分散 → 全体的な過負荷、スケールアウト検討 ``` ::: tip **top の `1` キー**: コアごとの内訳に切り替わる。シングルスレッド処理が 1 コア占有しているケースを即座に発見できる。 ::: ### `%us` 高だが原因プロセスが見つからない場合 - ユーザープロセスが短命で top の更新間隔(既定 3 秒)に映らない可能性 - `top -d 0.5` で更新を高速化、または `pidstat 1` で 1 秒粒度の観測 ## メモリ不足はどう見抜くのか? {#memory} **`free` が小さい = メモリ不足、ではない**。Linux は空きメモリをページキャッシュとして活用するため、`free` は常に小さく見える。 ### 真のメモリ不足の判定基準 1. `MiB Mem` の `avail Mem` が **総量の 5% 未満** 2. `MiB Swap` の `used` が **増加し続けている** 3. `top` 起動中 `Shift + M` で RES(実メモリ使用)ソート → 単一プロセスが異常に大きい 4. `dmesg | grep -i "killed process"` で OOM Killer の発動跡を確認 ::: warning **Swap 利用 ≠ 即メモリ不足**。低頻度アクセスページが Swap に追い出されるのは正常動作。問題は `swap used` が **継続的に増え続け、I/O wait(%wa)も同時に上がる** 状態(スラッシング)。 ::: ## I/O 待ちはどう特定するのか? {#iowait} `%wa` が 20% 以上で持続する場合、CPU は遊んでいるがディスク/ネット待ちでタスクが進まない状態。 ### 切り分けコマンド ```bash # どのプロセスが D 状態(uninterruptible sleep)か $ ps -eo state,pid,cmd | grep "^D" # ブロックデバイスごとの I/O 量 $ iostat -xz 1 # プロセスごとの I/O(iotop は root 権限必要) $ sudo iotop -o ``` ::: highlight **判定の早見表** | %us | %wa | 状態 | | --- | --- | ------------------------------------ | | 高 | 低 | CPU バウンド(演算過多) | | 低 | 高 | I/O バウンド(ディスク・ネット遅延) | | 高 | 高 | 混在(DB の重いクエリでありがち) | | 低 | 低 | ロック待ち・外部 API 待ち・スリープ | ::: ## htop の便利機能とは? {#htop} `htop` は `top` の上位互換に見えるが、特に強力なのは **ツリー表示・フィルタ・複数プロセス一括操作**。 ### 起動時の表示 ```text 0[|||||||||||||||| 45.2%] Tasks: 87, 234 thr; 3 running 1[||||||||| 22.1%] Load average: 1.85 1.42 1.05 2[||||||||||||||||||||||||||||||||||||| 98.7%] Uptime: 12 days, 03:45:11 3[||| 5.3%] Mem[||||||||||||||||||||| 12.5G/16.0G] Swp[||||| 1.9G/2.0G] ``` コアごとの使用率が **棒グラフで一目** で分かる。コア 2 だけが 98.7% → シングルスレッドプロセスがボトルネックという推測が立つ。 ### よく使う F キー / ショートカット | キー | 機能 | | ----------- | -------------------------------------- | | `F2` | 設定(色・列・表示モード変更) | | `F3` / `/` | プロセス名でインクリメンタル検索 | | `F4` / `\` | プロセス名でフィルタ(一覧を絞り込み) | | `F5` / `t` | ツリー表示の切替(親子関係を可視化) | | `F6` | ソート列の選択 | | `F9` / `k` | シグナル送信(SIGTERM / SIGKILL など) | | `Space` | プロセスを「タグ付け」(複数選択) | | `Shift + P` | CPU % でソート | | `Shift + M` | メモリ % でソート | | `Shift + T` | 起動時間でソート | | `u` | ユーザーで絞り込み | ### ツリー表示が刺さる場面 ```text └─ nginx: master process ├─ nginx: worker process ├─ nginx: worker process └─ nginx: cache manager process ``` `F5` のツリー表示は、**子プロセスが暴走している親を特定** したいときに最強。worker のうち 1 つだけが CPU 食い → 配信中のリクエスト固有の問題、と分かる。 ### 複数プロセスの一括 kill 1. `/` で検索 or `F4` でフィルタして対象を絞る 2. `Space` で目当てのプロセスに次々タグ付け(複数可) 3. `F9` でシグナル選択 → 全タグに一括送信 ::: warning SIGKILL(9)は最後の手段。まず SIGTERM(15)→ 反応なしなら SIGKILL の順を守る。データベースや書き込み中プロセスへの SIGKILL はデータ破損リスクあり。 ::: ## 実践: ボトルネック診断の型 {#workflow} 実際の障害対応で使う 3 ステップフロー。 ### ステップ 1: top のサマリで層を絞る(10 秒) ```bash $ top -b -n 1 | head -5 ``` `-b`(バッチ)+ `-n 1`(1 回)でログ収集にも使える。 - `%wa` 突出 → **I/O 層を疑う**(次は `iostat`) - `%us` 突出 → **アプリ層を疑う**(次は `htop` でプロセス特定) - `%sy` 突出 → **カーネル層を疑う**(次は `strace`, `perf`) - `Swap used` 増加 → **メモリ層を疑う**(次は `Shift + M` ソート) ### ステップ 2: htop で犯人プロセスを特定(30 秒) ```bash $ htop # Shift + P / M / T で関心軸でソート # F5 でツリー表示にして親子関係を確認 ``` ### ステップ 3: 根本原因の調査 - アプリ層 → `strace -p PID -f`, アプリログ - I/O 層 → `iotop -o`, `iostat -x 1` - カーネル層 → `dmesg -T`, `journalctl -k` - メモリ層 → `dmesg | grep -i oom`, アプリのリーク調査 ::: tip **現場テンプレ: 30 秒トリアージ** ```bash # 1. 全体把握 top -b -n 1 | head -20 # 2. load の傾向 uptime # 3. メモリ実状 free -h # 4. I/O 待ち詳細 iostat -xz 1 3 # 5. 犯人特定(対話モード) htop ``` ::: ## やってはいけないこと {#anti-patterns} ::: warning **top / htop でやりがちな失敗** - `load average` だけ見て「CPU 不足」と即断する(`%wa` 由来かも) - `free` の値だけ見て「メモリ不足」と即断する(`avail Mem` を見るべき) - `top` の一瞬の値で判断する(**最低 10 秒は観察**、一過性ピークかを判別) - SIGKILL を最初に使う(プロセスがデータをフラッシュする機会を奪う) - ツリー表示を無視して親プロセスだけ kill する(孤児プロセス化のリスク) ::: ## まとめ {#summary} - `top` の **3 行目(CPU 内訳)** と **4-5 行目(メモリ)** が診断の入口 - `%us` / `%sy` / `%wa` の **どれが高いか** でボトルネック層を切り分ける - `htop` の **ツリー表示・フィルタ・タグ付け** で犯人プロセスを効率特定 - 「30 秒トリアージ → 層特定 → 専用ツール(iostat/strace 等)で深堀り」が実務の型 ## 次に読む {#next} - [ps・top・kill の使い方](/articles/tutorials/process-management-basics) - [Linux プロセス管理の実践](/articles/tutorials/process-management-practical) - [CPU 高負荷の原因切り分け](/articles/troubleshooting/cpu-high-load) - [メモリ不足の調査](/articles/troubleshooting/memory-troubleshooting) - [ディスク I/O の遅延調査](/articles/troubleshooting/disk-io-troubleshooting) # traceroute / mtr 入門 - 経路をたどって遅延区間を特定する Source: https://penguin-gym-linux.com/articles/tutorials/traceroute-mtr ## この記事で解決できること {#intro} - `traceroute` で **宛先までの経路(どのルータを通るか)** を可視化できる - `mtr` で **各ホップのパケットロスと遅延を継続測定** し、どの区間が原因かを切り分けられる - 出力の `* * *` や「途中ホップのロス」を **誤読せず正しく解釈** できる ::: tip **結論(読みの型)** - 経路と到達点を**一度だけ**見たい → `traceroute` - どの区間で**ロス・遅延が起きているか**を見たい → `mtr` - **中間ホップだけのロスは無視**してよい(ICMP レート制限)。**最終ホップまで伝播するロス**が本物 ::: ::: warning **前提(対象環境)** - OS: Ubuntu / 一般的な Linux - `traceroute` / `mtr` は別パッケージ。未導入なら `sudo apt install traceroute mtr-tiny` ::: ## traceroute は何をしているのか? {#how-traceroute} > **結論**: TTL を 1 から順に増やしてパケットを送り、各ルータが返す ICMP Time Exceeded から経路を 1 ホップずつ復元している。 IP パケットには **TTL(Time To Live)** という「あと何台のルータを通過できるか」のカウンタがある。ルータは転送のたびに TTL を 1 減らし、0 になったパケットは破棄して送信元へ **ICMP Time Exceeded** を返す。 traceroute はこれを逆手に取る。 1. TTL=1 のパケットを送る → 1 台目のルータで TTL が 0 になり、そのルータが応答 → **1 ホップ目が判明** 2. TTL=2 を送る → 2 台目が応答 → **2 ホップ目が判明** 3. 宛先に届くまで TTL を増やし続ける 各ホップで既定 3 回プローブを送るため、出力には 3 つの往復時間(RTT)が並ぶ。 ```bash $ traceroute example.com ``` ```output traceroute to example.com (93.184.216.34), 30 hops max, 60 byte packets 1 _gateway (192.168.1.1) 0.512 ms 0.498 ms 0.471 ms 2 10.0.0.1 (10.0.0.1) 4.231 ms 4.118 ms 4.090 ms 3 * * * 4 93.184.216.34 (93.184.216.34) 12.043 ms 11.998 ms 12.110 ms ``` ## traceroute の `* * *` は障害なのか? {#asterisk} > **結論**: 多くの場合は障害ではない。途中ルータが ICMP/UDP 応答を返さない設定なだけで、その先まで到達できていれば経路は正常。 `* * *` は「3 回のプローブすべてに応答がなかった」という意味で、原因は主に次のいずれか。 - そのルータが **TTL 超過への応答を抑制**している(ファイアウォール / ポリシー) - **UDP プローブを遮断**している(Linux traceroute の既定は UDP) - **応答のレート制限**にかかった ::: tip 判定基準: `* * *` の **後のホップが応答していれば経路は通っている**。最終ホップ(宛先)まで `*` が続く場合だけ、その区間より先の到達性を疑う。 ::: UDP が遮断されている疑いがあるなら、プローブ方式を変える。 ```bash # ICMP Echo(ping と同じパケット種別)で試す $ sudo traceroute -I example.com # TCP SYN を 443 番に送る(HTTPS が通る経路なら最も実態に近い) $ sudo traceroute -T -p 443 example.com ``` ::: warning `-I`(ICMP)/ `-T`(TCP)は raw socket を使うため root 権限が必要(`sudo`)。既定の UDP モードは一般ユーザーでも実行できる。 ::: ## traceroute のよく使うオプションは? {#options} > **結論**: 実務でまず覚えるのは `-n`(DNS 解決を省いて高速化)、`-T -p`(TCP で確認)、`-m`(最大ホップ数)の 3 つ。 | オプション | 意味 | | ---------- | --------------------------------------- | | `-n` | IP を名前解決しない(速い・逆引き不要) | | `-I` | ICMP Echo でプローブ | | `-T` | TCP SYN でプローブ(`-p` でポート指定) | | `-p PORT` | 宛先ポート(`-T` と併用、例 `443`) | | `-m N` | 最大ホップ数(既定 30) | | `-q N` | 1 ホップあたりのプローブ数(既定 3) | | `-w SEC` | 応答待ちタイムアウト秒 | ```bash # 数値表示・最大 20 ホップ・各ホップ 1 プローブで素早く $ traceroute -n -m 20 -q 1 example.com ``` DNS の逆引きで待たされて遅く感じるときは、まず `-n` を付ける。 ## mtr は traceroute と何が違うのか? {#mtr-diff} > **結論**: traceroute が「経路の一回スナップショット」なのに対し、mtr は経路上の全ホップへ ping を撃ち続け、ロス率と遅延統計をリアルタイム更新する。 ネットワークの不調は瞬間的に出たり消えたりする。traceroute の一度きりの結果では「たまたま」を拾えない。mtr は traceroute(経路探索)と ping(継続測定)を合体させ、各ホップを連続監視する。 ```bash $ mtr example.com ``` ```output Packets Pings Host Loss% Snt Last Avg Best Wrst StDev 1. _gateway 0.0% 20 0.5 0.6 0.4 1.2 0.2 2. 10.0.0.1 0.0% 20 4.2 4.3 4.0 6.1 0.5 3. 203.0.113.1 10.0% 20 18.0 19.4 17.2 41.0 5.1 4. 93.184.216.34 0.0% 20 12.0 12.1 11.9 12.4 0.1 ``` 各列の意味は次のとおり。 - **Loss%**: そのホップで応答が返らなかった割合 - **Snt**: 送ったプローブ数 - **Last / Avg / Best / Wrst**: 直近 / 平均 / 最小 / 最大の RTT(ms) - **StDev**: RTT のばらつき(大きいほど不安定) ## mtr のロスはどう読むのか? {#read-loss} > **結論**: 中間ホップだけにロスが出て最終ホップで 0% に戻るなら、それは ICMP レート制限による偽のロス。最終ホップまで続くロスだけが本物の障害。 これが mtr 最大の落とし穴であり、最重要ポイント。上の出力例ではホップ 3 が `10.0%` のロスを示すが、**最終ホップ 4 は 0.0%** に戻っている。 ルータは「自分宛ての TTL 超過応答」を**低優先度で処理・抑制**することがある。つまりホップ 3 の `10.0%` は「ホップ 3 のルータが応答を間引いただけ」で、**通信そのものは素通りしている**。 ::: danger **判定ルール** - 中間ホップのロス → **最終ホップに伝播していなければ無視**(ICMP レート制限の偽陽性) - 最終ホップ(宛先)のロス → **本物のパケットロス**。その手前で初めてロスが立ち上がる区間が原因箇所 ::: つまり「ロスがどのホップから**始まり、最後まで続くか**」を見る。途中で消えるロスは捨てる。 ## 遅延区間はどう特定するのか? {#read-latency} > **結論**: Avg が大きく跳ね上がり、その値が以降のホップでも維持されていれば、跳ねた区間が遅延の発生源。一瞬跳ねて戻るのは応答処理の遅れで実害なし。 遅延もロスと同じ読み方をする。 - あるホップで **Avg が急増し、それ以降のホップでも高いまま** → その区間(直前ホップ→当該ホップ間)が真の遅延源 - あるホップだけ **Wrst だけが突出**して Avg は低い → そのルータが応答を後回しにしただけ。実通信の遅延ではない ```bash # レポートモード: 10 サイクル送って結果を確定出力(ログ共有・チケット添付向け) $ mtr -rwzbc 10 example.com ``` オプションの意味: - `-r` / `--report`: 対話画面ではなく集計結果を 1 回出力 - `-w`: ワイド表示(ホスト名を省略しない) - `-z`: 各ホップの AS 番号を表示(どの事業者の網かが分かる) - `-b`: ホスト名と IP の両方を表示 - `-c N`: 送信サイクル数(既定 10) - `-n`: 名前解決しない(速い) ::: tip 障害報告を ISP やインフラ担当に送るときは `mtr -rwzbc 100 宛先` のように **サイクル数を増やした report 出力**を添えると、ロス率の信頼性が上がり話が早い。 ::: ## traceroute と mtr の使い分けまとめ {#summary} > **結論**: 経路を一度確認するだけなら traceroute、どの区間でロス・遅延が起きているかを継続的に切り分けるなら mtr。報告には mtr のレポート出力を添える。 | 状況 | 使うもの | | ------------------------------------ | ---------------------- | | 宛先までの経路を一度だけ見たい | `traceroute -n` | | UDP が遮断され `* * *` が続く | `traceroute -T -p 443` | | どの区間でロス・遅延か継続測定したい | `mtr 宛先` | | 障害報告として結果を共有したい | `mtr -rwzbc 100 宛先` | ::: warning **やりがちな誤読** - 中間ホップの `* * *` を「障害」と即断する - 中間ホップのロスを真に受ける(最終ホップで 0% に戻る偽陽性) - 一度の traceroute だけで「問題なし」と結論づける ::: ## 次に読む {#next} - [ネットワークコマンド入門 - ip/ifconfig で接続状態を確認する](/articles/tutorials/network-commands-basics) - [ネットワーク障害対応の実践 - ping/traceroute/dig を使った切り分け](/articles/tutorials/network-troubleshooting-practical) - [netstat / ss でポートと接続を確認する](/articles/tutorials/netstat-ss-basics) # trap 入門 - シグナルを捕捉してクリーンアップ処理を書く Source: https://penguin-gym-linux.com/articles/tutorials/trap-signal-handling ## この記事で解決できること {#intro} - `trap` で **シグナルを捕捉** し、確実に後始末を走らせる型が分かる - 「Ctrl-C で中断したら一時ファイルが残った」という **典型的な事故を防ぐ** 方法が身につく - `EXIT` 疑似シグナル・`cleanup` 関数・`set -e` を組み合わせた **実務テンプレ** が手に入る ::: tip **結論(実務の型)** - 後始末は **`trap cleanup EXIT` 一本**に寄せる(どんな終わり方でも走る) - 一時ファイルは `mktemp` で作り、生成直後に `trap` を仕掛ける - `SIGKILL`(9)と `SIGSTOP` は **捕捉できない**。それ以外で対処する ::: ::: warning **前提(対象環境)** - シェル:bash(POSIX sh でも `EXIT` / `INT` / `TERM` は共通で動作) - 対象:シェルスクリプトを書く中級者 ::: ## trap とは何か? {#what} > **結論**: `trap` はシグナルや特殊イベントを受け取ったときに実行するコマンドを登録する bash 組み込みコマンド。後始末の予約に使う。 `trap` は「特定のシグナルを受け取ったら、このコマンドを実行する」という **割り込みハンドラを登録する** 組み込みコマンド。基本構文は次の形。 ```bash trap 'コマンド' シグナル名... ``` たとえば Ctrl-C(`SIGINT`)を捕まえてメッセージを出す例。 ```bash trap 'echo "中断されました"' INT ``` シグナル名は `SIGINT` でも `INT` でも数値 `2` でも指定できる。**`SIG` プレフィックスは省略可能**で、移植性の観点からは名前(`INT` / `TERM` / `EXIT`)での指定が推奨される。 ::: tip 第 1 引数はシェルが評価する **文字列**。シングルクォートで囲むと「シグナルを受け取った時点」で展開される。ダブルクォートにすると `trap` を仕掛けた時点で変数が展開されるため、後始末用の変数を埋め込むなら挙動の違いに注意する。 ::: ## なぜ EXIT 疑似シグナルを使うのか? {#exit} > **結論**: `EXIT` はスクリプトが終了する瞬間に必ず走る疑似シグナル。正常終了・エラー終了・Ctrl-C のどれでも実行されるため、後始末はここに集約するのが最も堅い。 実際のシグナル(`INT` / `TERM`)を一つずつ捕まえると、捕まえ漏れたシグナルで後始末がスキップされる。`EXIT` は **シグナルではなく「スクリプトが終わる」というイベント** に反応するため、終わり方を問わず一度だけ実行される。 ```bash #!/usr/bin/env bash tmpfile=$(mktemp) trap 'rm -f "$tmpfile"' EXIT # ここで何が起きても(正常終了 / set -e のエラー終了 / Ctrl-C) # スクリプト終了時に tmpfile は必ず削除される echo "作業中..." > "$tmpfile" cat "$tmpfile" ``` ::: warning `EXIT` トラップは **`SIGKILL`(kill -9)では走らない**。`SIGKILL` はプロセスを即座に強制終了するためハンドラを実行する余地がない。これは `trap` の限界として理解しておく。 ::: ## どうやってクリーンアップ関数を書くのか? {#cleanup} > **結論**: 後始末を `cleanup` 関数にまとめ、`trap cleanup EXIT` で登録する。複数の一時リソースがあっても 1 箇所で管理できる。 インラインのコマンド文字列は短い処理なら良いが、削除対象が増えると可読性が落ちる。**関数に切り出す** のが実務の定番。 ```bash #!/usr/bin/env bash set -euo pipefail workdir=$(mktemp -d) lockfile="/tmp/myjob.lock" cleanup() { local rc=$? # 直前の終了コードを保存 rm -rf "$workdir" rm -f "$lockfile" echo "クリーンアップ完了 (exit=$rc)" exit "$rc" # 元の終了コードで終わる } trap cleanup EXIT # --- 本処理 --- touch "$lockfile" echo "data" > "$workdir/output.txt" ``` ポイントは関数冒頭の `local rc=$?`。`cleanup` 内で別のコマンドを実行すると `$?` が上書きされるため、**最初に終了コードを退避** し、最後に `exit "$rc"` で元のコードを返す。これで「後始末はするが、エラーはエラーとして呼び出し元に伝える」挙動になる。 ::: tip `mktemp -d` で作業ディレクトリを作り、`rm -rf "$workdir"` でまとめて消す型は、個別の一時ファイルを追跡するより安全。`workdir` が空文字だと `rm -rf` が暴発するため、`set -u` で未定義変数をエラーにしておくと事故を防げる。 ::: ## 複数のシグナルを使い分けるには? {#signals} > **結論**: 後始末は `EXIT` に集約し、中断時の挙動を変えたい場合だけ `INT` / `TERM` を個別に捕捉する。捕捉できないシグナルもある。 主要なシグナルと用途を整理する。 | シグナル | 番号 | 送られる契機 | 捕捉 | | -------- | ---- | ---------------------- | ---- | | `INT` | 2 | Ctrl-C | 可 | | `TERM` | 15 | `kill` の既定 | 可 | | `HUP` | 1 | 端末切断 | 可 | | `QUIT` | 3 | Ctrl-\\ | 可 | | `EXIT` | 0 | スクリプト終了(疑似) | 可 | | `KILL` | 9 | `kill -9` | 不可 | | `STOP` | 19 | プロセス一時停止 | 不可 | `INT` を受けたときだけ独自メッセージを出し、後始末自体は `EXIT` に任せる例。 ```bash #!/usr/bin/env bash tmpfile=$(mktemp) trap 'rm -f "$tmpfile"' EXIT trap 'echo "ユーザーにより中断されました"; exit 130' INT echo "処理中... Ctrl-C で中断できます" sleep 30 ``` `INT` ハンドラ内の `exit 130` でスクリプトを終わらせると、続けて `EXIT` トラップが走って `tmpfile` が消える。慣例として **`INT` での終了コードは `128 + 2 = 130`** を使う。 ::: warning `SIGKILL`(9)と `SIGSTOP`(19)は OS 仕様で捕捉・無視・再定義のいずれも不可能。「`kill -9` でも掃除したい」という要求は `trap` では満たせない。別プロセスの監視や systemd の `ExecStopPost=` 等、外側の仕組みで対処する。 ::: ## set -e と組み合わせて安全にするには? {#set-e} > **結論**: `set -e`(エラー即終了)と `trap` は相性が良い。途中でコマンドが失敗してもトラップが後始末を実行するため、中途半端な状態を残さない。 `set -e` を付けると、コマンドが失敗した時点でスクリプトが終了する。このとき `EXIT` トラップは走るので、後始末は保証される。さらに `ERR` 疑似シグナル(bash 拡張)を使うと、**どのコマンドで失敗したか** を記録できる。 ```bash #!/usr/bin/env bash set -euo pipefail workdir=$(mktemp -d) trap 'rm -rf "$workdir"' EXIT trap 'echo "エラー: 行 $LINENO で失敗 (exit=$?)" >&2' ERR cp /etc/hostname "$workdir/" grep "存在しない文字列" "$workdir/hostname" # ここで失敗 → ERR と EXIT が走る echo "ここには到達しない" ``` `ERR` トラップで失敗を通知し、`EXIT` トラップで後始末する二段構えが、防御的スクリプトの基本形。 ::: tip `set -o pipefail` を併用すると、パイプライン途中の失敗も検出できる。`set -euo pipefail` + `trap cleanup EXIT` はシェルスクリプトの堅牢化テンプレートとして覚えておくと良い。 ::: ## trap の状態を確認・解除するには? {#manage} > **結論**: `trap -p` で設定中のトラップを一覧表示し、`trap - シグナル名` で解除する。デバッグ時に役立つ。 現在仕掛けられているトラップは `trap -p` で確認できる。 ```bash $ trap 'rm -f /tmp/x' EXIT $ trap -p trap -- 'rm -f /tmp/x' EXIT ``` 特定のシグナルのトラップを **解除** するには、コマンド部分を `-` にする。 ```bash trap - EXIT # EXIT トラップを解除(既定動作に戻す) ``` シグナルを **無視** したい(届いても何もしない)場合は、コマンド部分を空文字にする。 ```bash trap '' INT # Ctrl-C を無効化する ``` ::: warning `trap '' INT` で無視に設定すると、その状態は子プロセスにも継承される。スクリプトの一部だけ Ctrl-C を無効化したいなら、クリティカルセクションを抜けた後に `trap - INT` で元に戻すこと。 ::: ## まとめ:trap 安全テンプレート {#summary} > **結論**: `mktemp` で一時リソースを作り、直後に `trap cleanup EXIT` を仕掛け、`cleanup` 関数で終了コードを保存してから後始末する。これが事故らない型。 ::: tip **コピペ用:堅牢なスクリプトの骨格** ```bash #!/usr/bin/env bash set -euo pipefail workdir=$(mktemp -d) cleanup() { local rc=$? rm -rf "$workdir" exit "$rc" } trap cleanup EXIT trap 'echo "中断" >&2; exit 130' INT # --- ここに本処理を書く --- echo "scratch" > "$workdir/tmp.txt" ``` ::: 押さえるべき要点は 3 つ。 - 後始末は **`EXIT` に集約**(正常・異常・中断のすべてで走る) - 終了コードは `cleanup` 冒頭で **`local rc=$?` 退避**し、最後に `exit "$rc"` - `SIGKILL` / `SIGSTOP` は捕捉不能。それを前提に設計する 次のステップとして、`trap` を使った実用スクリプト(ロックファイル管理・定期ジョブ)を [getopts 入門](/articles/tutorials/getopts-argument-parsing) や [cron の基本](/articles/tutorials/cron-basics) と組み合わせて書いてみると理解が深まる。 ## 次に読む {#next} - [getopts でオプション引数を処理する](/articles/tutorials/getopts-argument-parsing) - [ヒアドキュメントで複数行を扱う](/articles/tutorials/heredoc-basics) - [プロセス管理とシグナルの基本](/articles/tutorials/process-management-basics) # tree コマンド入門 - ディレクトリ構造をツリー表示する Source: https://penguin-gym-linux.com/articles/tutorials/tree-command ## この記事で解決できること {#intro-list} - `tree` で **ディレクトリ構造を一目で把握** できるようになります - `-L` で **深さを制限** して、巨大なフォルダでも見やすく表示できます - `-d` `-a` `-I` で **必要なものだけ** を絞り込めます - `ls` の繰り返しから **構造の可視化** に切り替えられます **対象読者**:Linux 入門者。`ls` と `cd` でフォルダの中を行き来するのに疲れた方。 ::: tip **言葉の整理** - **ディレクトリ**:Windows や Mac でいうフォルダのことです。「フォルダ」「ディレクトリ」は同じものを指します。 - **階層**:フォルダの中にフォルダがある入れ子の深さのことです。「レベル」とも呼びます。 - **隠しファイル**:名前が `.` から始まるファイルです。既定では一覧に出ません。「ドットファイル」とも呼びます。 - **オプション**:コマンドの後ろに付ける `-L` のような指定です。動きを変えるための指示です。 - **カレントディレクトリ**:いま自分がいるディレクトリのことです。「現在地」と読み替えて構いません。 - **シェル**:ターミナルであなたの入力を受け取るプログラムです。「シェル」「ターミナル」「コンソール」はほぼ同じものを指す別の呼び方です。 - **クォート**:`'` で文字列を囲むことです。囲むと、記号がそのままの文字として渡ります。 `tree` は表示するだけのコマンドです。ファイルを消したり書きかえたりしません。何度実行してもフォルダの中身は変わりません。安心して試してください。 ::: ## 導入:リナの「ls 迷子」事件 {#intro} ::: dialogue @lina: ライニー先輩、プロジェクトのフォルダの構造を確認したいです。 @lina: でも `ls` して `cd` して、また `ls` して、の繰り返しです。全体像が頭に入ってきません。 @linny: いわゆる「`ls` 迷子」だね。一段ずつ潜って戻ってを繰り返すと、いま自分がどこにいるか分からなくなるよね。 @lina: そうなんです。紙に書き出したくなります。 @linny: その「紙に書き出した図」を一発で出してくれるのが `tree` コマンドだよ。ディレクトリの中身を **枝分かれの図** で表示してくれるんだ。 ::: ::: tip **結論を先に** - `tree` = ディレクトリ構造を **ツリー(枝分かれ図)で可視化** するコマンド - そのまま `tree` で現在地以下を表示、`tree パス` で場所を指定 - 巨大フォルダは `tree -L 2`(深さ制限)と `tree -I 'node_modules'`(除外)が必須 ::: ## 1. tree とは何か? {#what} > **結論**: `tree` はディレクトリの中身を枝分かれの図で表示するコマンド。`ls` を何度も叩かなくても階層構造が一目で分かる。 ::: dialogue @lina: `tree` は `ls` と何が違うんですか? @linny: `ls` は **その場所の中身だけ** を一覧にするね。`tree` は **その下の階層をすべて辿って**、枝分かれの図で見せてくれるんだ。 @lina: つまり `ls` を全部のフォルダで自動的に実行してくれる、ということですか。 @linny: そのイメージで合っているよ。しかも親子関係が線でつながって見える。 @linny: だから「この設定ファイルはどのフォルダの下にあるか」がすぐに分かるんだ。 ::: 実際に表示すると、次のようになります。 ```output . ├── README.md ├── src │ ├── index.js │ └── utils │ └── format.js └── tests └── index.test.js 3 directories, 4 files ``` ::: tip **図の読み方** - `├──` `└──`:その階層にあるファイルやフォルダです。`└──` は最後の 1 つを表します - `│`:枝が下に続いていることを示す縦線です - 字下げの深さ:そのまま階層の深さを表します - 最後の行:**ディレクトリ数とファイル数の合計** です ::: ## 2. インストール {#install} > **結論**: `tree` は標準では入っていないことが多い。`apt` / `dnf` でインストールする。 ::: dialogue @lina: さっそく使いたいです。どうやって入れるんですか? @linny: ディストリビューションによっては、最初から入っていないことがあるんだ。 @linny: `tree` と打って「command not found」が出たら、インストールしよう。 ::: ```bash # Ubuntu / Debian 系 $ sudo apt install tree # RHEL / Fedora 系 $ sudo dnf install tree # macOS(Homebrew) $ brew install tree ``` ::: tip パッケージ名は、どのディストリビューションでも `tree` で共通です。インストールが終わったら `tree --version` でバージョンを確認できます。 ::: ## 3. 基本の使い方 {#basic} > **結論**: 引数なしの `tree` でカレントディレクトリ以下を表示。`tree パス` で対象の場所を指定する。 ::: dialogue @linny: 使い方はとても簡単だよ。まずは何も付けずに実行してみよう。 ::: ### カレントディレクトリを表示 {#basic-cwd} ```bash # いまいる場所の中身をツリー表示 $ tree ``` ### 場所を指定して表示 {#basic-path} ```bash # 指定したディレクトリを表示 $ tree /etc # ホームディレクトリを表示 $ tree ~ ``` ::: dialogue @lina: `tree` と打っただけで、フォルダの奥の奥まで全部出てきました。 ::: ### リナの失敗:画面が流れて止まらない {#failure} ::: dialogue @lina: プロジェクトのフォルダで `tree` を実行しました。画面が滝のように流れて、最後の行しか残っていません。 ::: ```bash $ tree ``` ```output (数万行が流れたあと) 12043 directories, 98217 files ``` ::: dialogue @lina: えっ、9 万ファイル以上ありました。私のプロジェクトはそんなに大きくないはずです。壊れたんでしょうか。 @linny: 壊れてないよ。`tree` は **既定で一番下の階層まで全部** 辿るんだ。だから `node_modules` のような巨大なフォルダも、中身を 1 つ残らず表示してしまう。 @lina: 見たかったのは自分の書いたファイルだけです。数のほとんどはライブラリでした。 @linny: そう。だから深さを `-L` で制限して、不要なフォルダを `-I` で外す。この 2 つを覚えれば、いつでも読める量に収まるよ。 @lina: 納得しました。まず `-L 2` で全体を見てから、深掘りします。 ::: ```bash $ tree -L 2 -I 'node_modules|.git' ``` ::: tip 流れてしまった画面は `tree | less` と書くと、1 画面ずつ止めて読めます。`less` の中では矢印キーで動き、`q` で終了します。 ::: ## 4. 深さを制限する(-L) {#depth} > **結論**: `tree -L 数字` で表示する階層の深さを制限できる。巨大なフォルダはまず `-L 1` や `-L 2` で全体像を掴む。 ::: dialogue @lina: さっきのように出しすぎないようにするには、どうしたらいいですか? @linny: `-L`(Level)を使うよ。`-L 2` なら **2 階層目まで** しか表示しない。 @linny: これなら全体像だけを短時間で確認できるね。 ::: ```bash # 1階層だけ(直下の中身のみ、ls に近い) $ tree -L 1 # 2階層まで $ tree -L 2 /var/log ``` ```output /var/log ├── apt │ ├── history.log │ └── term.log ├── journal ├── syslog └── dpkg.log 2 directories, 4 files ``` ::: tip **`-L` の使いどころ** - 初めて見るフォルダは `tree -L 1` から始めます。次に `-L 2` と **浅いところから** 深掘りします - 深さは 1 以上の整数で指定します(`-L 0` はエラーになります) - 全体の地図が欲しいときは浅く指定します。特定フォルダの詳細を見たいときは深く指定します ::: ## 5. 表示するものを絞り込む {#filter} > **結論**: `-d` でディレクトリのみ、`-a` で隠しファイルも表示、`-I` で不要なフォルダを除外できる。 ::: dialogue @linny: 深さの次は「何を表示するか」を絞る方法だね。これを覚えると `tree` が一気に実用的になるよ。 ::: ### ディレクトリだけ表示(-d) {#filter-d} ```bash # フォルダ構造だけを把握したいとき $ tree -d -L 2 ``` ::: dialogue @lina: ファイルが多すぎるとき、フォルダの骨組みだけを見たいです。そのときに便利そうですね。 @linny: そのとおり。`-d`(directory)はファイルを全部隠すよ。**フォルダの骨格だけ** を見せてくれるんだ。 @linny: プロジェクトの構成を説明するときに役立つよ。 ::: ### 隠しファイルも表示(-a) {#filter-a} ```bash # .git や .env などドット始まりも表示 $ tree -a -L 1 ``` ::: warning **`tree` はデフォルトで隠しファイルを表示しない** `.bashrc` や `.git` のような **ドット(.)で始まるファイル** は、`-a`(all)を付けないと表示されません。 「設定ファイルが見当たらない」ときは、`-a` を付け忘れていないか確認してください。 ::: ### 不要なフォルダを除外(-I) {#filter-i} ```bash # node_modules を除外して表示 $ tree -I 'node_modules' # 複数を | で区切って除外 $ tree -I 'node_modules|.git|dist' ``` ::: dialogue @lina: `node_modules` は中身が何万ファイルもありますよね。さっき画面が流れた原因もこれでした。 @linny: そう。だから `-I`(Ignore)で除外するのが定番だよ。 @linny: パターンは `|`(パイプ)で区切れば複数を指定できる。`node_modules` `.git` `dist` あたりは除外の常連だね。 ::: ::: tip **逆に「これだけ見たい」なら `-P`** 特定のパターンだけを表示したいときは `-P`(Pattern)を使います。`tree -P '*.js'` と書けば `.js` ファイルだけが残ります。 空のフォルダが邪魔なときは `--prune` を一緒に付けてください。該当ファイルを含まないフォルダが消えて見やすくなります。 ::: ## 6. 見やすく・サイズ付きで表示する {#display} > **結論**: `-F` で種類の記号付き、`-h --du` でディレクトリの合計サイズ付き、`-o ファイル` でファイルに保存できる。 ::: dialogue @lina: もう少し情報を足したいときはありますか? @linny: あるよ。ファイルの種類やサイズを一緒に出すと、調査がぐっと楽になるんだ。 ::: ### 種類が分かる記号を付ける(-F) {#display-f} ```bash # ディレクトリに / 、実行ファイルに * を付ける $ tree -F -L 1 ``` `ls -F` と同じ記号が付きます。末尾が `/` ならフォルダ、`*` なら実行可能ファイルです。一目で見分けられます。 ### サイズを表示する(-h / --du) {#display-size} ```bash # 各ファイルのサイズを人間が読みやすい単位で $ tree -h # ディレクトリのサイズ(配下の合計)も表示 $ tree -h --du -L 1 ``` ::: tip **`--du` の意味** `--du` は各ディレクトリのサイズを **配下にあるファイルの合計** として表示します。`du` コマンドと同じ考え方です。 `-h`(human readable)と組み合わせると `1.2K` `3.4M` のような読みやすい単位になります。どのフォルダが容量を使っているかを、おおまかに知りたいときに便利です。 ::: ::: dialogue @lina: サイズが見られるなら、`ncdu` のように容量調査にも使えますか? @linny: その時点の記録としては使えるよ。ただし矢印キーで掘り下げたり、その場で消したりはできない。 @linny: そういう **対話的な調査** は [ncdu](/articles/tutorials/ncdu-disk-usage) の方が向いている。`tree` は構造を図で残す道具、`ncdu` は容量を探して掃除する道具、と考えるといいよ。 ::: ### 結果をファイルに保存(-o) {#display-output} ```bash # ツリーをテキストファイルに書き出す $ tree -L 2 -o structure.txt ``` ::: tip README に「ディレクトリ構成」を載せるときに便利です。`tree -L 2` の出力をそのまま貼れば、構成図が一瞬で作れます。 不要なものは `-I` で除外してから貼ってください。読みやすい図になります。 なお `-o` は指定した名前のファイルを新しく作り、そこに結果を書き込みます。同じ名前のファイルがある場合は中身が置きかわります。実行する前に `ls` で同名ファイルがないか確認してください。 ::: ## 7. ミニ課題:自分の環境で試してみよう {#exercise} > **結論**: 深さ制限・ディレクトリのみ・除外指定の3問で `tree` の実用オプションを定着させる。 ::: dialogue @lina: 知識は入りました。手を動かして試したいです。 @linny: いいね、3 問用意したよ。`tree` は表示するだけだから、何度実行しても大丈夫だよ。 ::: **課題 1**: ホームディレクトリの直下に何があるかを、1 階層だけ表示して確かめよう。 :::details ヒント 1(方向づけ)を見る 深さを制限するオプションを使います。今回は「直下だけ」なので、いちばん浅い指定にします。 英語の Level が手がかりです。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `tree` です。深さの制限は `-L` に数字を続けて書きます。今回は `1` です。 ホームディレクトリは `~` と書けます。 ::: :::details 答えを見る ```bash $ tree -L 1 ~ ``` ```output /home/user ├── Documents ├── Downloads ├── Videos └── notes.txt 3 directories, 1 file ``` `-L 1` に制限すると、`ls` と同じ範囲がツリーの形で並びます。 ::: **課題 2**: ホーム以下のフォルダの骨組みだけを、2 階層まで表示しよう。ファイルは出さないようにします。 :::details ヒント 1(方向づけ)を見る 2 つのオプションを組み合わせます。1 つは深さの制限です。もう 1 つはファイルを隠す指定です。 ファイルを隠す方は、英語の directory が手がかりです。 ::: :::details ヒント 2(コマンド名)を見る 深さは `-L 2` です。ディレクトリだけにするのは `-d` です。2 つとも `tree` の後ろに並べて書けます。 ::: :::details 答えを見る ```bash $ tree -d -L 2 ~ ``` ```output /home/user ├── Documents │ └── work ├── Downloads └── Videos 4 directories ``` `-d` でファイルが消え、`-L 2` で深さが 2 階層に収まります。最後の行もディレクトリ数だけになります。 ::: **課題 3**: プロジェクトのフォルダで `.git` を除外して表示し、消えたことを確かめよう。 :::details ヒント 1(方向づけ)を見る 見せたくないフォルダの名前を、コマンドに伝えます。深さの制限とは別のオプションです。 英語の Ignore が手がかりです。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `tree -I` です。`-I` の後ろに、除外したい名前をクォートで囲んで書きます。 囲まずに書くと、`|` がシェルのパイプと解釈されます。 ::: :::details 答えを見る ```bash $ tree -a -L 1 -I '.git' ``` ```output . ├── README.md ├── src └── tests 2 directories, 1 file ``` `-a` を付けても `.git` は出てきません。除外が効いている証拠です。 複数を除外したいときは `'.git|node_modules'` のように `|` で区切ります。 ::: ## 8. よくある落とし穴 {#pitfalls} > **結論**: 深さ無制限で巨大フォルダを叩く、隠しファイルの表示忘れ、除外パターンのクォート忘れに注意。 ::: warning **やりがちな失敗 3 つ** 1. **大きいフォルダで `-L` なしで実行する** → 画面が滝のように流れます。まず `-L 1` か `-L 2` から始めます 2. **隠しファイルが見えず焦る** → `tree` は既定で `.` 始まりを隠します。`-a` の付け忘れを確認します 3. **除外パターンをクォートし忘れる** → `|` がシェルのパイプと解釈されます。`-I 'node_modules|.git'` のように囲みます ::: ::: tip **安全で使いやすい型** - 初めて見るフォルダ:`tree -L 2`(浅く全体像を見ます) - 構成を説明したい:`tree -d -L 2`(骨組みだけを出します) - リポジトリを見る:`tree -L 2 -I 'node_modules|.git|dist'`(不要なものを除外します) ::: ## 9. 振り返り {#review} ::: dialogue @lina: 整理します。`tree` は `ls` と違って、下の階層まで辿って図にしてくれるんですね。 @linny: そのとおり。だから既定のままだと、巨大なフォルダで画面が流れてしまう。 @lina: そこで `-L` で深さを止めて、`-I` で不要なフォルダを外すんでした。 @linny: よく覚えていたね。初めて見るフォルダは `-L 2` から。この 1 手で読める量に収まるよ。 ::: ## 今日の 3 行まとめ {#three-lines} 1. `tree` はディレクトリ構造を枝分かれの図で表示する。`ls` の繰り返しが 1 回で済む 2. `-L 数字` で深さを制限する。初めて見るフォルダは `-L 2` から始める 3. `-d` はフォルダだけ、`-a` は隠しファイルも表示、`-I 'パターン'` は除外に使う ## 次に読む {#next} - [cp・mv・rm の使い方 - ファイル操作の基本](/articles/tutorials/file-operations-basics) - [グロブ(ワイルドカード)入門](/articles/tutorials/globbing-wildcards) - [ncdu 入門 - 対話的にディスク使用量を調べる](/articles/tutorials/ncdu-disk-usage) # ulimit 入門 - プロセスのリソース上限を理解する Source: https://penguin-gym-linux.com/articles/tutorials/ulimit-resource-limits ## ulimit とは? {#what} > **結論**: `ulimit` は shell とそこから起動するプロセスが使えるリソース量(開けるファイル数・プロセス数・メモリ等)の上限を確認・変更するコマンド。 `ulimit` は bash 等の shell に組み込まれたコマンドで、カーネルの `getrlimit(2)` / `setrlimit(2)` を呼び出してリソース上限(resource limit)を操作する。上限は「1 プロセスあたり」に適用され、`fork` した子プロセスへ継承される。 代表的な用途は次の 3 つ。 - 「Too many open files」のような上限到達エラーの**原因切り分け** - DB / Web サーバ等の**同時接続数に見合う上限の引き上げ** - 暴走スクリプトによる資源枯渇を防ぐ**上限の引き下げ** ::: warning **前提(対象環境)** - bash を前提(`ulimit` は shell ビルトイン。`zsh` 等でも利用可だがオプションが一部異なる) - 永続化の節は systemd ベースのディストリ(Ubuntu / RHEL 系)を想定 ::: ## ソフトリミットとハードリミットの違いは? {#soft-hard} > **結論**: ソフトは実際に効く上限、ハードはソフトの天井。非特権ユーザーはソフトをハードまで上げられるが、ハードを引き上げるには root 権限が必要。 各リソースには 2 つの値がある。 | 種別 | 意味 | 非特権ユーザーの変更可否 | | -------------- | -------------------------- | -------------------------------- | | ソフト(soft) | 実際に強制される現在の上限 | ハード以下なら自由に増減できる | | ハード(hard) | ソフトが超えられない天井 | **下げる方向のみ**(上げは不可) | ハードを一度下げると同一プロセス内では戻せない(戻すには root 権限 = `CAP_SYS_RESOURCE` が必要)。`ulimit` ではフラグで対象を切り替える。 ```bash $ ulimit -Sn # ソフトの open files 上限を表示 $ ulimit -Hn # ハードの open files 上限を表示 ``` - `-S`:ソフトリミットを対象 - `-H`:ハードリミットを対象 - 省略時:**表示はソフト**、**設定は両方**を同時に変更する点に注意 ## 現在の上限を確認するには? {#check} > **結論**: `ulimit -a` で全リソースの現在のソフト上限を一覧表示できる。個別に見るときは `-n`(ファイル)等の単独フラグを使う。 ```bash $ ulimit -a ``` ```output real-time non-blocking time (microseconds, -R) unlimited core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 15363 max locked memory (kbytes, -l) 8192 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 stack size (kbytes, -s) 8192 max user processes (-u) 15363 virtual memory (kbytes, -v) unlimited ``` `-a` はソフト値を表示する。ハード値をまとめて見たいときは `ulimit -aH` を使う。 ```bash $ ulimit -aH # 全リソースのハード上限 ``` ## 主なリソース種別の一覧 {#resources} > **結論**: 実務で触る頻度が高いのは `-n`(open files)・`-u`(プロセス数)・`-c`(core)・`-s`(stack)の 4 つ。 | フラグ | リソース | 典型的な用途・症状 | | ------ | ------------------ | ------------------------------------------------- | | `-n` | open files | 「Too many open files」。FD 数の上限 | | `-u` | max user processes | 「fork: retry: Resource temporarily unavailable」 | | `-c` | core file size | クラッシュ時の core dump 出力可否(`0` で無効) | | `-f` | file size | 1 ファイルあたりの最大サイズ | | `-s` | stack size | 深い再帰での segfault 調査 | | `-v` | virtual memory | プロセスの仮想メモリ総量の上限 | | `-l` | max locked memory | `mlock` できるメモリ量(DB 等で関係) | ::: tip `-u`(max user processes)はそのユーザー全体のプロセス数に対する上限。一見「このシェルだけ」に見えて、同一 UID の全プロセスを合算してカウントする点に注意する。 ::: ## 上限を一時的に変更するには? {#temporary} > **結論**: `ulimit -Sn 4096` のように実行すると、その shell と以降に起動する子プロセスにだけ反映される。shell を閉じると元に戻る。 ```bash # ソフトの open files をハードの範囲内で 4096 に引き上げ $ ulimit -Sn 4096 # 確認 $ ulimit -Sn 4096 ``` ハードを超える値を指定すると拒否される。 ```bash $ ulimit -Sn 999999 bash: ulimit: open files: cannot modify limit: Operation not permitted ``` この場合はハードの引き上げが必要で、非特権ユーザーでは不可。ハードごと上げるには root で次のように実行する(root は両方変更できる)。 ```bash # root のみ。ソフト/ハードを同時に 65536 へ $ sudo bash -c 'ulimit -n 65536; exec your-server' ``` ::: warning `ulimit` の効果は**それを実行した shell とその子孫**にしか及ばない。既に動いているデーモンの上限は変わらない。稼働中プロセスへの適用は [prlimit](#running) を使う。 ::: ## 上限を永続化するには? {#persist} > **結論**: ログインセッションには `/etc/security/limits.d/*.conf`、systemd 管理のサービスには unit の `LimitNOFILE=` を使う。両者は適用経路が別物。 ### ログインシェル(pam_limits 経由) `/etc/security/limits.conf` および `/etc/security/limits.d/*.conf` は、PAM の `pam_limits` モジュールがログイン時に適用する。SSH ログインや `login` 経由の shell が対象。 ```bash # /etc/security/limits.d/90-nofile.conf * soft nofile 8192 * hard nofile 65536 deploy soft nproc 4096 ``` 書式は ` `。`domain` はユーザー名・`@グループ名`・全体を表す `*`。設定後は**いったんログアウトして再ログイン**しないと反映されない。 ::: warning `pam_limits` は PAM セッションを経由しないプロセスには効かない。**systemd が起動するデーモンには limits.conf は適用されない**ため、サービスの上限を上げたつもりが効かない事故が多い。 ::: ### systemd サービス systemd 管理のサービスは unit ファイルの `[Service]` セクションで指定する。 ```bash # /etc/systemd/system/myapp.service.d/override.conf [Service] LimitNOFILE=65536 LimitNPROC=4096 ``` ```bash $ sudo systemctl daemon-reload $ sudo systemctl restart myapp ``` 全サービス共通のデフォルトを変えるなら `/etc/systemd/system.conf` の `DefaultLimitNOFILE=` を編集する。`systemctl show myapp -p LimitNOFILE` で実効値を確認できる。 ## 実行中プロセスの上限を調べるには? {#running} > **結論**: `/proc//limits` を読むか `prlimit --pid ` を使う。`prlimit` は再起動なしに稼働中プロセスの上限を変更もできる。 ```bash $ cat /proc/1234/limits ``` ```output Limit Soft Limit Hard Limit Units Max open files 1024 524288 files Max processes 15363 15363 processes ... ``` `prlimit` なら 1 行で確認・変更できる。 ```bash # 確認 $ prlimit --pid 1234 --nofile # 稼働中プロセスのソフト/ハードを 8192 に変更(権限が必要な場合あり) $ sudo prlimit --pid 1234 --nofile=8192:8192 # コマンドを上限付きで起動 $ prlimit --nofile=4096:4096 ./myserver ``` ## Too many open files の対処 {#troubleshoot} > **結論**: まず `lsof` で実際の FD 数を数え、上限と突き合わせる。アプリの FD リークか、上限不足かを切り分けてから対処する。 「Too many open files」は open files(`-n`)の上限到達を示す。手順は次の通り。 1. 対象プロセスの PID を特定する 2. 実効上限を確認する(`cat /proc//limits`) 3. 実際に開いている FD 数を数える ```bash # プロセスが開いている FD 数をカウント $ lsof -p 1234 | wc -l ``` 4. FD 数が上限に張り付いていれば、原因を判断する。 - **FD リーク**(開きっぱなしで閉じていない)→ アプリ側の修正が本筋。上限引き上げは延命策 - **正当に多い**(高同時接続)→ [永続化](#persist)の手順で上限を引き上げる ::: danger 上限をやみくもに `unlimited` 近くまで上げるのは避ける。FD リークを隠蔽し、メモリ枯渇やカーネルの `file-max` 到達といった別の障害に化けることがある。 ::: ## まとめ {#summary} - `ulimit` はプロセス単位のリソース上限を操作するコマンド。ソフト/ハードの二層構造を押さえる - 一時変更は `ulimit -Sn N`、確認は `ulimit -a` / `/proc//limits` / `prlimit` - 永続化はログインなら `limits.d/*.conf`、systemd サービスなら unit の `LimitNOFILE=`。**経路の取り違えに注意** - 「Too many open files」は上限引き上げの前に FD リークを疑う ## 次に読む {#next} - [lsof 入門 - 開いているファイルとポートを調べる](/articles/tutorials/lsof-basics) - [systemd ユニットファイルを書く - 自作サービスの登録](/articles/tutorials/systemd-unit-creation) - [swap の役割と運用 - メモリ不足を回避する](/articles/tutorials/swap-management) # umask の仕組み - 新規ファイルの権限を制御する Source: https://penguin-gym-linux.com/articles/tutorials/umask-basics ## umask とは何か? {#what-is-umask} umask は新規ファイルやディレクトリ作成時のデフォルト権限を決める「引き算マスク」だ。ファイル作成時の最大権限(ファイル: `666`、ディレクトリ: `777`)から umask の値をビット演算で除いた結果が実際の権限になる。 ::: tip **結論** - ファイルのデフォルト最大権限: `666`(`rw-rw-rw-`) - ディレクトリのデフォルト最大権限: `777`(`rwxrwxrwx`) - `umask 022` の場合: ファイルは `644`(`rw-r--r--`)、ディレクトリは `755`(`rwxr-xr-x`) ::: ## umask の値はどう計算するのか? {#calculation} umask は「許可しないビットを指定する」マスクだ。デフォルト最大権限の各ビットのうち、umask のビットが立っているものは除去される。 ### 計算式 ``` 実際の権限 = デフォルト最大権限 AND NOT(umask) ``` `umask 022` でファイルを作成した場合: ``` 666 = 110 110 110 (owner: rw-, group: rw-, other: rw-) 022 = 000 010 010 (マスク: group/other の w を除去) ───────────────────────────────────────────────────────── = 644 = 110 100 100 (owner: rw-, group: r--, other: r--) ``` ::: warning **引き算ではなくビット演算** 「最大権限から umask を引く」と説明されることもあるが、厳密には AND NOT 演算だ。例として `umask 023` の場合、単純な引き算では `666 - 023 = 643` になるが、実際の結果は `644` になる。`other` の `w`(2)と `x`(1)はそれぞれ独立したビットで処理されるためだ。 ::: ## 現在の umask を確認するには? {#check-umask} `umask` コマンドを引数なしで実行すると現在の値を 8 進数で表示する。 ```bash $ umask 0022 ``` シンボリック形式で確認する場合: ```bash $ umask -S u=rwx,g=rx,o=rx ``` 先頭の `0` は特殊ビット(setuid / setgid / sticky)の桁だ。通常は `0022` のように先頭が `0` になる。 ## umask を設定するには? {#set-umask} `umask` コマンドに値を渡すと設定できる。この設定は**現在のシェルセッションのみ**有効で、新しいターミナルを開くとデフォルト値に戻る。 ### 一時的な設定 ```bash $ umask 022 # よく使われる標準的な設定 $ umask 027 # グループに書き込み禁止、他人に全権限禁止 $ umask 077 # 所有者のみアクセス可(最もセキュア) $ umask 002 # グループ書き込みを許可(共同作業環境) ``` 設定後に新規ファイルを作成して確認: ```bash $ umask 022 $ touch testfile.txt $ ls -la testfile.txt -rw-r--r-- 1 user user 0 Jun 1 10:00 testfile.txt $ umask 077 $ touch private.txt $ ls -la private.txt -rw------- 1 user user 0 Jun 1 10:00 private.txt ``` ## umask を永続化するには? {#persistent-umask} シェルの設定ファイルに `umask` コマンドを追記することで永続化できる。 ### ユーザー単位での設定 ```bash # ~/.bashrc(bash のインタラクティブシェル) echo 'umask 022' >> ~/.bashrc source ~/.bashrc # ~/.profile または ~/.bash_profile(ログインシェル) echo 'umask 022' >> ~/.profile ``` ### システム全体への適用 ```bash # /etc/profile(全ユーザーのログインシェル)を root 権限で編集 sudo nano /etc/profile # → 末尾に umask 022 を追加 ``` ::: tip 多くの Linux ディストリビューションでは `/etc/profile` か `/etc/login.defs`(PAM 経由)でシステムデフォルトの umask が設定されている。現在のシステム設定を確認する場合は `/etc/profile` や `/etc/login.defs` を参照せよ。 ::: ## よく使われる umask の値 {#common-values} | umask | ファイル権限 | ディレクトリ権限 | 用途 | | ----- | ------------------- | ------------------- | -------------------------------------- | | `022` | `644` (`rw-r--r--`) | `755` (`rwxr-xr-x`) | 一般的なデスクトップ / サーバ環境 | | `027` | `640` (`rw-r-----`) | `750` (`rwxr-x---`) | グループ書き込み禁止・他人ブロック | | `077` | `600` (`rw-------`) | `700` (`rwx------`) | 機密ファイルや個人環境 | | `002` | `664` (`rw-rw-r--`) | `775` (`rwxrwxr-x`) | グループ作業環境(共有書き込みを許可) | ::: warning **セキュリティの注意** `umask 000` は全権限を残すため使用しないこと。`rw-rw-rw-` のファイルが量産され、他ユーザーが書き込める状態になる。 ::: ## umask とファイルの実行権限について {#executable} umask がどう設定されていても、`touch` やテキストエディタで作成したファイルには**実行権限は付与されない**。ファイルのデフォルト最大権限が `666` であるため、`umask 000` でも `rw-rw-rw-` が上限だ。実行ビットは決して残らない。 ```bash $ umask 000 # 何も引かない設定 $ touch script.sh $ ls -la script.sh -rw-rw-rw- 1 user user 0 Jun 1 10:00 script.sh # x が付かない ``` 実行可能なスクリプトを作成する場合は chmod で明示的に付与する: ```bash $ chmod +x script.sh $ ls -la script.sh -rwxrwxrwx 1 user user 0 Jun 1 10:00 script.sh ``` ## setgid ディレクトリと umask の組み合わせ {#sgid} ディレクトリに setgid(SGID)ビットが設定されている場合、そのディレクトリ内で作成されたファイルはディレクトリのグループを継承する。`umask 002` と組み合わせると、グループメンバー全員が書き込める共有ディレクトリを構築できる。 ```bash # SGID が設定されたディレクトリを確認 $ ls -ld /var/shared/ drwxrwsr-x 2 root devteam 4096 Jun 1 10:00 /var/shared/ # umask 002 で作業するとグループに書き込み権限が残る $ umask 002 $ touch /var/shared/project.conf $ ls -la /var/shared/project.conf -rw-rw-r-- 1 user devteam 0 Jun 1 10:00 /var/shared/project.conf ``` ## まとめ {#summary} ::: tip **umask チートシート** ```bash umask # 現在値を表示(数値) umask -S # 現在値を表示(シンボリック) umask 022 # 標準(ファイル: 644、ディレクトリ: 755) umask 077 # 最もセキュア(所有者のみ) umask 002 # グループ共同作業環境向け ``` ::: ::: warning **やってはいけないこと** - `umask 000` でファイルを全公開 - ログインシェルと非ログインシェルで設定が食い違うことを無視 - 一時設定が永続化されていると思い込む(セッションを閉じるとリセット) ::: ## 次に読む {#next} - [chmod・chownの使い方 - Linux権限管理の基本](/articles/tutorials/permissions-basics) - [chmod・chown・sudoの使い分け - Linux権限管理の応用](/articles/tutorials/permissions-advanced) - [stat コマンド入門 - ファイルメタ情報を読み解く](/articles/tutorials/stat-file-inspection) # user・group 管理の実践 — マルチユーザー環境の構築 Source: https://penguin-gym-linux.com/articles/tutorials/user-group-practical ## この記事のポイント {#intro} - `useradd` / `adduser` の違いと、実務で安全なユーザー追加の型 - `usermod -aG` でグループにユーザーを追加する方法と `-a` を忘れた場合の事故 - `/etc/passwd` / `/etc/shadow` / `/etc/group` の読み方と権限管理の実態 - マルチユーザー環境の構築手順をゼロから一本道で ::: tip **結論(実務の型)** - ユーザー追加: `useradd -m -s /bin/bash username` → `passwd username` - グループ追加: `usermod -aG groupname username`(`-a` 必須) - 確認: `id username` / `groups username` - `/etc/passwd` の編集は `usermod` 経由のみ(直接編集禁止) ::: ::: warning **前提(対象環境)** - OS: Ubuntu / Debian 系(`adduser` コマンドも使用可) - 操作は `sudo` またはルートで実施 ::: ## useradd と adduser はどう違うのか? {#useradd-vs-adduser} `useradd` は低レベルなバイナリ、`adduser` は Perl/Python 製の高レベルラッパー。Ubuntu では `adduser` が対話的にホームディレクトリ・パスワード設定まで行うため初心者向けだが、スクリプト内では `useradd` を使うほうが移植性が高い。 | | useradd | adduser | | ---------- | ------------------------ | ------------------ | | 種類 | バイナリ | スクリプトラッパー | | 対話 | なし | あり | | ホーム作成 | `-m` が必要 | デフォルトで作成 | | 移植性 | 全ディストリビューション | Debian 系のみ | 実務ではスクリプトに `useradd`、手作業に `adduser` を使い分けるのが一般的。 ## どうやってユーザーを追加するのか? {#add-user} `useradd -m -s /bin/bash username` でホームディレクトリ付きのユーザーを作成し、その後 `passwd` でパスワードを設定するのが標準手順。 ```bash # ユーザー作成(ホームディレクトリ付き) sudo useradd -m -s /bin/bash alice # パスワード設定 sudo passwd alice ``` ```output New password: Retype new password: passwd: password updated successfully ``` 主なオプション: | オプション | 説明 | | -------------- | ------------------------------ | | `-m` | ホームディレクトリを作成 | | `-s /bin/bash` | デフォルトシェル指定 | | `-d /path` | ホームディレクトリのパスを指定 | | `-u 1500` | UID を明示的に指定 | | `-g groupname` | プライマリグループ指定 | | `-G grp1,grp2` | 補助グループ指定(複数可) | 作成後の確認: ```bash id alice ``` ```output uid=1001(alice) gid=1001(alice) groups=1001(alice) ``` ::: tip `adduser` を使う場合は `sudo adduser alice` だけで対話的に全設定が完了する。スクリプト外での手動作業ならこちらが速い。 ::: ## グループをどう作成・管理するのか? {#group-management} `groupadd groupname` でグループを作成する。グループは `/etc/group` に記録され、ファイルの共有アクセス制御の基本単位になる。 ```bash # グループ作成 sudo groupadd developers # グループ確認 getent group developers ``` ```output developers:x:1002: ``` グループの変更・削除: ```bash # グループ名変更 sudo groupmod -n dev developers # グループ削除 sudo groupdel dev ``` ::: warning グループを削除しても、そのグループを参照している `/etc/group` の補助グループエントリは残る。`groupdel` 後に `grep ':dev:' /etc/group` で残骸がないか確認すること。 ::: ## ユーザーをグループに追加するにはどうするのか? {#add-to-group} `usermod -aG groupname username` の `-a`(append)が最重要。`-a` なしで `-G` だけ使うと、既存の補助グループがすべて上書きされ削除される。 ```bash # グループに追加(-a 必須) sudo usermod -aG developers alice # 複数グループに同時追加 sudo usermod -aG developers,sudo alice # 確認 groups alice ``` ```output alice : alice developers sudo ``` ::: danger `-a` を忘れると既存グループがすべて失われる。 ```bash # 危険: sudo グループが剥奪される sudo usermod -G developers alice ``` 特権ユーザーから `sudo` グループを誤って剥奪した場合、別のルートセッションから復旧する必要がある。 ::: グループ変更はログイン中のセッションには即時反映されない。`newgrp developers` または再ログインで有効になる。 ```bash # セッションを再起動せずにグループを有効化 newgrp developers ``` ## /etc/passwd と /etc/shadow の読み方は? {#etc-passwd} `/etc/passwd` はユーザー情報のデータベース、`/etc/shadow` は暗号化パスワードを格納する。パスワードが `/etc/passwd` に直接書かれていた時代は全ユーザーが平文ハッシュを閲覧可能だったが、現代では `/etc/shadow`(root のみ読取可)に分離されている。 **/etc/passwd の構造(コロン区切り 7 フィールド):** ```output alice:x:1001:1001:Alice Smith:/home/alice:/bin/bash ^ ^ ^ ^ ^ ^ ^ | | | | | | デフォルトシェル | | | | | ホームディレクトリ | | | | GECOS(フルネーム等) | | | GID(プライマリグループ) | | UID | パスワード('x' = /etc/shadow に格納) ユーザー名 ``` **/etc/shadow の構造(コロン区切り 9 フィールド):** ```bash sudo cat /etc/shadow | grep alice ``` ```output alice:$6$salt$hashedpassword...:19500:0:99999:7::: ^ ^ ^ ^ ^ | | | | 警告日数 | | | 最大有効日数 | | 最小変更間隔 | 最終変更日(epoch 日数) ハッシュ($6$ = SHA-512) ``` ::: tip `/etc/passwd` の直接編集は禁止。`usermod` や `chsh` を経由すること。直接編集でフォーマットを壊すとログイン不能になる。 ::: ## ユーザー情報の変更・削除はどうするのか? {#modify-delete} `usermod` でほぼすべての属性を変更できる。削除には `userdel` を使い、ホームディレクトリも削除する場合は `-r` を付ける。 ```bash # ログインシェル変更 sudo usermod -s /bin/zsh alice # ホームディレクトリ移動(既存ディレクトリを移動しながら変更) sudo usermod -d /home/alice2 -m alice # ロック(ログイン無効化) sudo usermod -L alice # ロック解除 sudo usermod -U alice # ユーザー削除(ホームディレクトリは残す) sudo userdel alice # ユーザー削除(ホームディレクトリも削除) sudo userdel -r alice ``` ::: warning `userdel -r` は復元できない。削除前にホームの内容を確認すること。 ```bash ls -la /home/alice ``` ::: ## マルチユーザー環境の構築手順 {#multiuser-setup} 開発チームが共有サーバーを使う典型的な構成を一本道で示す。 ```bash # 1. 共有グループ作成 sudo groupadd developers # 2. ユーザー作成 sudo useradd -m -s /bin/bash -G developers alice sudo useradd -m -s /bin/bash -G developers bob # 3. パスワード設定 sudo passwd alice sudo passwd bob # 4. 共有ディレクトリ作成 sudo mkdir /srv/project sudo chown root:developers /srv/project sudo chmod 2775 /srv/project # setgid: 新規ファイルのグループを自動継承 ``` `chmod 2775` の `2` は setgid ビット。このディレクトリ下で作成されたファイルのグループが自動的に `developers` になり、グループメンバー全員が読み書きできる。 ```bash # 設定確認 ls -ld /srv/project ``` ```output drwxrwsr-x 2 root developers 4096 Jun 2 00:00 /srv/project ^ 's' = setgid ビット ``` ```bash # メンバー全員の確認 getent group developers ``` ```output developers:x:1002:alice,bob ``` ::: tip **sudo 権限の付与** 特定ユーザーに管理権限を与える場合は `sudo` グループへ追加(Ubuntu の場合): ```bash sudo usermod -aG sudo alice ``` または `visudo` でより細かい権限制御も可能。 ::: ## ログイン中ユーザーと接続状況を確認するには? {#who-w} `who` / `w` コマンドで現在ログインしているユーザーと作業状況を確認できる。 ```bash who ``` ```output alice pts/0 2026-06-02 09:00 (192.168.1.10) bob pts/1 2026-06-02 09:05 (192.168.1.11) ``` ```bash w ``` ```output 09:10:00 up 2 days, 3:00, 2 users, load average: 0.10, 0.08, 0.05 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT alice pts/0 192.168.1.10 09:00 2:00 0.05s 0.01s bash bob pts/1 192.168.1.11 09:05 0.00s 0.10s 0.02s top ``` `last` コマンドでログイン履歴も確認できる: ```bash last -n 10 ``` ## 次に読む {#next} - [chmod・chownの使い方](/articles/tutorials/permissions-basics) - [Linux権限管理の応用](/articles/tutorials/permissions-advanced) - [Permission deniedの解決方法](/articles/tutorials/permissions-practical) # ユーザー管理入門 - useradd/usermod/userdelの基本操作 Source: https://penguin-gym-linux.com/articles/tutorials/user-management-basics ## この記事で身につくこと {#intro} - `useradd` でユーザーを作成し、ログインできる状態まで整えられるようになります。 - `usermod -aG` でグループを安全に追加できるようになります。 - `userdel` の削除範囲を理解して、取り返しのつく手順で削除できるようになります。 **想定読者**:サーバに新しいメンバーのアカウントを作る必要が出てきた人。 ### 先に用語を整理する {#terms} 初めて出てくる言葉を、ここで一度だけ定義します。 - **ユーザー**とは、Linux にログインして操作する主体のことです。人が使うアカウントと、サービスが使うアカウント(システムユーザー)の 2 種類があります。 - **グループ**とは、複数のユーザーをまとめた集まりです。ファイルの権限やコマンドの許可は、ユーザー単位だけでなくグループ単位でも与えられます。 - **UID / GID** とは、ユーザーとグループに割り当てられる管理番号です。Linux は内部では名前ではなくこの番号で判定します。 - **ログインシェル**とは、ログイン直後に起動するシェルです。`/bin/bash` が一般的で、`/usr/sbin/nologin` を指定するとログインを禁止できます。 - **補助グループ**とは、そのユーザーが所属する 2 つ目以降のグループです。「セカンダリグループ」とも呼びます。最初から所属する 1 つ目は「プライマリグループ」と呼びます。 - **ホームディレクトリ**とは、そのユーザー専用の作業場所(`/home/ユーザー名`)です。 ## useradd / usermod / userdel で何ができるのか? {#overview} Linux のユーザー管理は 3 コマンドで完結する。`useradd` でユーザーを作成し、`usermod` でグループや属性を変更し、`userdel` で削除する。本記事では Ubuntu / Debian 系・RHEL/CentOS 系の両方を対象に実際のコマンド例で解説する。 以下のコマンドはすべて `sudo` が必要になる。ユーザー情報はシステム全体の設定であり、一般ユーザーの権限では書き換えられない。 ::: tip **最小手順** ```bash sudo useradd -m -s /bin/bash alice # ユーザー作成 sudo passwd alice # パスワード設定 sudo usermod -aG sudo alice # sudo 権限付与 sudo userdel -r alice # ユーザー削除(ホームごと) ``` ::: ::: warning **前提環境** - OS:Ubuntu 22.04 / Debian または RHEL 系(例は Ubuntu 基準) - `sudo` 権限のあるユーザーで実行する ::: ## 1. useradd でユーザーを作成するには? {#useradd} `useradd` コマンドはシステムにユーザーを追加する。オプションなしで実行するとホームディレクトリが作成されないため、実務では `-m` フラグが必須。 ```bash sudo useradd -m -s /bin/bash alice ``` **主要オプション:** | オプション | 意味 | | ------------------ | ---------------------------------------- | | `-m` | ホームディレクトリを作成する | | `-s /bin/bash` | ログインシェルを指定 | | `-d /custom/home` | ホームディレクトリのパスを変更 | | `-G group1,group2` | 補助グループを初期設定 | | `-c "Alice Smith"` | コメント(フルネーム等) | | `-e 2026-12-31` | アカウント有効期限(YYYY-MM-DD) | | `-r` | システムユーザーとして作成(サービス用) | ### パスワードを設定する `useradd` だけではパスワードが設定されずログインできない。必ず続けて `passwd` を実行する。 ```bash sudo passwd alice ``` ### 作成結果を確認する ```bash id alice ``` ```output uid=1001(alice) gid=1001(alice) groups=1001(alice) ``` ```bash grep alice /etc/passwd ``` ```output alice:x:1001:1001::/home/alice:/bin/bash ``` `/etc/passwd` の各フィールドは `ユーザー名:パスワード(x):UID:GID:コメント:ホームディレクトリ:シェル` の順。 ::: tip **Ubuntu / Debian では `adduser` も使える** `adduser` は `useradd` のラッパーで対話形式でユーザーを作成できる。スクリプト・自動化では `useradd` を使い、手動設定には `adduser` が便利。 ```bash sudo adduser alice # 対話形式で作成(パスワードも設定できる) ``` ::: ## 2. usermod でユーザー設定を変更するには? {#usermod} `usermod` は既存ユーザーの属性を変更する。操作頻度が最も高いのはグループへの追加。 ### sudo 権限を付与する ```bash sudo usermod -aG sudo alice # Ubuntu/Debian sudo usermod -aG wheel alice # RHEL/CentOS/Fedora ``` ::: danger **`-a` を忘れると所属グループが消える** `-aG` の `-a`(append、追加)を**絶対に忘れない**こと。**失敗するとどうなるか**は次のとおり。 - `-G` だけを使うと、指定したグループ以外の所属がすべて外れる。追加ではなく置換になる。 - 自分自身に対して実行し、`sudo` グループから外れてしまった場合、以後 `sudo` が使えない。Ubuntu は root パスワードが無効なため、復旧には物理コンソールからのリカバリ起動が必要になる。 **安全に試す方法**は次の 2 つ。 - 実行前に `id alice` で現在の所属グループを記録しておく。置換してしまっても書き戻せる。 - 練習は自分以外のテスト用ユーザーで行う。自分のアカウントで `-G` を試さない。 ```bash id alice # 実行前に所属を記録 sudo usermod -aG sudo alice id alice # 追加されたことを確認 ``` ::: ### その他の変更操作 **ホームディレクトリを変更する:** ```bash sudo usermod -d /new/home -m alice # -m でファイルも移動 ``` **ログインシェルを変更する:** ```bash sudo usermod -s /bin/zsh alice ``` **アカウントをロック・アンロックする:** ```bash sudo usermod -L alice # パスワード認証をロック(鍵ログインは止まらない) sudo usermod -U alice # アンロック ``` ::: warning `usermod -L` は `/etc/shadow` のパスワードハッシュの先頭に `!` を付ける操作で、**無効化されるのはパスワード認証だけ**。`~/.ssh/authorized_keys` に公開鍵が置かれているユーザーは、ロック後も SSH でログインできる。ログインを確実に止める手順は後述の [userdel の節](#userdel) を参照。 ::: ### 変更後の確認 ```bash id alice groups alice ``` ```output uid=1001(alice) gid=1001(alice) groups=1001(alice),27(sudo) ``` ::: tip グループ変更は**次回ログイン時**に反映される。現在のセッションで即時反映したい場合は `newgrp sudo` を実行する。 ::: ## 3. userdel でユーザーを削除するには? {#userdel} `userdel` でユーザーをシステムから削除する。ホームディレクトリごと削除するには `-r` が必要。 ```bash sudo userdel -r alice ``` **動作の違い:** | コマンド | ユーザー削除 | ホームディレクトリ | メールスプール | | ------------------ | :----------: | :----------------: | :------------: | | `userdel alice` | 削除 | 残る | 残る | | `userdel -r alice` | 削除 | 削除 | 削除 | ::: danger **`userdel -r` は取り消せない** **失敗するとどうなるか**は次の 2 点。 - `-r` はホームディレクトリを中身ごと削除する。そのユーザーが置いていたスクリプト・鍵・作業ファイルもすべて消える。 - 削除後に UID が再利用されると、バックアップから戻したファイルの所有者が別人になる場合がある。 **安全に試す方法**は次の 3 段階。 1. 何が消えるのかを先に確認する。 ```bash sudo ls -la /home/alice # 削除対象の中身を確認 sudo du -sh /home/alice # 容量を確認 ``` 2. 必要なら退避する。 ```bash sudo tar czf /root/alice-home.tar.gz /home/alice ``` 3. すぐに消す必要がなければ、まずログインを止めて様子を見る。 `usermod -L` はパスワード認証だけを無効化する操作で、SSH 公開鍵によるログインは止まらない。確実に止めるには次の 3 つを合わせて実行する。 ```bash sudo usermod -L alice # パスワード認証を無効化 sudo chage -E 0 alice # アカウントを期限切れにする(鍵ログインも拒否される) sudo usermod -s /usr/sbin/nologin alice # ログインシェルを無効化 ``` 戻すときは `sudo usermod -U alice` / `sudo chage -E -1 alice` / `sudo usermod -s /bin/bash alice` の 3 つで元に戻せる。鍵そのものを止める場合は `sudo mv /home/alice/.ssh/authorized_keys /root/alice-authorized_keys.bak` で退避する。 削除は「もう戻さない」と判断できてから実行する。 ::: ### ログイン中のユーザーを削除する場合 ログイン中のユーザーは削除できない。`userdel: user alice is currently used by process 1234` のように拒否される。 ```bash who # ログイン中ユーザーを確認 sudo pkill -u alice # 該当ユーザーのプロセスを強制終了 sudo userdel -r alice ``` ::: warning `pkill -u` はそのユーザーのプロセスを問答無用で終了させる。保存していない作業や、そのユーザーが動かしているサービスも止まる。実行前に `ps -u alice` で何が動いているかを確認する。 ::: ## 4. グループ管理の基本 {#groups} ユーザー管理と密接に関わるグループ操作の基本を整理する。 ### グループを作成する ```bash sudo groupadd developers ``` ### ユーザーをグループに追加する ```bash sudo usermod -aG developers alice ``` ### グループを削除する ```bash sudo groupdel developers ``` ### グループ情報を確認する ```bash grep developers /etc/group ``` ```output developers:x:1002:alice ``` `/etc/group` のフィールド:`グループ名:パスワード:GID:メンバーリスト` ## 5. パスワードポリシーの確認と設定 {#password} `chage` コマンドでアカウントのパスワード有効期限を確認・変更する。 ```bash sudo chage -l alice ``` ```output Last password change : May 31, 2026 Password expires : never Account expires : never ``` **有効期限を設定する:** ```bash sudo chage -M 90 alice # パスワードを90日で期限切れに sudo chage -E 2026-12-31 alice # アカウント有効期限を設定 ``` ## まとめ:実務でよく使うコマンドパターン {#summary} ### 新規ユーザー追加の標準手順 ```bash sudo useradd -m -s /bin/bash -c "Alice Smith" alice sudo passwd alice sudo usermod -aG sudo alice id alice # 確認 ``` ### 確認コマンド一覧 ```bash id username # UID/GID/グループ一覧 groups username # 所属グループ grep username /etc/passwd # passwd エントリ grep username /etc/group # グループ所属確認 sudo cat /etc/shadow # パスワードハッシュ(要 sudo) ``` ## トラブルシューティング {#troubleshooting} ### 症状: 作成したユーザーでログインできない **原因**: `passwd` を実行していないため、パスワードが未設定でロック状態にある。 **確認**: ```bash sudo passwd -S alice ``` ```output alice L 05/31/2026 0 99999 7 -1 ``` 2 番目が `L` ならロック、`P` ならパスワード設定済み。 **対処**: ```bash sudo passwd alice ``` ### 症状: `useradd` したのにホームディレクトリがない **原因**: `-m` を付けずに実行した。Ubuntu の `useradd` は既定でホームを作らない。 **確認**: ```bash ls -ld /home/alice ``` **対処**: `/etc/skel`(新規ユーザー用の雛形ディレクトリ)の中身をコピーしてから所有者と権限を設定する。雛形をコピーしないと `.bashrc` や `.profile` が無い状態になり、プロンプトが崩れる・エイリアスが効かない・`PATH` に `~/.local/bin` が入らないといった別の不具合に移行する。 ```bash sudo mkdir /home/alice sudo cp -a /etc/skel/. /home/alice/ sudo chown -R alice:alice /home/alice sudo chmod 700 /home/alice ``` Ubuntu / Debian では `sudo mkhomedir_helper alice` で同じ処理をまとめて実行できる。 ### 症状: グループを追加したのに権限が反映されない **原因**: グループの変更は、そのユーザーの次回ログイン時に反映される。現在のセッションは古い情報を持ったままになる。 **確認**: ```bash id alice # /etc/group 上の最新状態 groups # 現在のセッションが持っている情報 ``` **対処**: いったんログアウトして再ログインする。すぐに反映したい場合は `newgrp sudo` を実行する。 ### 症状: `userdel` が `currently used by process` で失敗する **原因**: そのユーザーのプロセスが残っている。 **確認**: ```bash ps -u alice ``` **対処**: 内容を確認したうえで `sudo pkill -u alice` を実行し、その後に `userdel` する。 ## 作業完了チェックリスト {#checklist} - [ ] `useradd -m` でホームディレクトリを作成した - [ ] `passwd` でパスワードを設定し、ログインできる状態にした - [ ] グループ追加は `usermod -aG`(`-a` 付き)で実行した - [ ] `id` コマンドで UID・GID・所属グループを確認した - [ ] 削除前にホームディレクトリの中身を確認し、必要ならバックアップを取った - [ ] ロックで済ませる場合、`chage -E 0` まで実行して鍵ログインも止めた ## 次に読む {#next} - [権限管理の基本 - chmod・chownの使い方](/articles/tutorials/permissions-basics) - [chmod・chown・sudoの使い分け - 権限管理の応用](/articles/tutorials/permissions-advanced) - [プロセス管理入門 - ps・top・killの使い方](/articles/tutorials/process-management-basics) # Vim入門 - 基本操作と実践テクニック Source: https://penguin-gym-linux.com/articles/tutorials/vim-basics ## この記事で身につくこと {#intro} - Vim の **モード** を切り替えて、「抜け出せない」状態から自力で復帰できるようになります。 - 移動・編集・検索置換について、**実務で使う最低限の型** が身につきます。 - 保存・破棄・強制終了を、状況に応じて選べるようになります。 - `.vimrc` で **快適な初期設定** ができるようになります。 - サーバ作業で `vi` しか入っていない状況でも、手が止まらなくなります。 **想定読者**:SSH でサーバに入って設定ファイルを直す必要が出てきた人。Vim を開いて閉じられなかった経験があるなら、ちょうどこの記事の対象です。 ### 先に用語を整理する {#terms} Vim の説明で必ず出てくる言葉を、ここで一度だけ定義します。 - **モード**とは、同じキーを押しても動作が変わる「今の状態」のことです。Vim には移動用の状態と文字入力用の状態が別々にあります。 - **ノーマルモード**とは、移動・削除・コピーを行う状態です。「コマンドモード」と呼ぶ資料もありますが、この記事では後述のコマンドラインモードと区別するため「ノーマルモード」で統一します。 - **挿入モード**とは、キーを押すとその文字が本文に入る状態です。「インサートモード」とも呼びます。 - **コマンドラインモード**とは、`:` を押して保存・終了・置換などの命令を打ち込む状態です。「Ex コマンド」と呼ぶこともあります。 - **バッファ**とは、Vim がメモリ上に読み込んでいるファイルの内容です。バッファを編集してもディスク上のファイルはまだ変わりません。`:w` で書き込んだ時点で初めてファイルが変わります。 - **ヤンク**(yank)とは、Vim でのコピー操作の呼び名です。「コピー」と読み替えて構いません。 - **スワップファイル**とは、編集内容を一時保存する `.swp` という隠しファイルです。異常終了しても内容を回収できるようにする仕組みです。 ::: tip **結論(実務の型)** - 起動したら **まずノーマルモード**(`Esc`)を意識する - 編集は `i` → 編集 → `Esc` → `:w` の 4 ステップが基本 - 抜けられなくなったら `Esc` を 2 回押して `:q!` で強制終了 - 設定ファイル編集は `sudo vim` ではなく `sudoedit` を使う ::: ::: warning **前提(対象環境)** - OS:Ubuntu / 他 Linux 一般 - Vim 8 以降 または Neovim(vi 互換動作にも触れる) - SSH 越しのリモートサーバ作業を主に想定 ::: ## 1. モード:これだけで 8 割解決 {#modes} > **結論**: Vim の難しさはモードに集約され、ノーマル・挿入・ビジュアル・コマンドラインの 4 つを理解すれば 8 割解決する。 Vim が初心者を脱落させる最大の原因は **モード** の存在。逆に言えば、モードさえ理解できれば残りは「コマンドの暗記」だけ。 | モード | 入り方 | 何ができる | | -------------- | --------------- | ------------------ | | ノーマル | `Esc` | 移動・削除・コピー | | 挿入 | `i` / `a` / `o` | 文字入力 | | ビジュアル | `v` / `V` | 範囲選択 | | コマンドライン | `:` | 保存・終了・置換 | ::: highlight **鉄則**: 何か変なことが起きたら、まず `Esc` を 2 回押してノーマルモードに戻る。 ::: 今どのモードにいるかは画面左下で判断できる。挿入モードなら `-- INSERT --`、ビジュアルモードなら `-- VISUAL --` と表示される。何も表示されていなければノーマルモードにいる。`Esc` は何度押しても害がないため、迷ったら押して構わない。 ### 1-1. 挿入モードへの入り方(使い分け) ```bash i # カーソル位置の前に挿入 a # カーソル位置の後に挿入(行末で使う) o # 下に新規行を開いて挿入 O # 上に新規行を開いて挿入 I # 行頭に挿入 A # 行末に挿入 ``` ::: tip 最初のうちは `i` と `o` だけで十分。慣れたら `A`(行末追記)が便利。 ::: ## 2. 保存と終了:最頻出の事故ポイント {#save-quit} > **結論**: `:w` で保存、`:wq` で保存終了、`:q!` で破棄終了。抜けられない事故はこの 3 つで解決する。 「Vim から抜けられない」問題はここで全部解決する。 ```bash :w # 保存(write) :q # 終了(quit) :wq # 保存して終了 :x # 保存して終了(:wq と同じ。変更がなければ書き込まない) ZZ # ノーマルモードで :wq 相当 :q! # 保存せず強制終了 :w !sudo tee % > /dev/null # sudo で開き忘れたファイルを保存(詳細は 2-1) ``` ::: warning `:q` だけだと「未保存の変更があります」で蹴られる。捨ててよいなら `:q!`、保存したいなら `:wq`。 ::: ::: danger **`:q!` は編集内容を捨てる** `!` は「確認せず実行する」という指示。**失敗するとどうなるか**は明確で、`:q!` で抜けた場合、それまでの編集内容はすべて失われる。undo では戻せない。 **安全に試す方法**は次の 2 つ。 - 保存したいのか捨てたいのか迷ったら、まず `:w ~/backup.txt` で別名保存してから `:q!` で抜ける。 - 設定ファイルを編集する前に `cp /etc/ssh/sshd_config ~/sshd_config.bak` でコピーを取る。書き戻せる状態を作ってから編集を始める。 ::: ### 2-1. sudo で開き忘れた事故の回復 `/etc/` 配下を `vim` で開いて編集後、保存時に Permission denied で詰む定番事故。 ```bash :w !sudo tee % > /dev/null ``` - `%` は現在開いているファイル名に展開される - `tee` で書き込み、`> /dev/null` で標準出力を捨てる - 保存後 `:q!`(既にディスクへ書かれているため `!` でよい) ::: tip そもそも設定ファイル編集は `sudoedit /etc/ssh/sshd_config`(または `sudo -e`)が安全。一時ファイルで編集し、終了時に上書きされる。 ::: ## 3. 移動:マウスを使わずに飛ぶ {#move} > **結論**: 移動は `hjkl`・単語/行/画面単位・検索ベースをノーマルモードで使い分けると素早く飛べる。 ノーマルモードで使う。挿入モードでは効かないので注意。 ### 3-1. 1 文字単位 ```bash h j k l # 左 下 上 右 ``` ::: tip `hjkl` の覚え方:`j` が `↓`(落ちる)、`k` が `↑`(突き上げる)。 ::: ### 3-2. 単語・行・画面単位 ```bash w / b # 次の単語頭 / 前の単語頭 e # 単語末尾 0 / ^ / $ # 行頭 / 行頭(空白除く) / 行末 gg / G # ファイル先頭 / 末尾 :42 # 42 行目へジャンプ Ctrl-d / Ctrl-u # 半画面 下 / 上 Ctrl-f / Ctrl-b # 1画面 下 / 上 ``` ### 3-3. 検索ベース移動 ```bash /word # 前方検索 ?word # 後方検索 n / N # 次 / 前のマッチへ * # カーソル位置の単語を前方検索 ``` ::: highlight **実務 Tips**: ログを見るときは `G` で末尾に飛び、`?ERROR` で後方検索すると早い。 ::: ## 4. 編集:最低限これだけ {#edit} > **結論**: 削除 `x`/`dd`、コピー `yy`、貼付 `p`、取消 `u`、繰返し `.` の数個で日常編集は足りる。 ```bash x # 1 文字削除 dd # 1 行削除(クリップボードへ) 3dd # 3 行削除 dw # 単語削除 d$ / D # 行末まで削除 yy # 1 行コピー(yank) 3yy # 3 行コピー p / P # 貼り付け(後 / 前) u # 取り消し(undo) Ctrl-r # やり直し(redo) . # 直前の操作を繰り返し ``` ::: tip **最強コマンドは `.`(ドット)**。直前の編集操作を再実行する。`dw`(単語削除)→ 別の場所で `.` で同じ削除が走る。 ::: ### 4-1. 数値プレフィックス ほとんどのコマンドは数値を前置できる。 ```bash 5j # 5 行下へ 10x # 10 文字削除 3yy # 3 行コピー ``` ## 5. 検索と置換:実務で一番使う {#search-replace} > **結論**: 検索は `/pattern`、置換は `:%s/foo/bar/g`。強力なので `gc` で確認しながら進めると安全。 ### 5-1. 検索 ```bash /pattern # 前方検索(正規表現可) ?pattern # 後方検索 n / N # 次 / 前 ``` ### 5-2. 置換(最重要) ```bash :s/foo/bar/ # 現在行の最初の foo を bar に :s/foo/bar/g # 現在行のすべて :%s/foo/bar/g # ファイル全体すべて :%s/foo/bar/gc # ファイル全体、確認しながら :5,10s/foo/bar/g # 5〜10 行目のすべて ``` 末尾の `g`(global)は「その行のすべての一致」、`c`(confirm)は「1 件ずつ確認する」という指定。`%` はファイル全体を対象にする範囲指定。 ::: danger **全置換は一括で本文を書き換える** `:%s/foo/bar/g` はファイル全体を一度に書き換える。**失敗するとどうなるか**は次のとおり。 - パターンが広すぎると、意図しない箇所まで置換される。設定ファイルなら構文が壊れてサービスが起動しなくなる。 - 置換直後なら `u`(undo)で戻せる。ただし `:w` で保存して Vim を終了したあとは、undo 履歴が消えるため戻せない(`set undofile` 未設定の場合)。 **安全に試す方法**は次の 3 段階。 1. まず `/foo` で検索して、何件どこに当たるかを目で見る。 2. `:%s/foo/bar/gc` の確認モードで実行し、`y`(はい)/ `n`(いいえ)/ `a`(残り全て)/ `q`(中止)で 1 件ずつ判断する。 3. 想定外の置換をしてしまったら、保存する前に `u` を押して戻す。 ::: ### 5-3. 正規表現の罠 Vim の正規表現は POSIX 拡張正規表現と微妙に違う。 ```bash :%s/\v(\w+)\s+\1/\1/g # \v で very magic モード(標準的な正規表現に近づく) ``` ::: tip `\v` を付けると `()` `+` `?` のエスケープが不要になる。覚えておくと事故が減る。 ::: ## 6. 複数ファイル・ウィンドウ {#multi} > **結論**: バッファで複数ファイルを切り替え、`:sp`/`:vsp` の分割ウィンドウで並べて作業できる。 ### 6-1. バッファ(複数ファイル) ```bash :e other.txt # 別ファイルを開く :ls # 開いているバッファ一覧 :b 2 # バッファ番号 2 へ :bn / :bp # 次 / 前のバッファ :bd # バッファを閉じる ``` ### 6-2. 分割ウィンドウ ```bash :sp file.txt # 水平分割 :vsp file.txt # 垂直分割 Ctrl-w w # ウィンドウ移動 Ctrl-w q # ウィンドウ閉じる Ctrl-w = # サイズ均等化 ``` ::: tip ログを片側、設定ファイルを片側で開きながら作業すると効率的。SSH 越しなら tmux と組み合わせるとさらに強い。 ::: ## 7. ビジュアルモード:範囲選択して操作 {#visual} > **結論**: `v`/`V`/`Ctrl-v` で範囲選択し、削除・コピー・インデント・置換をまとめて適用できる。 ```bash v # 文字単位の選択開始 V # 行単位の選択開始 Ctrl-v # 矩形(ブロック)選択 ``` 選択中に: ```bash d # 削除 y # コピー > # インデント < # 逆インデント :s/a/b/ # 選択範囲内のみ置換 ``` ::: highlight **矩形選択の活用**: 複数行の行頭に `#` を付ける(コメントアウト)操作は `Ctrl-v` → 行範囲選択 → `I` → `#` → `Esc` で一括処理できる。 ::: ## 8. .vimrc:最低限の設定 {#vimrc} > **結論**: `~/.vimrc` に行番号・検索・インデント・バックアップ等の最小設定を書けば快適に使える。 `~/.vimrc` に書く。新しいサーバに入るたびに 30 秒で書ける程度のミニマム設定。 ```vim " 基本 set number " 行番号表示 set ruler " カーソル位置表示 set showcmd " 入力中コマンド表示 set wildmenu " コマンド補完を強化 " 検索 set hlsearch " 検索結果ハイライト set incsearch " インクリメンタルサーチ set ignorecase " 大文字小文字無視 set smartcase " 大文字を含む検索は区別 " インデント set autoindent " 自動インデント set expandtab " Tab をスペースに set tabstop=4 " Tab 幅 set shiftwidth=4 " 自動インデント幅 " 安全 set backup " バックアップ作成 set backupdir=~/.vim/backup,/tmp set undofile " undo 履歴を保存 set undodir=~/.vim/undo " UI syntax on " シンタックスハイライト set background=dark " 暗い背景前提 ``` ::: tip `~/.vim/backup` と `~/.vim/undo` ディレクトリは事前に `mkdir -p` で作成しておく。 ::: ::: warning リモートサーバで `vi` しかない(Vim 互換モード)場合、`set` の一部が効かない。`:version` で確認可能。 ::: ## 9. 詰まったときの脱出術 {#stuck} > **結論**: 抜けられない・保存不可・画面停止・スワップ残りなど典型の詰まりには機械的な脱出手順がある。 実際に詰まる典型ケースと、機械的な脱出手順。 ### 9-1. 抜けられない ```bash Esc Esc :q! ``` ノーマルモードへ確実に戻ってから強制終了。 ### 9-2. 保存できない(読み取り専用) ```bash :set noreadonly :w ``` または `:w !sudo tee % > /dev/null`。 ### 9-3. 画面が固まった ```bash Ctrl-q # Ctrl-s で停止した端末を再開 ``` `Ctrl-s` はターミナル側の出力停止。Vim の問題ではない。 ### 9-4. 画面左端に `~` が並ぶ これは異常ではない。`~` は「この行にはまだ何もない」ことを示す Vim の通常表示で、ファイル末尾より下の領域に必ず出る。対処は不要。 同様に、編集後に `file.txt~` のような `~` 付きファイルが増えている場合も異常ではない。これは後述の `.vimrc` の `set backup` が作るバックアップファイルで、スワップファイルとは別物。不要なら削除してよい。 ### 9-5. 起動時に `E325: ATTENTION` が出る スワップファイル(`.swp`)が残っている。前回 Vim が正常終了しなかった、または誰かが同じファイルを開いているサイン。 ```output E325: ATTENTION Found a swap file by the name ".file.txt.swp" ``` ```bash ls -la .*.swp :recover # Vim 起動後に実行 ``` `:recover` で復旧できた内容を確認し、必要な分を保存したうえで `rm .file.txt.swp` する。スワップファイル名は「`.` + 元のファイル名 + `.swp`」の形になる。 ::: danger **スワップファイルを反射的に削除しない** `.swp` を消すと、そこに退避されていた未保存の編集内容も消える。**失敗するとどうなるか**は次の 2 点。 - 自分の未保存作業を消してしまう。`:recover` で回収できたはずの内容が失われる。 - 他人が今まさに同じファイルを編集中だった場合、その編集を壊す。 **安全に試す方法**は削除前に 2 つ確認すること。 ```bash ps aux | grep vim # 誰かが編集中でないか確認 ``` 編集中のプロセスがなく、`:recover` の内容も不要だと確認できてから削除する。 ::: ## 10. vi と Vim と Neovim {#vi-vim} > **結論**: vi は多くが Vim の互換モード、Vim がデファクト、Neovim は Lua 設定や LSP を備えたフォーク。 | 名前 | 実体 | 備考 | | ------ | ------------------------------- | -------------------------------- | | vi | 多くの場合 Vim の vi 互換モード | 一部設定が無効化される | | Vim | Vi IMproved | デファクトスタンダード | | Neovim | Vim のフォーク | Lua 設定・LSP 統合・モダンな実装 | ```bash which vi ls -l $(which vi) ``` ```output /usr/bin/vi lrwxrwxrwx 1 root root 20 Feb 15 2024 /usr/bin/vi -> /etc/alternatives/vi ``` `->` の右側が実体。Ubuntu では `/etc/alternatives/vi` 経由で `vim.basic` や `vim.tiny` に繋がっている。どれが実体かで使える設定が変わる。 ::: tip Ubuntu 最小構成だと `vim-tiny` しか入っていない場合がある。`sudo apt install vim` で full 版を入れる。 ::: ## 11. まとめ:明日から使う型 {#summary} ::: tip **コピペ用:最低限の作業フロー** ```bash # 1. 開く vim file.txt # 2. ノーマルモードで移動 gg # 先頭へ /keyword # 検索 # 3. 挿入モードへ i # 編集開始 (編集) Esc # ノーマルへ戻る # 4. 保存して終了 :wq # 失敗した場合 :q! # 捨てて終了 ``` ::: ### 作業完了チェックリスト - [ ] 画面左下の表示で、今どのモードにいるか判断できた - [ ] `:wq`(保存して終了)と `:q!`(破棄して終了)を意識して選べた - [ ] 全置換の前に `/pattern` で対象を確認した - [ ] 設定ファイルの編集は `sudoedit` またはバックアップ取得後に行った - [ ] `.swp` を消す前に `ps aux | grep vim` で編集中プロセスを確認した ::: warning **やってはいけないこと** - 挿入モードのまま `:wq` と打つ(本文に `:wq` が入る) - `sudo vim /etc/...` で開いて保存時に詰む → `sudoedit` を使う - スワップファイルを脊髄反射で削除する → 他人の編集中の可能性 - `.vimrc` を巨大化させる → サーバごとに使い回せなくなる ::: ## 次に読む {#next} - [シェルスクリプトの書き方](/articles/tutorials/shell-scripting-basics) - [journalctl でログを読む](/articles/tutorials/journalctl-basics) - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) # vmstat/iostat/sar - 性能解析ツール 3 兄弟 Source: https://penguin-gym-linux.com/articles/tutorials/vmstat-iostat-sar ## この記事で分かること {#intro} - `vmstat` / `iostat` / `sar` それぞれの役割と読み方 - CPU・メモリ・I/O ボトルネックの切り分け手順 - 3 つのツールの**実務での使い分け判断基準** ::: tip **結論:3 ツールの役割分担** | ツール | 得意なこと | | -------- | -------------------------------------------------------- | | `vmstat` | CPU/メモリ/スワップ/I/O を**一画面で俯瞰**。まず全体把握 | | `iostat` | **デバイス単位の I/O 詳細**(await・IOPS・util%)を確認 | | `sar` | **過去の履歴データ**を時系列で参照。事後調査に不可欠 | ::: ::: warning **前提環境** - OS:Ubuntu / RHEL 系 Linux - `iostat` / `sar` は `sysstat` パッケージが必要 - `sudo apt install sysstat` でインストール ::: ## vmstat とは何か? {#vmstat} CPU・メモリ・スワップ・I/O・プロセス状態を 1 行にまとめて表示する。ボトルネックが「CPU か I/O かメモリか」を最初に絞り込む一手目として使う。 ### 基本的な使い方 ```bash vmstat [interval [count]] ``` ```bash $ vmstat 1 5 ``` ```output procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 2 0 0 1543200 52480 921600 0 0 14 38 312 580 12 3 84 1 0 0 0 0 1540800 52480 922100 0 0 0 12 280 510 5 1 93 1 0 0 0 0 1539900 52480 922500 0 0 0 16 295 530 4 1 94 1 0 ``` ### 各フィールドの読み方 {#vmstat-fields} **procs(プロセス数)** - `r`:実行待ちプロセス数。論理 CPU 数を継続的に超えると CPU ボトルネック - `b`:I/O 待ちでブロックされているプロセス数 **memory(KB 単位)** - `swpd`:使用中スワップ量。0 以外になったら注意 - `free`:未使用メモリ - `buff` / `cache`:バッファ・ページキャッシュ(OS が管理する再利用可能な領域) **swap(スワップ活動)** - `si`:スワップイン(ディスク → メモリ)。継続的に非 0 → メモリ不足 - `so`:スワップアウト(メモリ → ディスク)。継続的に非 0 → メモリ不足 **io(ブロック I/O、ブロック/秒)** - `bi`:ブロックデバイスからの読み込み - `bo`:ブロックデバイスへの書き込み **cpu(CPU 使用率 %)** - `us`:ユーザー空間の処理 - `sy`:カーネル空間の処理 - `id`:アイドル - `wa`:I/O 待機。10% を超え続けると I/O ボトルネックの疑い - `st`:仮想化環境で他 VM に奪われた時間(steal) ::: tip **最初の行は累積値** `vmstat` 起動時の第 1 行はシステム起動後の累積平均値(参考程度)。実際の診断は 2 行目以降で判断する。定番は `vmstat 1`(1 秒間隔で継続)または `vmstat 5 12`(5 秒間隔 × 12 回で 1 分間の定点観測)。 ::: ## iostat とは何か? {#iostat} CPU 概要とデバイス単位の I/O 統計を表示する。`vmstat` で I/O ボトルネックの疑いが生じた後、「どのデバイスが問題か」を特定する二手目として使う。 ### インストール(初回のみ) ```bash $ sudo apt install sysstat # Ubuntu/Debian $ sudo dnf install sysstat # RHEL/Fedora ``` ### 基本的な使い方 ```bash $ iostat -x 1 5 ``` ```output Device r/s w/s rkB/s wkB/s await r_await w_await util% sda 1.20 5.30 48.00 212.00 2.50 1.80 2.70 3.20 nvme0n1 25.00 80.00 800.00 3200.00 0.48 0.40 0.52 8.50 ``` ### 重要フィールドの読み方 {#iostat-fields} | フィールド | 説明 | 目安 | | ------------ | ------------------------------ | -------------------------------------- | | `await` | リクエストの平均応答時間(ms) | HDD: 20ms 超で注意 / SSD: 1ms 超で注意 | | `r_await` | 読み込み平均応答時間(ms) | — | | `w_await` | 書き込み平均応答時間(ms) | — | | `util%` | デバイスのビジー率 | 80% 超で飽和懸念、100% で飽和確定 | | `r/s`, `w/s` | 毎秒の読み書き回数(IOPS) | デバイス仕様の IOPS 上限と比較 | ::: warning `util%` が 100% に近づくとデバイスが飽和(I/O キューが詰まり応答遅延が増大)。`await` の急増と合わせて確認する。なお `util%` はパーティション単位ではなくデバイス単位の指標。 ::: ### 特定デバイスのみ絞り込む ```bash $ iostat -x -d sda nvme0n1 1 ``` ## sar とは何か? {#sar} CPU・メモリ・I/O・ネットワークのメトリクスを継続収集し、履歴として参照できる。「昨夜の深夜に何が起きたか」「先週と比べて負荷が増えているか」を調べるときに使う。 ### 有効化手順 ```bash $ sudo apt install sysstat $ sudo systemctl enable sysstat --now ``` 有効化後、`sadc` デーモンが `/var/log/sa/saDD`(DD は日付)にデータを蓄積する。 ### 主要オプション {#sar-options} ```bash $ sar -u 1 5 # CPU 使用率(1秒間隔×5回) $ sar -r 1 5 # メモリ使用量 $ sar -b 1 5 # I/O 統計 $ sar -n DEV 1 5 # ネットワーク統計(デバイス別) $ sar -n EDEV 1 5 # ネットワークエラー統計 $ sar -q 1 5 # ロードアベレージ・プロセス数 ``` ### 過去データを参照する {#sar-history} ```bash # 今日の CPU 統計(すべての時刻) $ sar -u # 1 日分の全統計(-A で全メトリクス) $ sar -A -f /var/log/sa/sa01 ``` ```output 00:00:01 all 2.34 0.00 5.67 0.12 0.00 91.87 01:00:01 all 1.23 0.00 3.45 0.08 0.00 95.24 02:00:01 all 0.98 0.00 2.11 0.05 0.00 96.86 ``` ::: tip **収集間隔の変更** デフォルトは 10 分間隔(`/etc/cron.d/sysstat` または `/etc/sysstat/sysstat` で設定)。本番環境での障害調査精度を上げるには 1〜2 分間隔を推奨。間隔を短くするほどディスク使用量は増える点に注意。 ::: ## 3 つのツールをどう使い分けるか? {#when-to-use} 調査目的に応じてツールを選ぶことで、診断時間を大幅に短縮できる。 | 調査したいこと | 使うツール | 確認するフィールド | | ------------------------ | ------------------- | ------------------ | | まず全体像を把握したい | `vmstat 1` | `r`, `wa`, `si/so` | | CPU ボトルネックを確認 | `vmstat` / `sar -u` | `r`, `us+sy`, `id` | | I/O が遅いデバイスを特定 | `iostat -x 1` | `await`, `util%` | | スワップ活動を確認 | `vmstat` | `si`, `so`, `swpd` | | 昨夜の障害を事後調査 | `sar -u / -r / -b` | 時刻別の推移 | | ネットワーク帯域を確認 | `sar -n DEV` | `rxkB/s`, `txkB/s` | ## ボトルネック特定の実践パターン {#patterns} 現場でよく使う 4 つの診断シーケンスを示す。 ### 1. CPU ボトルネック疑いの場合 ```bash # Step 1: 全体を確認 $ vmstat 1 10 # r(実行待ちプロセス数)が CPU 数を超え続けるか確認 # Step 2: sar で詳細と推移を確認 $ sar -u 1 10 # us + sy が継続して 90% 超 → CPU ボトルネック確定 ``` ### 2. メモリ・スワップ問題疑いの場合 ```bash # Step 1: スワップ活動の確認 $ vmstat 1 10 # si/so が継続的に非 0 → スワップ中(メモリ不足) # Step 2: メモリ詳細 $ sar -r 1 5 # %memused が高い + kbswpused が増加中 → 要対処 ``` ### 3. I/O ボトルネック疑いの場合 ```bash # Step 1: vmstat で wa を確認 $ vmstat 1 5 # wa が 10% 超 → I/O ボトルネック疑い # Step 2: iostat で問題デバイスを特定 $ iostat -x 1 10 # await が高いか util% が 80% 超のデバイスに注目 # HDD なら await 20ms 超、SSD なら await 1ms 超が警戒ライン ``` ### 4. ネットワーク問題疑いの場合 ```bash $ sar -n DEV 1 5 # rxkB/s / txkB/s で帯域使用量を確認 # rxerr/s / txerr/s が非 0 ならハード・ドライバ問題の可能性 ``` ## 次に読む {#next} - [top と htop 徹底活用 - ボトルネックを見抜く](/articles/tutorials/top-htop-mastery) - [swap の役割と運用 - メモリ不足を回避する](/articles/tutorials/swap-management) - [ディスク管理入門 - fdisk/lsblkでストレージを確認する](/articles/tutorials/disk-management-basics) # watch コマンド入門 - コマンドを定期実行して変化を監視する Source: https://penguin-gym-linux.com/articles/tutorials/watch-command-basics ## この記事でわかること {#intro} - `watch` で **同じコマンドを一定間隔で繰り返し実行** できるようになる - `-n`(間隔)と `-d`(差分強調)という **よく使う 2 つのオプション** が分かる - パイプやリダイレクトを使うときの **クォートの落とし穴** を避けられる - 監視を **正しく終了する方法**(Ctrl + C)が分かる ::: tip **結論(先に要点)** - `watch コマンド` → 2 秒ごとに繰り返し実行して画面を更新 - 間隔を変えるなら `watch -n 5 コマンド`(5 秒ごと) - 変わった部分を光らせたいなら `watch -d コマンド` - パイプ `|` を使うときは `watch 'ls | wc -l'` のように **クォートで囲む** ::: ::: warning **前提(対象環境)** - OS:Linux(Ubuntu / Debian / RHEL 系など) - `watch` は `procps-ng` パッケージに含まれ、多くのディストリで標準インストール済み - macOS には標準では入っていない(必要なら `brew install watch`) ::: ## 1. watch コマンドとは? {#what} > **結論**: `watch` は指定したコマンドを一定間隔(既定 2 秒)で繰り返し実行し、最新の出力を画面に上書き表示するツール。変化の監視に使う。 ::: dialogue @lina: 先輩、ファイルのサイズが増えてるか確認したくて、何回も `ls -l` を打ってるんですけど…これ面倒すぎませんか? @linny: それ、`watch` の出番だね。`watch` は同じコマンドを自動で繰り返し実行して、画面を更新し続けてくれるんだ。手で連打しなくてよくなるよ。 @lina: 自動で繰り返してくれる!便利そう。どうやって使うんですか? @linny: 監視したいコマンドの前に `watch` を付けるだけ。まずは一番シンプルな形から見ていこう。 ::: ## 2. 基本の使い方 - watch でコマンドを繰り返す {#basic} > **結論**: `watch コマンド` と書くだけ。既定では 2 秒ごとに実行され、画面上部に間隔・実行コマンド・時刻が表示される。 ::: dialogue @lina: さっきの `ls -l` で試してみます。 @linny: いいね。こう打ってみて。 ```bash watch ls -l ``` @linny: すると画面の一番上にこんな見出しが出るはずだよ。 ```output Every 2.0s: ls -l host: Fri Jun 5 19:30:00 2026 ``` @lina: 「Every 2.0s」って、2 秒ごとって意味ですね。その下にいつもの `ls -l` の結果が出てます! @linny: そう。2 秒たつたびに中身が最新の状態に更新される。ファイルが追加されたり、サイズが変わったりすると、その場で反映されるんだ。 ::: ::: tip 画面上部の見出しは「実行間隔・実行中のコマンド・ホスト名・現在時刻」を表す。コマンドが今も生きて動いていることの確認にもなる。 ::: ## 3. 実行間隔を変えるには?(-n オプション) {#interval} > **結論**: `-n 秒数`(または `--interval`)で間隔を指定する。例 `watch -n 5 コマンド` は 5 秒ごと。最小は 0.1 秒。 ::: dialogue @lina: 2 秒だとちょっと速い気がします。もっとゆっくりにできますか? @linny: `-n` で秒数を指定できるよ。5 秒ごとにしたいならこう。 ```bash watch -n 5 ls -l ``` @lina: 逆に、もっと速く見たいときは? @linny: 小数も使えるんだ。`-n 0.5` で 0.5 秒ごと。最小は 0.1 秒まで。ただし間隔を短くしすぎると CPU に負担がかかるから、必要な速さで十分だよ。 ```bash watch -n 0.5 ls -l ``` ::: ::: warning `-n 1` のように間隔を短くすると、その分コマンドが頻繁に実行される。重いコマンド(大きなディレクトリの集計など)を短間隔で回すと負荷が高くなるので注意。 ::: ## 4. 変化した部分を目立たせるには?(-d オプション) {#differences} > **結論**: `-d`(`--differences`)で前回からの変化部分がハイライトされる。`-d=permanent` で最初からの累積変化を保持表示する。 ::: dialogue @lina: 出力が多いと、どこが変わったのか目で追うのが大変です… @linny: そんなときは `-d`。前回の表示から変わった部分を反転表示で目立たせてくれるよ。 ```bash watch -d ls -l ``` @lina: 変わったところだけ光って見える!これなら見逃さないですね。 @linny: さらに `-d=permanent` を使うと、一度変化した箇所をずっと強調したままにできる。「いつの間にか変わっていた場所」を後から把握したいときに便利だよ。 ```bash watch -d=permanent ls -l ``` ::: ## 5. パイプやリダイレクトを使うときの注意(クォート) {#quoting} > **結論**: `|` や `>` を含めたいときは全体をクォートで囲む。囲まないとシェルが先に解釈し、`watch` の出力にパイプがかかってしまう。 ::: dialogue @lina: ファイルの行数が増えるのを監視したくて `watch ls | wc -l` と打ったら、変な動きになりました… @linny: それ、よくある落とし穴。クォートで囲んでいないと、シェルは「`watch ls` の出力を `wc -l` に渡す」と解釈してしまうんだ。`watch` に `ls | wc -l` 全体を渡したいときは、こう書く。 ```bash watch 'ls | wc -l' ``` @lina: なるほど、クォートで囲むと `ls | wc -l` がまとめて `watch` に渡されるんですね。 @linny: そのとおり。リダイレクト `>` やワイルドカード `*` など、シェルが特別扱いする記号を含むときも同じ。迷ったら「watch に渡すコマンド全体をシングルクォートで囲む」と覚えておくといいよ。 ::: ::: tip **覚え方**: パイプや記号を使うなら `watch 'コマンド全体'`。クォートし忘れが watch のトラブルで一番多い。 ::: ## 6. ヘッダーを消す・色を活かす・変化で止める {#more-options} > **結論**: `-t` で上部見出しを消す、`-c` で色付き出力を再現、`-g` で出力が変化した瞬間に終了できる。 ::: dialogue @lina: 上の「Every 2.0s…」の見出し、消せたりしますか? @linny: `-t`(`--no-title`)で消せるよ。出力だけをすっきり見たいときに使う。 ```bash watch -t ls -l ``` @lina: あと、`ls --color` みたいに色を付けたコマンドだと、色が消えちゃうんですけど… @linny: `-c`(`--color`)を付けると、コマンドが出力した色(ANSI カラー)をそのまま表示してくれる。 ```bash watch -c ls --color=always ``` @linny: もう一つ便利なのが `-g`(`--chgexit`)。出力が前回と変わった瞬間に `watch` を終了するんだ。「何かが変化したら知りたい」というときに使える。 ```bash watch -g ls -l ``` ::: ::: tip 主なオプションまとめ | オプション | 意味 | | -------------- | ---------------------------------------------- | | `-n 秒` | 実行間隔を指定(既定 2 秒、最小 0.1 秒) | | `-d` | 前回からの変化をハイライト | | `-d=permanent` | 最初からの累積変化を保持表示 | | `-t` | 上部の見出しを消す | | `-c` | コマンドの色(ANSI)を再現 | | `-g` | 出力が変化したら終了 | | `-x` | `sh -c` を経由せず直接実行(引数の解釈に注意) | ::: ## 7. watch を終了するには? {#exit} > **結論**: `watch` は止めるまで動き続ける。`Ctrl + C` を押すと監視を終了して通常のプロンプトに戻る。 ::: dialogue @lina: 動かしっぱなしですけど、これどうやって止めるんですか? @linny: `Ctrl + C`(コントロールキーを押しながら C)だよ。これで `watch` が終了して、いつものプロンプトに戻る。 @lina: あ、戻れました!ずっと動いてるから不安だったんです。 @linny: `watch` は自分で止めるまで延々と繰り返すからね。確認が終わったら `Ctrl + C` で止める、と覚えておけば大丈夫。 ::: ## 8. よくある使いどころ {#usecases} > **結論**: ディスク容量・プロセス・ログ件数・ネットワーク接続など「時間とともに変わる数字」を目で追いたい場面で活躍する。 ::: dialogue @lina: 実際にはどんな場面で使うんですか? @linny: 「変化を見張りたい」場面ならどこでも。よく使う例を挙げるね。 ```bash # ディスク使用量の変化を監視 watch df -h # 特定プロセスの数を監視(パイプはクォート) watch 'ps aux | grep nginx' # ログの行数が増えていくのを監視 watch 'wc -l /var/log/syslog' # ネットワーク接続数の変化を 5 秒ごとに監視 watch -n 5 'ss -tan | wc -l' ``` @lina: なるほど、「数字が変わっていくのを見たい」ときの定番なんですね。 @linny: そう。ただし、ログを流れるように追いたいだけなら `tail -f` のほうが向いている。`watch` は「画面全体を定期的に撮り直す」、`tail -f` は「新しい行が出るたびに下に流す」。目的で使い分けよう。 ::: ## 次に読む {#next} - [less/more/tail でログを読む](/articles/tutorials/less-more-tail) - [top と htop 徹底活用](/articles/tutorials/top-htop-mastery) - [cron が動かない原因と対処法](/articles/tutorials/cron-basics) # wc コマンド入門 - 行数・単語数・文字数を数える Source: https://penguin-gym-linux.com/articles/tutorials/wc-text-counting ## この記事で学べること {#intro} - `wc` で **行数・単語数・バイト数** をまとめて数える基本が分かる - `-l` / `-w` / `-c` / `-m` の **使い分け** が身につく - `ls | wc -l` のように **パイプで「件数」を数える** 定番形が書けるようになる - 初心者がハマりやすい **「バイト数と文字数がずれる(日本語)」「最終行のカウントが合わない」** の理由がわかります ::: tip **言葉の整理(先に取りちがえを防ぎます)** - **行**:ファイルの中の 1 行のことです。`wc` は改行の数で行を数えます。 - **単語**:空白か改行で区切られたひとかたまりです。日本語の「単語」とは数え方がちがう点に注意してください。 - **バイト**:データの大きさを表す単位です。ファイルの容量にあたります。 - **文字**:人が読む文字 1 つ分です。日本語の 1 文字は 1 バイトではありません。ここが最大の関門です。 - **文字コード**:文字をデータに置きかえる決まりです。この記事では UTF-8 を使います。「エンコーディング」も同じものを指します。 ::: ::: tip **結論(先に覚える型)** - とりあえず全部数えたい → `wc ファイル` - 行数だけ欲しい → `wc -l` - 件数を数えたい(一覧の数え上げ) → `何かのコマンド | wc -l` - 日本語の「文字数」を数えたい → `wc -m`(`-c` はバイト数なので注意) ::: ::: warning **前提(対象環境)** - OS:Ubuntu / 一般的な Linux - GNU coreutils の `wc`(`wc` = **w**ord **c**ount の略) - 文字コードは UTF-8 を想定 ::: ## 1. wc とは何か?まずは全部数えてみる {#what-is-wc} > **結論**: `wc ファイル` は「行数・単語数・バイト数」を左から順に 3 つ表示する。名前は word count だが行もバイトも数えられる。 ::: dialogue @lina: 先輩、「このファイルは何行あるのか」を知りたいときは、手で数えるしかないのですか。 @linny: そんなときのためのコマンドが `wc` だよ。**w**ord **c**ount の略なんだ。 @linny: 名前は「単語を数える」だけど、実際は **行数・単語数・バイト数** をまとめて数えてくれる。 @lina: 名前と中身が少しちがうんですね。 ::: サンプルとして次のファイルを用意しよう。 ```bash $ cat sample.txt ``` ```output hello world linux command penguin gym ``` `wc` にそのまま渡してみる。 ```bash $ wc sample.txt ``` ```output 3 6 38 sample.txt ``` ::: highlight **3 つの数字の意味(左から順番)** | 位置 | 数字 | 意味 | | ------ | ---- | -------- | | 1 番目 | `3` | 行数 | | 2 番目 | `6` | 単語数 | | 3 番目 | `38` | バイト数 | 右はしにファイル名が付きます。 ::: ::: dialogue @lina: 数字が 3 つも出ると、どれが何だかわからなくなりそうです。 @linny: 順番さえ覚えれば大丈夫。**「行・単語・バイト」** の順だよ。 @linny: それに現場では「行数だけ欲しい」のように **1 種類だけ** 使うことが多い。次でオプションを見ていこう。 ::: ## 2. 行数だけ数える `-l` {#count-lines} > **結論**: `wc -l` は行数だけを表示する。ログの行数やリストの件数を数えるときの定番。 ```bash $ wc -l sample.txt ``` ```output 3 sample.txt ``` `-l` は **l**ine(行)の頭文字です。 ::: tip ログファイルが何行あるかを知りたいときの定番です。 ```bash # エラーログが何行たまっているか $ wc -l /var/log/syslog ``` ::: ## 3. 単語数・バイト数・文字数 `-w` / `-c` / `-m` {#count-words-chars} > **結論**: `-w` は単語数、`-c` はバイト数、`-m` は文字数。日本語など多バイト文字では `-c` と `-m` の値がずれる。 ### 3-1. 単語数 `-w` ```bash $ wc -w sample.txt ``` ```output 6 sample.txt ``` `-w` は **w**ord(単語)の頭文字です。**空白か改行で区切られたかたまり** を 1 単語として数えます。 日本語の文章は空白で区切りません。そのため `-w` の結果は、人が思う単語数とは一致しません。 ### 3-2. バイト数 `-c` ```bash $ wc -c sample.txt ``` ```output 38 sample.txt ``` `-c` は **c**haracter(文字)に見えます。ところが実際に数えるのは **バイト数** です。ここが最初の落とし穴です。 ### 3-3. 文字数 `-m` ```bash $ wc -m sample.txt ``` ```output 38 sample.txt ``` 英数字だけのファイルなら **バイト数と文字数は一致** します。1 文字がちょうど 1 バイトだからです。 ちがいが出るのは日本語のときです。次の章でくわしく見ます。 | オプション | 略 | 数えるもの | | ---------- | ---- | ---------- | | `-l` | line | 行数 | | `-w` | word | 単語数 | | `-c` | — | バイト数 | | `-m` | — | 文字数 | ## 4. リナの失敗:日本語で「バイト」と「文字数」がずれる {#bytes-vs-chars} > **結論**: UTF-8 では日本語 1 文字が 3 バイト。文字数を数えたいなら `-c`(バイト)ではなく `-m`(文字)を使う。 ::: dialogue @lina: 「あいう」という 3 文字のファイルを数えました。すると `wc -c` が `10` と出ました。これはバグですか。 @linny: バグではないよ。`-c` が数えているのは **バイト数** なんだ。 @linny: UTF-8 という文字コードでは、**日本語の 1 文字が 3 バイト** になる。だから「あいう」は 3 文字 × 3 バイトで 9 バイト。 @linny: そこに行末の改行 1 バイトが足されて、合計 10 になるんだ。 @lina: えっ、文字の数とデータの大きさは別ものなんですね。びっくりしました。 @linny: そう。人が読む文字数を知りたいときは `-m` を使う。そう覚えておけば混乱しないよ。 @lina: 数字が 3 倍くらいになっていた理由がわかりました。すっきりしました。 ::: 実際に見てみよう。 ```bash $ echo "あいう" > jp.txt $ wc jp.txt ``` ```output 1 1 10 jp.txt ``` ```bash # バイト数 $ wc -c jp.txt ``` ```output 10 jp.txt ``` ```bash # 文字数(改行も 1 文字として数える) $ wc -m jp.txt ``` ```output 4 jp.txt ``` ::: warning **初心者あるある** - 日本語の文字数を数えたいのに `-c` を使い、3 倍ほどの数字が出て混乱します - **文字数を数えたいなら `-m`** です。データの容量を知りたいなら `-c` です - どちらも行末の改行を 1 つ分として数えます ::: ## 5. パイプで「件数」を数える定番形 {#pipe-counting} > **結論**: `何かのコマンド | wc -l` で出力行数=件数を数えられる。`ls | wc -l` や `grep ... | wc -l` が頻出。 ::: dialogue @lina: ファイルの行数はわかりました。「このフォルダにファイルがいくつあるか」も数えたいです。 @linny: そこで `wc` が力を発揮する。`wc` は **パイプ(`|`)で受け取った出力** も数えられるんだ。 @linny: `ls` は画面に出すときは何列かに分けて並べる。でもパイプで次のコマンドに渡すときは、1 項目を 1 行にして送るんだ。 @linny: だから `ls | wc -l` の行数が、そのままファイルの数になるよ。 ::: ### 5-1. ファイル・ディレクトリの個数を数える ```bash $ ls | wc -l ``` ```output 12 ``` パイプに渡されたときの `ls` は、1 行に 1 項目を出します。だから行数を数えれば **個数** がわかります。 なお `ls` は隠しファイルを数えません。名前が `.` で始まるファイルのことです。含めたいときは `ls -A | wc -l` と書きます。 ### 5-2. 検索にヒットした行数を数える ```bash # ログから "error" を含む行が何件あるか $ grep "error" app.log | wc -l ``` ```output 27 ``` ::: tip `grep` にも件数を数える `-c` オプションがあります。`grep -c "error" app.log` でも同じ結果になり、こちらのほうが短く書けます。 いっぽう `wc -l` は、どんなコマンドの出力にも使えます。おぼえておくと便利です。 ::: ::: highlight **パイプ + `wc -l` が便利なポイント** - 出力にファイル名が付きません。数字だけが返るので、そのまま集計に使えます - `ls`・`grep`・`find` など **どのコマンドの出力でも数えられます** ::: ## 6. 複数ファイルをまとめて数える&合計 {#multiple-files} > **結論**: 複数ファイルを渡すと 1 行ずつ表示し、最後に `total` 行で合計を出す。 ```bash $ wc -l *.txt ``` ```output 1 jp.txt 3 sample.txt 4 total ``` 最後の `total` が **全ファイルの合計** 行数です。プロジェクト全体の行数をはかるときに便利です。 ## 7. よくある初心者のつまずき {#pitfalls} > **結論**: 文字数のずれは `-c`/`-m` 取り違え、ファイル名が邪魔なときはリダイレクトで渡す、最終行のカウントは改行有無で変わる。 ### 7-1. 出力にファイル名が付いて邪魔 `wc -l file` はファイル名も一緒に出ます。**数字だけ欲しい** ときは、リダイレクト `<` を使います。ファイルの中身を標準入力として流しこむ書き方です。 ```bash # ファイル名が付く $ wc -l sample.txt ``` ```output 3 sample.txt ``` ```bash # 数字だけ(ファイル名なし) $ wc -l < sample.txt ``` ```output 3 ``` ::: highlight **仕組み** - `wc -l file` … `wc` が自分でファイルを開きます。だから、どのファイルかを名前で添えます - `wc -l < file` … シェルが中身を流しこむだけです。`wc` はファイル名を知りません。だから数字だけが出ます ::: ### 7-2. 最終行に改行がないと行数が 1 少なく見える `wc -l` は **改行の数** を数えます。最後の行に改行がないと、その行は数えられません。 ```bash # 改行で終わらないファイル $ printf "a\nb\nc" | wc -l ``` ```output 2 ``` 画面では `a` `b` `c` の 3 行に見えます。しかし改行は 2 個しかありません。だから結果は `2` です。 テキストファイルは、最後の行も改行で終わらせるのがふつうです。そう覚えておくと混乱しません。 ### 7-3. 日本語の文字数なのに数字が大きすぎる 第 4 章のとおり、`-c` はバイト数を数えます。文字数を知りたいときは `-m` を使います。 ```bash $ wc -m jp.txt # 文字数 $ wc -c jp.txt # バイト数(日本語は約 3 倍) ``` ## 8. ミニ課題:実際にやってみよう {#exercise} > **結論**: 行数カウント・件数カウント・文字数の 3 問で、`wc` の基本とパイプ連携を手で確かめる。 ::: dialogue @lina: 知識は入りました。手を動かして確かめたいです。 @linny: いいね、3 問用意したよ。ターミナルで試してみて。 ::: **課題 1**:次のファイルが何行あるか数えよう。 ```bash $ cat << 'EOF' > memo.txt りんご ばなな みかん ぶどう EOF ``` :::details ヒント 1(方向づけ)を見る 数えたいのは行の数です。行だけを数えるオプションが第 2 章にありました。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `wc` です。オプションは `-l` です。 ::: :::details 答えを見る ```bash $ wc -l memo.txt ``` ```output 4 memo.txt ``` ::: **課題 2**:いまいるディレクトリにある `.txt` ファイルが何個あるか数えよう。 :::details ヒント 1(方向づけ)を見る まず `.txt` の一覧を出します。その一覧が何行あるかを数えれば、それが個数です。 ::: :::details ヒント 2(コマンド名)を見る `ls *.txt` の出力をパイプで `wc -l` に渡します。 ::: :::details 答えを見る ```bash $ ls *.txt | wc -l ``` 数字は環境によって変わります。 ::: **課題 3**:`memo.txt` の **文字数** を数えよう(バイト数ではなく)。 :::details ヒント 1(方向づけ)を見る 数えたいのは人が読む文字の数です。データの大きさではありません。第 4 章の使い分けを思い出してください。 ::: :::details ヒント 2(コマンド名)を見る 使うのは `wc` です。オプションは `-m` です。`-c` を選ぶとバイト数になります。 ::: :::details 答えを見る ```bash $ wc -m memo.txt ``` ```output 16 memo.txt ``` 1 行は「ひらがな 3 文字 + 改行 1 つ」で 4 文字ぶんです。それが 4 行なので合計 16 文字になります。 `wc -m` は改行も 1 文字として数えます。`wc -c memo.txt` も実行して、バイト数との差を見くらべてみてください。 ::: ## 9. コピペ用テンプレート {#templates} > **結論**: 行数・件数・文字数・合計のよく使う型を手元に置いておく。 ::: tip **よく使う型をまとめておく** ```bash # 全部数える(行・単語・バイト) wc file.txt # 行数だけ wc -l file.txt # 数字だけ欲しい(ファイル名なし) wc -l < file.txt # 件数を数える(一覧の数え上げ) ls | wc -l # 検索ヒット件数 grep "keyword" file.txt | wc -l # 日本語の文字数 wc -m file.txt # バイト数(データ容量の目安) wc -c file.txt # 複数ファイルの合計行数 wc -l *.txt ``` ::: ## 10. 振り返り {#review} ::: dialogue @lina: 整理します。`-c` はバイト数で、`-m` が文字数。日本語では結果が変わるんですね。 @linny: そのとおり。UTF-8 では日本語 1 文字が 3 バイトだからね。 @lina: 件数を数えたいときは、コマンドの出力をパイプで `wc -l` に渡す。ここも覚えました。 @linny: 完璧だね。行数は改行の数だという点も、あわせて頭に入れておくといいよ。 ::: ## 今日の 3 行まとめ {#three-lines} 1. `wc` は左から「行数・単語数・バイト数」の順に表示する 2. 日本語の文字数は `-m` で数える。`-c` はバイト数なので数字が大きくなる 3. 件数を数えたいときは `コマンド | wc -l` の形にする ## まとめ:次に読む {#next} - [sort と uniq の使い方](/articles/tutorials/sort-uniq-basics) - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [find・grep・awk の使い方入門](/articles/tutorials/find-grep-awk-basics) # who / w / last 入門 - ログイン中のユーザーと履歴を調べる Source: https://penguin-gym-linux.com/articles/tutorials/who-w-last ## このサーバー、今だれが使ってる? {#intro} ::: dialogue @lina: せんぱい、共有のサーバーで作業していたら、急に動作が重くなったんです。もしかして他の誰かもログインしていますか? そういうのって調べられるんですか? @linny: 調べられるよ。`who` で「今ログインしている人」がわかる。`w` なら「その人が何をしているか」まで見える。`last` は「過去に誰がいつログインしたか」だね。 @lina: 3 つもあるんですね。 @linny: そう。この 3 つはセットで覚えると強いよ。 ::: ::: warning **先に覚える「画面が止まって見えるとき」の抜け方** `last` は履歴をたくさん出す。画面が流れ続けて止まらないように見えることがある。次の方法でぬけられる。 | 見えている状態 | 抜け方 | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | 出力がえんえんと流れて終わらない | `Ctrl+C` を押してやめる | | `:` や `(END)` が出て 1 画面ずつ止まる | ページャ(長い出力を 1 画面ずつ見せる道具。`less` や `more` のこと)が動いている。`q` を押すとぬけられる | | `who` を打っても何も出ない | 失敗ではない。今ログインしている人がいない状態。WSL やコンテナでは記録ファイルが空のこともある | | そもそもたくさん出したくない | `last -n 5` のように件数を先にしぼって実行する | `who` と `w` と `last` はどれも表示するだけのコマンド。押しても設定は変わらないので、安心して試してほしい。 ::: ## この記事でわかること {#what-you-learn} - `who` で **今ログイン中のユーザー** を一覧で見る方法 - `w` で各ユーザーの **作業内容とサーバーの負荷** をたしかめる方法 - `last` で **過去のログイン履歴** をさかのぼる方法 - `last reboot` で **再起動・起動の履歴** を調べる方法 - `who`・`w`(今)と `last`(過去)の **使い分け** ## 1. who / w / last の違いは? {#difference} > **結論**: `who` と `w` は「今ログインしている人」を表示する。`last` は「過去のログイン履歴」を表示する。`w` は `who` より詳しく、各人の作業内容まで見える。 ::: dialogue @lina: 3 つもあると、どれを使えばいいか迷いそうです。 @linny: ざっくり「今」か「過去」かで分けると覚えやすいよ。`who` と `w` は今この瞬間のログイン状況。`last` は履歴だね。 @linny: そのうえで `who` はシンプル。`w` はそこに「何をしているか」が加わるイメージだよ。 ::: ::: highlight **セッションとは** セッションは「1 人のユーザーがログインしてからログアウトするまでの、ひとつながりの利用」のこと。「ログインセッション」「接続」も、ほぼ同じ意味で使われる。同じ人が 2 か所から入れば、セッションは 2 つになる。 ::: | コマンド | 何を見る | 情報の種類 | | -------- | ------------------ | ------------------ | | `who` | 今ログイン中の人 | 今(ライブ) | | `w` | 今+各自の作業内容 | 今(ライブ・詳細) | | `last` | 過去のログイン履歴 | 過去(ログ) | ::: tip `who` と `w` は今のセッション情報(`/run/utmp`。環境によっては存在しない)を読む。`last` は蓄積されたログ(`/var/log/wtmp`)を読む。だから `last` だけは「すでにログアウトした人」も見える。 ::: ## 2. who で今ログイン中の人を見るには? {#who} > **結論**: `who` と打つだけで、今ログインしているユーザー名・端末・ログイン時刻が一覧表示される。 ::: dialogue @lina: まずは「今だれがいるか」ですね。 @linny: そう、`who` をそのまま打つだけ。引数もオプションもいらないよ。 ::: ```bash $ who ``` ```output lina tty1 2026-06-05 09:12 linny pts/0 2026-06-05 10:03 (192.168.1.20) ``` ::: dialogue @lina: 2 人いますね。`tty1` とか `pts/0` って何ですか? @linny: ログインしている「端末」の名前だよ。端末(ターミナル、コンソールも同じものを指す呼び方)は「入力と出力の窓口」だと思ってほしい。 @linny: `tty1` は本体に直接つないだコンソール。`pts/0` はネットワーク越し(SSH など)の仮想端末だね。右端の `(192.168.1.20)` は接続元の IP アドレス。リモートログインのときに出るよ。 ::: ::: tip **列の意味(左から)** - ユーザー名 - 端末(`tty` = 物理端末、`pts` = 擬似端末=リモート接続) - ログイン日時 - (あれば)接続元のホスト名 / IP ::: ::: dialogue @lina: 「今ログインしているのは自分だよね?」と確認したいときは? @linny: `who am i` と打つと、自分のセッションの行だけが出るよ。スペースを入れて 3 単語で打つのがポイント。 ::: ```bash $ who am i ``` ```output linny pts/0 2026-06-05 10:03 (192.168.1.20) ``` ### リナの失敗:whoami と who am i を混同する {#lina-mistake} ::: dialogue @lina: せんぱい、`whoami` を打ったのに、名前しか出ません。端末も時刻も出ないんですけど...。 @linny: それは別のコマンドを打っているからだよ。`whoami`(スペースなし・1 単語)は **ユーザー名だけ** を表示する別コマンドなんだ。 @lina: えっ、`who` の仲間じゃないんですか? @linny: 見た目は似ているけれど別ものだね。`who am i`(スペースあり・3 単語)なら、`who` の出力から自分の行だけを抜き出してくれるよ。 @lina: なるほど...!スペースの有無で別のコマンドになるんですね。打つ前にたしかめます。 ::: ::: warning `who am i`(スペースあり・3 単語)と `whoami`(スペースなし・1 単語)は別物。`whoami` は `who` のオプションではない。混同しやすいので気をつけよう。 ::: ## 3. w で各ユーザーの作業内容を見るには? {#w} > **結論**: `w` は `who` の情報に加えて、各ユーザーが今どんなコマンドを実行しているか、サーバーの負荷(load average)まで一画面で表示する。 ::: dialogue @lina: 重くなった原因を知りたいんですけど、誰が何をしているかまで見えますか? @linny: それなら `w` の出番。一番上にサーバー全体の状態が出る。その下に、各ユーザーが今打っているコマンドまで並ぶよ。 ::: ```bash $ w ``` ```output 10:15:32 up 2 days, 3:41, 2 users, load average: 0.15, 0.10, 0.05 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT lina tty1 - 09:12 1:03m 0.20s 0.20s -bash linny pts/0 192.168.1.20 10:03 2.00s 0.10s 0.05s w ``` ::: dialogue @lina: 一番上の行、情報が多いです... @linny: 左から「現在時刻」「稼働時間(up)」「ログイン中の人数」「load average(負荷)」だよ。 @linny: load average は数字が大きいほど混んでいる目安。3 つ並ぶのは「1 分・5 分・15 分の平均」なんだ。 ::: ::: highlight **load average(負荷平均)とは** load average は「順番待ちを含めて、どれだけの仕事が積まれているか」を表す数字。混雑度の目安と考えてよい。レジの行列に例えると、並んでいる人数に近い。数字が小さいほど空いている。 ::: ::: tip **w の主な列** - `USER` / `TTY` / `FROM`:誰が・どの端末で・どこから - `LOGIN@`:ログインした時刻 - `IDLE`:操作していない時間(長いほど放置中) - `JCPU` / `PCPU`:CPU をどれだけ使ったかの時間(最初は読み飛ばしてよい) - `WHAT`:今実行しているコマンド ::: ::: dialogue @lina: `WHAT` を見ると、私は `-bash`(シェルを開いているだけ)ですね。せんぱいは `w` を実行中、と。 @linny: その通り。誰がどの作業で負荷をかけているか、あたりをつけられる。 @linny: ヘッダー行がじゃまなときは `w -h` で消せるよ。`w lina` のように名前を付ければ、そのユーザーだけに絞れる。 ::: ```bash $ w -h lina ``` ```output lina tty1 - 09:12 1:03m 0.20s 0.20s -bash ``` ## 4. last で過去のログイン履歴を見るには? {#last} > **結論**: `last` は過去のログイン履歴を新しい順に表示する。すでにログアウトした人も含めて「いつ誰がどこからログインしたか」をさかのぼれる。 ::: dialogue @lina: 「今」ではなく「昨日の夜にログインしたのは誰か」みたいに、過去を調べたいときは? @linny: それは `last` だね。ログインとログアウトの記録が、新しい順にずらっと出るよ。 @linny: 件数が多くなりやすい。最初は `-n` で件数をしぼるのがおすすめ。 ::: ```bash $ last -n 5 ``` ```output linny pts/0 192.168.1.20 Fri Jun 5 10:03 still logged in lina tty1 Fri Jun 5 09:12 still logged in linny pts/0 192.168.1.20 Thu Jun 4 18:40 - 19:55 (01:15) lina pts/1 192.168.1.31 Thu Jun 4 14:02 - 17:30 (03:28) reboot system boot 6.8.0-generic Thu Jun 4 08:00 still running ``` ::: dialogue @lina: `still logged in` と、`- 19:55 (01:15)` みたいな書き方がまざっています。 @linny: いいところに気づいたね。`still logged in` は「今もログイン中」という意味。 @linny: `18:40 - 19:55 (01:15)` は「18:40 にログインして 19:55 にログアウト、滞在は 1 時間 15 分」だよ。過去の人はログアウト時刻と滞在時間までわかるんだ。 ::: ::: tip `last -n 5` は新しい 5 件だけ表示する。`last -5` と書いても同じ。履歴はたまりやすいので、まず件数をしぼってからさがすと見やすい。 ::: ## 5. 再起動や特定ユーザーを調べるには? {#reboot-filter} > **結論**: `last reboot` で再起動・起動の履歴、`last ユーザー名` で特定ユーザーのログイン履歴だけに絞り込める。 ::: dialogue @lina: さっきの履歴に `reboot` という行がありましたね。サーバーがいつ再起動したかもわかるんですか? @linny: わかるよ。`last reboot` と打つと、起動した時刻の履歴だけが並ぶ。「いつ落ちた / いつ立ち上がった」を追うときに便利なんだ。 ::: ```bash $ last reboot ``` ```output reboot system boot 6.8.0-generic Thu Jun 4 08:00 still running reboot system boot 6.8.0-generic Mon Jun 1 07:55 - Jun 4 07:58 (2+23:03) ``` ::: dialogue @lina: 特定の人、たとえば私のログイン履歴だけを見たいときは? @linny: `last` の後ろにユーザー名をつけるだけ。`last lina` なら lina のログインだけが並ぶよ。 ::: ```bash $ last lina ``` ```output lina tty1 Fri Jun 5 09:12 still logged in lina pts/1 192.168.1.31 Thu Jun 4 14:02 - 17:30 (03:28) lina tty1 Wed Jun 3 09:30 - 18:10 (08:40) ``` ::: warning **ログインの失敗** を調べたいときは `last` ではなく `lastb`(btmp ログ)を使う。`lastb` は管理者の権限がいるので `sudo lastb` と打つ。不正アクセスの兆候(知らない IP からの大量の失敗)を確認するのに役に立つ。 ::: ## 6. ミニ課題で手を動かそう {#practice} > **結論**: 「今いる人数」「直近の履歴」「再起動の履歴」の 3 課題で、今と過去の見分け方を体に入れる。 ::: dialogue @linny: 読むだけでは身につかない。3 つ手を動かしてみよう。 ::: ### 課題 1: 今ログインしている人数を調べる {#challenge-1} **やること**: 今このサーバーに何人ログインしているかをたしかめよう。 :::details ヒント1(方向づけ)を見る 「今」を見るコマンドは 2 つある。人数そのものを 1 行目に出してくれる方を選ぼう。 ::: :::details ヒント2(コマンド名)を見る 使うのは `w`。1 行目のヘッダーに `users` という表示がある。`who` の行数を数えてもわかる。 ::: :::details 答えを見る ```bash $ w ``` ```output 10:15:32 up 2 days, 3:41, 2 users, load average: 0.15, 0.10, 0.05 ``` `2 users` の部分が人数。 ::: ### 課題 2: 直近 3 件のログイン履歴を見る {#challenge-2} **やること**: 過去のログイン履歴を、新しい方から 3 件だけ表示しよう。 :::details ヒント1(方向づけ)を見る 「過去」を見るコマンドを使う。そのままだと大量に出るので、件数をしぼるオプションを付けよう。 ::: :::details ヒント2(コマンド名)を見る 使うのは `last`。件数をしぼるのは `-n 件数`。`last -3` という書き方でも同じ。 ::: :::details 答えを見る ```bash $ last -n 3 ``` ```output linny pts/0 192.168.1.20 Fri Jun 5 10:03 still logged in lina tty1 Fri Jun 5 09:12 still logged in linny pts/0 192.168.1.20 Thu Jun 4 18:40 - 19:55 (01:15) ``` ::: ### 課題 3: サーバーの再起動履歴を見る {#challenge-3} **やること**: このサーバーがいつ起動したかの履歴を表示しよう。 :::details ヒント1(方向づけ)を見る 履歴を見るコマンドは、後ろに言葉をつけると絞り込める。起動を表す言葉をつけてみよう。 ::: :::details ヒント2(コマンド名)を見る 使うのは `last`。後ろに `reboot` をつける。 ::: :::details 答えを見る ```bash $ last reboot ``` ```output reboot system boot 6.8.0-generic Thu Jun 4 08:00 still running ``` ::: ::: dialogue @lina: できました!「今いる人は `who` と `w`、過去は `last`」で覚えられそうです。 @linny: その分け方が本質だよ。困ったらまず `w` を打つ。人数も負荷も作業内容も、1 画面でわかるからね。 ::: ## 7. よくあるつまずきと対処 {#pitfalls} > **結論**: つまずきの多くは「`whoami` と `who am i` の混同」「`last` の `still logged in` の読み違い」「ログイン失敗を `last` でさがしてしまう」の 3 つ。 | 症状 | 原因 | 対処 | | ------------------------------------ | -------------------------- | --------------------------------- | | ユーザー名しか出ない | `whoami` を使っている | 一覧なら `who`、詳細なら `w` | | `who am i` がエラーになる | スペースの付け方が違う | 半角スペースで 3 単語 `who am i` | | `last` の出力が大量で読めない | 全履歴を表示している | `last -n 10` などで件数をしぼる | | ログイン失敗が `last` に出てこない | 成功ログ(wtmp)を見ている | 失敗は `sudo lastb`(btmp)で見る | | `still logged in` の意味がわからない | 滞在時間の表記と混同 | 「今もログイン中」の意味 | | `last` が「wtmp begins ...」の 1 行しか出ない | 履歴ファイル(`/var/log/wtmp`)が空 | WSL やコンテナではよくある。実サーバか VM で試す | ::: tip まよったらこの順で。**今いる人を知りたい → `who`**、**今いる人が何をしているか・負荷 → `w`**、**過去にさかのぼりたい → `last`**。 ::: ## 今日の 3 行まとめ {#summary} - **`who` は今いる人の一覧**、**`w` はそこに作業内容と負荷が加わる**。まずこの 2 つで現状をおさえる - **`last` は過去の履歴**。`last -n 5` で件数を絞り、`last reboot` で起動の履歴だけを見られる - `whoami`(1 単語)と `who am i`(3 単語)は別のコマンド。ログイン失敗を追うときは `sudo lastb` ## まとめ / 次に読む {#next} - [ps・top・kill の使い方(プロセス管理入門)](/articles/tutorials/process-management-basics) - [passwd / chage 入門(パスワードと有効期限の管理)](/articles/tutorials/passwd-chage-aging) - [journalctl の使い方(システムログを読む)](/articles/tutorials/journalctl-basics) # xargs 実践活用 - 標準入力をコマンド引数に変換する Source: https://penguin-gym-linux.com/articles/tutorials/xargs-practical ## この記事で解決できること {#intro} - `xargs` の **正しい使い分け** が分かる(パイプで渡すだけでは動かないコマンドが動くようになる) - 「`find | xargs rm` でファイル名にスペースが入ってると壊れる」**定番事故** を回避できる - `-I {}` / `-n` / `-P` で **複雑な置換・分割・並列実行** ができるようになる ::: tip **結論(実務の型)** - まずは **`find ... -print0 | xargs -0 コマンド`** を覚える(スペース対応の安全形) - 引数を中間に挟みたいなら **`-I {}`** - 並列で速くしたいなら **`-P N`** - 空入力で全件爆発を防ぐなら **`-r`**(GNU 拡張) ::: ::: warning **前提(対象環境)** - OS:Linux(GNU findutils)/ macOS は BSD 版なので一部オプションが異なる - 動作確認は Ubuntu 22.04 / GNU xargs (findutils) 4.8.0 系 - パイプ・標準入出力の基本は [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) を参照 ::: ## xargs とは何か?なぜ必要なのか? {#what-is-xargs} `xargs` は **標準入力を読み取って、それを別コマンドの引数として組み立てる** ためのツール。パイプは「標準出力 → 標準入力」しか渡せないため、引数として受け取りたいコマンド(`rm` / `mv` / `cp` 等)と直接つなぐと動かない。 ```bash # NG: rm は標準入力からファイル名を読まない(何も削除されない / エラー) find . -name "*.tmp" | rm # OK: xargs が標準入力を引数に変換して rm に渡す find . -name "*.tmp" | xargs rm ``` ::: highlight **1 行で言うと**:`xargs` は「パイプの中身を、次のコマンドの **引数欄** に貼り付けてくれる」ツール。 ::: ## 1. 基本形:パイプから引数を組み立てる {#basic} ```bash $ echo "a b c" | xargs echo a b c ``` `xargs` は標準入力をスペース/改行で区切り、まとめて次のコマンドに渡す。 ### 1-1. find と組み合わせる定番形 ```bash $ find . -name "*.log" | xargs ls -lh ``` `find` で見つけたファイル群を `ls -lh` の引数として渡す。 ### 1-2. ARG_MAX を意識せず大量ファイルを処理 シェルの直接展開(`ls *.log`)は **ARG_MAX** で打ち止めになるが、`xargs` は適切に分割実行する。 ```bash # 数十万ファイルを処理しても "Argument list too long" にならない $ find /var/log -name "*.gz" | xargs gzip -t ``` ## 2. なぜ -print0 と -0 を使うのか? {#null-separator} ファイル名に **スペース・タブ・改行・引用符** が含まれると、デフォルトの `xargs` は誤った区切りで分解する。これが xargs 最大の事故源。 ### 2-1. 危険な例 ```bash # ファイル名「my file.txt」がスペースで分割され、 # "my" と "file.txt" の 2 ファイルを削除しようとする $ find . -name "*.txt" | xargs rm ``` ### 2-2. 安全形:NUL 区切りペア ```bash $ find . -name "*.txt" -print0 | xargs -0 rm ``` - `find -print0`:区切り文字を **NUL (\0)** にする - `xargs -0`:区切り文字を NUL として読む NUL はファイル名に含めることが不可能な唯一の文字なので、確実に区切れる。 ::: warning **find と xargs を使うなら、ペアで `-print0` / `-0` を付けるのを反射にする**。 これだけで事故の 8 割は防げる。 ::: ### 2-3. grep -l / grep -rl も同様 ```bash # 危険 $ grep -rl "TODO" . | xargs sed -i 's/TODO/DONE/g' # 安全形 $ grep -rlZ "TODO" . | xargs -0 sed -i 's/TODO/DONE/g' ``` `grep -Z` も NUL 区切り出力。`grep -rl` と `xargs` の組み合わせ時は `-Z` / `-0` ペアを使う。 ## 3. -I {} プレースホルダ:引数を任意位置に挿入する {#placeholder} デフォルトの `xargs` は **引数を末尾に追加** する。中間や複数箇所に置きたいなら `-I {}` を使う。 ### 3-1. 末尾固定の限界 ```bash # NG: mv は「移動元 移動先」の順なので、末尾追加だと壊れる $ ls *.bak | xargs mv archive/ # → mv archive/ file1.bak file2.bak ... のようになる(archive/ が移動元扱い) ``` ### 3-2. -I {} で引数位置を指定 ```bash $ ls *.bak | xargs -I {} mv {} archive/ # → mv file1.bak archive/ # mv file2.bak archive/ # (1 件ずつ実行される) ``` `{}` の部分が標準入力の **1 件ずつ** に置換される。 ### 3-3. 複数回置換も可能 ```bash $ cat hosts.txt | xargs -I {} ssh {} "hostname && uptime" ``` ::: tip `-I {}` を使うと **自動的に 1 件ずつ実行** される(バッチ実行されない)。大量実行時は次の `-n` / `-P` を併用。 ::: ## 4. -n / -P でバッチサイズと並列度を制御 {#batch-parallel} ### 4-1. -n N:N 個ずつ引数を渡す ```bash $ seq 1 10 | xargs -n 3 echo 1 2 3 4 5 6 7 8 9 10 ``` 1 行 3 引数ずつ `echo` に渡される。 ### 4-2. -P N:N 並列で実行 ```bash # 4 並列で .gz ファイルを検証 $ find . -name "*.gz" -print0 | xargs -0 -n 1 -P 4 gzip -t ``` - `-n 1`:1 件ずつ独立して - `-P 4`:同時に最大 4 プロセス `-P 0` で「コア数まで自動」(GNU 拡張)。CPU バウンドな処理を一気に速くできる。 ::: warning 並列実行の注意点: - 出力順は **不定** になる(並列でログを出すと混ざる) - DB 接続・I/O を伴う処理は **接続数の上限** に注意 - 失敗時のリカバリが難しいので、本番では `--dry-run` 相当の確認を先に ::: ## 5. 事故防止:-r / -t / -p で安全に動かす {#safety} ### 5-1. -r:空入力時は実行しない(必須レベル) ```bash # 危険: find が何もマッチしなくても rm が呼ばれる(引数なしで暴発する可能性) $ find . -name "存在しない条件" | xargs rm # 安全: 入力が空なら何もしない $ find . -name "存在しない条件" | xargs -r rm ``` ::: danger **最悪のケース**:`find ... | xargs rm -rf` で find が 0 件ヒット、かつ後続の `rm -rf` が引数なしで `.` を見るような状況は壊滅的。`-r` を反射で付ける。 ::: GNU xargs はデフォルトで空入力を渡すが、`--no-run-if-empty`(`-r`)で抑止できる。BSD xargs(macOS)はデフォルトで空入力時に実行しないため、移植性を意識して `-r` を明示するのが安全。 ### 5-2. -t:実行コマンドを表示(dry-run の代替) ```bash $ find . -name "*.log" | xargs -t rm rm ./access.log ./error.log ``` `-t` は実行前に組み立てたコマンドを `stderr` に出力する。**実際には実行する** ので、本番前は次の `echo` トリックで確認。 ### 5-3. echo トリック:dry-run ```bash # 本当に実行する前に、組み立て結果を確認 $ find . -name "*.tmp" -print0 | xargs -0 echo rm rm ./a.tmp ./b.tmp ./c.tmp ``` 頭に `echo` を挟むと、組み立て結果が「出力されるだけ」になる。問題なければ `echo` を外して実行。 ### 5-4. -p:1 件ずつ実行確認(対話モード) ```bash $ find . -name "*.tmp" -print0 | xargs -0 -p rm rm ./a.tmp ./b.tmp ?...y ``` `y` 入力時のみ実行。慎重に進めたい時に。 ## 6. xargs と find -exec の使い分けは? {#vs-exec} `find` だけでも `-exec` で同等のことが可能。どちらを使うべきか。 | 観点 | `find -exec` | `xargs` | | ---------------- | -------------------------- | -------------------------- | | パフォーマンス | 1 件ずつ exec(遅い) | バッチで exec(速い) | | `+` 終端 | `-exec ... +` でバッチ化可 | デフォルトでバッチ | | ファイル名安全性 | 標準で安全(NUL 不要) | `-print0` / `-0` 必須 | | 並列実行 | 不可 | `-P` で可能 | | 任意の標準入力 | 不可(find 限定) | 可(任意のコマンドの出力) | | 可読性 | 1 行で完結 | パイプで明示的 | ### 6-1. find だけで完結するなら -exec で十分 ```bash # シンプルで安全 $ find . -name "*.tmp" -exec rm {} + ``` `-exec ... +` を使えば内部的にバッチ化される。ファイル名にスペースがあっても安全。 ### 6-2. xargs を選ぶケース - `find` 以外の出力(`grep -l` / `ls` / `cat list.txt`)を引数化したい - **並列実行** (`-P`) で高速化したい - パイプの中で他コマンドの出力と組み合わせたい ::: tip **判断基準**:find 単体で済むなら `-exec ... +`、並列・他コマンドと組み合わせるなら `xargs`。 ::: ## 7. 実務テンプレート集 {#templates} ::: tip **安全テンプレ(コピペ用)** ```bash # 1. 大量ファイル削除(スペース対応) find /tmp -name "*.cache" -mtime +7 -print0 | xargs -0 -r rm # 2. ファイル一覧から移動(プレースホルダ) cat filelist.txt | xargs -I {} mv {} /archive/ # 3. 並列でファイル圧縮(4 並列) find . -name "*.log" -print0 | xargs -0 -n 1 -P 4 gzip # 4. 複数ファイルに一括置換 grep -rlZ "old-string" ./src | xargs -0 sed -i 's/old-string/new-string/g' # 5. リモートホストへ並列 ssh cat hosts.txt | xargs -I {} -P 8 ssh {} "uptime" # 6. 本番前の dry-run find . -name "*.bak" -print0 | xargs -0 echo rm ``` ::: ::: warning **やってはいけないこと** - `find | xargs rm` で `-print0` / `-0` を付け忘れる - `xargs -r` 無しで空入力に対する保険を取らない - `-P` で並列実行する処理を本番でいきなり試す(先に小規模で確認) - 出力順が重要な処理に `-P` を使う(並列で混ざる) ::: ## 次に読む {#next} - [find・grep・awk の組み合わせ方](/articles/tutorials/find-grep-awk-practical) - [find の安全な使い方:削除で事故らない](/articles/troubleshooting/find-safe-delete) - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [シェルスクリプトの書き方入門](/articles/tutorials/shell-scripting-basics) # gzip / xz / zstd / bzip2 の違いと選び方 - Linux 圧縮形式の比較 Source: https://penguin-gym-linux.com/articles/tutorials/xz-gzip-bzip2-compare ## 4 つの圧縮形式、どれを選べばいい? {#intro} > **結論**: 迷ったら **zstd**。速度・圧縮率・並列対応のバランスが最良。配布互換性なら gzip、極限の圧縮率なら xz、bzip2 は新規採用の理由がほぼない。 Linux でファイルを圧縮するとき、`gzip` / `bzip2` / `xz` / `zstd` の 4 つが定番の選択肢になる。どれも `tar` と組み合わせて使えるが、**圧縮率・圧縮速度・展開速度・並列対応**が大きく異なる。 ::: tip **実務の早見表** | 形式 | 拡張子 | 圧縮率 | 圧縮速度 | 展開速度 | 一言 | | ----- | ------ | ------ | -------- | -------- | ---------------- | | gzip | `.gz` | 低 | 速 | 速 | 互換性の王者 | | bzip2 | `.bz2` | 中 | 遅 | 遅 | 旧世代、出番減 | | xz | `.xz` | 高 | 最も遅い | 中 | 圧縮率重視 | | zstd | `.zst` | 中〜高 | 速い | 最速 | 万能・現代の本命 | ::: ::: warning **前提(対象環境)** - 一般的な Linux ディストリビューション(Ubuntu / RHEL 系など) - `zstd` は古い環境では未インストールの場合がある(`apt install zstd` / `dnf install zstd`) ::: ## 各形式の特徴は? {#features} > **結論**: gzip は DEFLATE で速くて互換性抜群、bzip2 は BWT で圧縮率は中程度だが遅い、xz は LZMA2 で最高圧縮率、zstd は高速かつ調整幅が広い。 ### gzip — 互換性の標準 `gzip` は **DEFLATE**(LZ77 + ハフマン符号)を使う。1992 年から存在し、ほぼあらゆる環境にインストール済み。圧縮率は 4 つの中で最も低いが、**速くてどこでも展開できる**のが最大の強み。HTTP の `Content-Encoding: gzip` をはじめ、配布フォーマットのデファクトスタンダード。 ### bzip2 — BWT 採用の旧世代 `bzip2` は **Burrows-Wheeler 変換(BWT)** によるブロックソート圧縮。gzip より圧縮率は高いが、**圧縮も展開も遅い**。かつては「gzip より縮む」枠だったが、現在は xz・zstd に圧縮率でも速度でも劣るため、**新規に選ぶ理由はほぼない**。既存の `.bz2` を展開する用途が中心。 ### xz — 圧縮率の最高峰 `xz` は **LZMA2** アルゴリズムを使い、4 つの中で**最も高い圧縮率**を出す。その代わり圧縮は最も遅く、高レベルではメモリも多く消費する。**一度圧縮して何度も配布する**(カーネルソース、ディストリのパッケージなど)用途に向く。 ### zstd — 現代の本命 `zstd`(Zstandard)は速度と圧縮率のバランスに優れた比較的新しい形式。**gzip 並みの速度で gzip より高い圧縮率**を実現し、レベルを上げれば xz に迫る圧縮率も狙える。**展開が非常に速い**点も大きく、Linux カーネル・btrfs・各種パッケージ管理で採用が進んでいる。 ## 圧縮率と速度はどう違う? {#tradeoff} > **結論**: 圧縮率は xz ≧ zstd(高レベル) > bzip2 > gzip。展開速度は zstd > gzip > xz > bzip2。zstd は「速くてそこそこ縮む」を一台で満たす。 圧縮の世界は基本的に**「縮むほど遅い」というトレードオフ**で動く。各形式の傾向は次の通り。 - **圧縮率**: `xz` が最高。`zstd` は高レベルなら xz に迫る。`bzip2` は中程度、`gzip` が最も低い - **圧縮速度**: `gzip` と `zstd`(低〜中レベル)が速い。`bzip2` は遅く、`xz` は最も遅い - **展開速度**: `zstd` が最速。`gzip` も速い。`xz` は中程度、`bzip2` が最も遅い ::: tip 重要なのは**展開速度**。圧縮は 1 回でも、展開は配布先で何度も走る。多数のサーバや CI で繰り返し展開するなら、`zstd` の展開の速さがそのまま効いてくる。 ::: ::: warning 具体的な数値はデータの種類(テキスト / バイナリ / 既圧縮)と CPU で大きく変わる。**自分の代表データで実測する**のが唯一の正解。`time` と `ls -l` で計測すればよい。 ```bash $ for c in gzip bzip2 xz zstd; do \ echo "== $c =="; \ time $c -k -9 -f sample.dat; \ ls -l sample.dat.* ; rm -f sample.dat.{gz,bz2,xz,zst}; \ done ``` ::: ## どう使い分ければいい? {#choose} > **結論**: 配布互換性なら gzip、ディスク削減を最優先するなら xz、それ以外の大半は zstd でよい。bzip2 は既存ファイルの展開専用。 判断フローはシンプル。 1. **相手の環境で確実に展開できる必要があるか?**(古い環境・他人へ配布) → **gzip**(`.gz` はどこでも展開できる) 2. **保存容量・転送量を 1 バイトでも削りたいか?**(アーカイブ、長期保管、配布回数が多い) → **xz**(圧縮は遅いが最も縮む) 3. **上記以外(バックアップ、ログ、日常作業の大半)** → **zstd**(速い・よく縮む・展開が最速) 4. **`.bz2` を受け取った/既存資産がある** → **bzip2** で展開(新規圧縮には使わない) ::: highlight **ワンライン指針** - 迷ったら `zstd` - 「相手に渡す」なら `gzip` - 「とにかく小さく」なら `xz` ::: ## 基本的な使い方は? {#usage} > **結論**: 4 形式とも `cmd file` で圧縮、`cmd -d file.ext` で展開、`-k` で元ファイルを残す、という共通の操作体系を持つ。 単体ファイルの圧縮・展開は、どのコマンドもほぼ同じ作法で扱える。 ```bash # 圧縮(元ファイルは消える点に注意) $ gzip file.txt # → file.txt.gz $ bzip2 file.txt # → file.txt.bz2 $ xz file.txt # → file.txt.xz $ zstd file.txt # → file.txt.zst(元ファイルは残る) # 元ファイルを残して圧縮(-k = keep) $ gzip -k file.txt $ xz -k file.txt # 展開(-d = decompress) $ gzip -d file.txt.gz $ xz -d file.txt.xz $ zstd -d file.txt.zst # 標準の展開専用コマンドもある $ gunzip file.txt.gz $ bunzip2 file.txt.bz2 $ unxz file.txt.xz $ unzstd file.txt.zst ``` ::: warning `gzip` / `bzip2` / `xz` は**デフォルトで元ファイルを削除**する。残したい場合は `-k`(keep)を付ける。`zstd` は逆に**デフォルトで元ファイルを残す**ため、消したい場合は `--rm` を付ける。挙動が逆なので注意。 ::: 中身を展開せずに確認したいときは、各コマンドの `-c`(標準出力へ)や `zcat` / `bzcat` / `xzcat` / `zstdcat` が使える。 ```bash $ zcat access.log.gz | grep 500 $ zstdcat backup.tar.zst | tar tf - ``` ## 圧縮レベルとマルチスレッドは? {#level-thread} > **結論**: いずれも `-1`〜`-9` でレベル指定(数字が大きいほど高圧縮・低速)。xz と zstd は `-T0` で全 CPU コアを使った並列圧縮ができ、大きく時短できる。 ### 圧縮レベル 数字を上げるほど縮むが遅くなる。代表的なデフォルトは以下。 - `gzip`: `-1`〜`-9`、デフォルト `-6` - `bzip2`: `-1`〜`-9`、デフォルト `-9`(ブロックサイズ) - `xz`: `-0`〜`-9`、デフォルト `-6` - `zstd`: `-1`〜`-19`、デフォルト `-3`。さらに `--ultra -22` で最大レベルまで引き上げられる ```bash $ gzip -9 file # 最大圧縮 $ xz -9 file # 高圧縮(遅い・メモリ多め) $ zstd -19 file # zstd の通常最大 $ zstd --ultra -22 file # zstd の最大圧縮 ``` ### マルチスレッド(並列化) 大きなファイルでは並列化が効く。 ```bash $ xz -T0 big.tar # 全コアを使って圧縮 $ zstd -T0 big.tar # 全コアを使って圧縮(0 = 自動) ``` ::: tip `gzip` / `bzip2` 自体は並列化に対応しないが、互換の並列実装がある。`pigz`(parallel gzip)と `pbzip2` / `lbzip2` を入れれば、`.gz` / `.bz2` 形式のまま全コアを活用できる。 ::: ## tar と組み合わせるには? {#tar} > **結論**: `tar` は `-z`(gzip) / `-j`(bzip2) / `-J`(xz) のショートカットを持つ。zstd は `--zstd`、または拡張子から自動判定する `-a`(`caf`)が便利。 複数ファイルをまとめるアーカイブ(`tar`)と圧縮は別の処理。`tar` のオプションで圧縮形式を指定する。 ```bash # 作成(c = create, f = file) $ tar czf archive.tar.gz dir/ # gzip $ tar cjf archive.tar.bz2 dir/ # bzip2 $ tar cJf archive.tar.xz dir/ # xz $ tar --zstd -cf archive.tar.zst dir/ # zstd # 展開時は形式を指定しなくても自動判定される $ tar xf archive.tar.gz $ tar xf archive.tar.zst ``` ::: tip **拡張子で自動判定(-a)** `tar` の `-a`(`--auto-compress`)を使うと、出力ファイル名の拡張子から圧縮形式を選んでくれる。形式を変えてもオプション(`z` / `j` / `J`)を覚え直さなくてよい。 ```bash $ tar caf archive.tar.zst dir/ # .zst → zstd $ tar caf archive.tar.xz dir/ # .xz → xz $ tar caf archive.tar.gz dir/ # .gz → gzip ``` ::: ::: warning 古い `tar`(とくに非 GNU 環境)では `--zstd` や `-a` が使えないことがある。その場合はパイプで繋ぐ。 ```bash $ tar cf - dir/ | zstd -T0 -o archive.tar.zst $ zstd -dc archive.tar.zst | tar xf - ``` ::: ## まとめと次の一歩 {#next} - **迷ったら zstd**。速度・圧縮率・展開速度・並列対応のバランスが最良 - **配布・互換性なら gzip**、**極限の圧縮率なら xz** - **bzip2 は新規採用の理由が薄い**(既存 `.bz2` の展開専用) - レベルは `-1`〜`-9`(zstd は `-19`/`--ultra -22`)、`-T0` で並列化 - 数値は環境依存。**自分の代表データで実測**してから決める 次に読む記事: - [tar の基本:圧縮・解凍・よくある事故の防ぎ方](/articles/tutorials/tar-basics) - [split で大きなファイルを分割する](/articles/tutorials/split-command) - [チェックサムで改ざん・破損を検証する](/articles/tutorials/checksum-md5-sha256) # yes コマンド入門 - 対話プロンプトを自動応答する Source: https://penguin-gym-linux.com/articles/tutorials/yes-command ## この記事でわかること {#intro} - **yes コマンド** が何をするコマンドなのか分かる - 「本当に削除しますか? (y/n)」のような **対話プロンプトに自動で答える** 方法が分かる - yes コマンドが **なぜ危険になりうるか** と、その回避方法が分かる - `-y` オプションなど、yes より **安全な代替手段** が分かる ::: tip **結論(先に覚えるべき型)** - とりあえず全部 `y` で答えたい → `yes | コマンド` - でも基本は **コマンド自身の `-y` オプション** を使うほうが安全 - yes を止めるには **Ctrl + C** ::: ## 1. yes コマンドって何? {#what} > **結論**: yes は `y` という 1 文字を、止めるまで無限に出力し続けるだけのコマンド。 ::: dialogue @lina: 先輩、`yes` っていう名前のコマンドがあるって聞いたんですけど、何をするんですか? 「はい」って答えるコマンド? @linny: いい質問。`yes` は名前のとおりだけど、もっと単純だよ。**`y` という文字を、止めるまでひたすら出力し続けるだけ** のコマンドなんだ。 @lina: ひたすら? 試しに打ってみますね。 @linny: ちょっと待った。そのまま打つと画面が `y` で埋め尽くされて止まらなくなるよ。止め方を先に教えるね。**Ctrl + C** を押せば止まる。それを覚えてから試してみて。 ::: 実際に打つとこうなる(すぐ `Ctrl + C` で止めること)。 ```bash $ yes ``` ```output y y y y (Ctrl + C で止めるまで延々と続く) ``` ::: warning `yes` を引数なしで打つと **無限に `y` が出力され続ける**。慌てず **Ctrl + C** を押して止めること。これは故障ではなく、yes の正常な動作。 ::: ## 2. なぜ「無限に y を出す」コマンドが役に立つの? {#why} > **結論**: 「本当に実行しますか? (y/n)」と何度も聞いてくるコマンドに、その y を自動で流し込むため。 ::: dialogue @lina: でも、`y` を無限に出すなんて、何の役に立つんですか? @linny: 単体だとただのいたずらみたいだよね。真価は **パイプ(`|`)と組み合わせたとき** に出るんだ。 @lina: パイプ……前にやった「左の出力を右に渡す」やつですね。 @linny: そう。たとえばファイルをたくさん消すとき、`rm` が 1 個ずつ「本当に消す? (y/n)」と聞いてくることがある。100 個あったら 100 回 `y` を打つことになる。そこで yes の出番。 ::: ::: highlight **yes が活躍する場面** 「本当に実行しますか? (y/n)」のような **確認プロンプトを何度も出すコマンド** に対して、その `y` を自動で供給する。これで人間が何度も `y` を打つ手間が省ける。 ::: ## 3. パイプで自動応答する:yes | コマンド {#pipe} > **結論**: `yes | コマンド` で、コマンドが出す確認プロンプトすべてに自動で `y` が渡される。 たとえば、`-i`(確認あり)オプション付きの `rm` は、削除のたびに確認してくる。 ```bash $ rm -i file1.txt file2.txt file3.txt ``` ```output rm: remove regular file 'file1.txt'? ``` ここで毎回 `y` を打つ代わりに、`yes` をパイプでつなぐと、すべての確認に自動で `y` が渡される。 ```bash $ yes | rm -i file1.txt file2.txt file3.txt ``` ::: dialogue @lina: `yes |` を頭に付けただけで、全部の確認に答えてくれるんですね! @linny: そういうこと。yes が `y` を流し続けて、`rm` がそれを 1 個ずつ受け取って消していく。確認の回数がいくつでも対応できるよ。 ::: ::: warning これは便利だが **同時にとても危険**。確認をすべて飛ばすということは、**間違ったファイルも問答無用で消える** ということ。実行前にコマンドの対象を必ず確認すること。詳しくは後の「yes の危険性」で扱う。 ::: ## 4. 任意の文字列を繰り返す:yes STRING {#string} > **結論**: `yes` に引数を付けると、`y` の代わりにその文字列を繰り返し出力する。 `yes` は `y` 専用ではない。引数を渡すと、その文字列を繰り返す。 ```bash $ yes hello ``` ```output hello hello hello (Ctrl + C まで続く) ``` 複数の単語を渡すと、スペースでつないで 1 行にする。 ```bash $ yes I am ready ``` ```output I am ready I am ready (Ctrl + C まで続く) ``` ::: tip **空文字を繰り返す(Enter 連打の代わり)** `yes ""` とすると、空の行(改行だけ)を繰り返す。「ただ Enter を押し続ける」だけでよいプロンプトに使える。 ```bash $ yes "" | コマンド ``` ::: ## 5. 回数を区切って使う:yes | head {#head} > **結論**: `yes` を `head` とつなぐと、無限ではなく決まった行数だけ取り出せる。テストデータ作りに便利。 無限なのが困る場合は、`head` で必要な行数だけ取り出す。 ```bash $ yes | head -n 5 ``` ```output y y y y y ``` 任意の文字列でも同じ。同じ行を 3 つ並べたテキストが一瞬で作れる。 ```bash $ yes test | head -n 3 ``` ```output test test test ``` ::: dialogue @lina: 無限ループを `head` で「ここまで」と切るんですね。 @linny: そう。yes はコマンドの動作確認やテスト用のダミーデータを作るときにも地味に役立つんだ。`head -n 回数` で好きな行数に区切れる。 ::: ## 6. yes の危険性 {#danger} > **結論**: yes は確認を全部スキップするため、削除や上書きなど取り返しのつかない操作と組み合わせると事故になる。 ::: dialogue @lina: `yes |` って万能じゃないですか。全部これで済ませちゃダメなんですか? @linny: そこが一番大事なところ。確認プロンプトは **「本当に大丈夫? 取り返しつかないよ?」という最後の安全装置** なんだ。yes はそれを全部無視する。だから消してはいけないファイルまで巻き込むと、もう戻せない。 ::: ::: danger **特に危険な組み合わせ** - `yes | rm -ri ディレクトリ/` — 中身を確認なしで全削除 - `yes | コマンド`(上書き・初期化系) — 大事なデータを確認なしで上書き **確認の意味があるからプロンプトが出ている**。yes で潰す前に、コマンドの対象(どのファイル・どのディレクトリか)を必ず目で確認すること。 ::: ::: warning **自分が何を実行しようとしているか分からないコマンドに `yes |` を付けない**。ネット上のコマンドをコピペするとき、頭に `yes |` が付いていたら特に警戒すること。 ::: ## 7. yes より安全な代替手段 {#alternative} > **結論**: 多くのコマンドは自前の `-y` / `--yes` オプションを持つ。yes パイプよりこちらを優先する。 実は、確認を省きたいだけなら yes を使わずに済むことが多い。コマンド自身が **「はい」を指定するオプション** を持っているからだ。 ::: highlight **コマンド自身の確認スキップオプション(一例)** | コマンド | 確認をスキップするオプション | | ------------- | ---------------------------- | | `apt` | `-y` / `--yes` | | `dnf` / `yum` | `-y` | | `cp` / `mv` | `-f`(強制) | | `rm` | `-f`(確認を出さない) | 例: `sudo apt install -y パッケージ名` ::: ::: dialogue @lina: なるほど、`apt install -y` の `-y` って、まさに「はい」なんですね! @linny: そのとおり。コマンド専用のオプションは「このコマンドのこの確認だけ」を狙って消せる。一方 yes は **すべての確認を無差別に潰す**。だから、専用オプションがあるならそっちを使うのが安全なんだ。 ::: ::: tip **使い分けの目安** - 専用の `-y` / `-f` オプションがある → **そちらを使う**(安全・意図が明確) - 専用オプションがない、または何度も繰り返し聞かれる → `yes |` を検討(対象を確認したうえで) ::: ## 8. ミニ課題:実際にやってみよう {#exercise} > **結論**: yes の出力、回数の区切り、任意文字列の 3 問で基本を手で確かめる。Ctrl + C を忘れずに。 ::: dialogue @lina: 手を動かして覚えたいです! @linny: いいね。どれも `Ctrl + C` で止められるから、安心して試してみて。 ::: **課題1**: `yes` を引数なしで実行して、`y` が出力されることを確認し、Ctrl + C で止めてみよう。 :::details ヒントを見る `yes` と打って Enter。画面が `y` で埋まったら **Ctrl + C** を押す。 ::: :::details 解答例 ```bash $ yes ``` 止めるには `Ctrl + C`。 ::: **課題2**: `OK` という文字列を、ちょうど 4 行だけ出力しよう。 :::details ヒントを見る `yes 文字列` と `head -n 行数` をパイプでつなぐ。 ::: :::details 解答例 ```bash $ yes OK | head -n 4 ``` ::: **課題3**: `apt` でパッケージをインストールするとき、確認を省く方法を、yes コマンドを使わずに書いてみよう。 :::details ヒントを見る コマンド自身の `-y` オプションを使う。 ::: :::details 解答例 ```bash $ sudo apt install -y パッケージ名 ``` ::: ## 9. コピペ用テンプレート {#templates} > **結論**: 自動応答・回数区切り・空行・安全な代替の型をまとめて手元に置いておく。 ::: tip **よく使う型をまとめておく** ```bash # 確認プロンプトに自動で y を答える(対象を確認してから) yes | コマンド # 同じ行を決まった回数だけ出力(テストデータ作成) yes | head -n 5 yes test | head -n 3 # 空行(Enter)を繰り返す yes "" | コマンド # 【推奨】専用オプションで確認をスキップ sudo apt install -y パッケージ名 # yes を止める Ctrl + C ``` ::: ## 次に読む {#next} - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [ブレース展開入門](/articles/tutorials/brace-expansion) - [mkdir・touch・echoの使い方](/articles/tutorials/file-creation-basics) - [仮想ターミナルでコマンドを試す](/terminal) # zip / unzip コマンド入門 - Windowsとやり取りする圧縮・解凍の基本 Source: https://penguin-gym-linux.com/articles/tutorials/zip-unzip-basics ## この記事でわかること {#intro-list} > **結論**: zipで固めて、unzipで開く。中身確認と日本語ファイル名の文字化け対処まで覚えれば、Windowsとのやり取りで困らない。 - `zip` でファイルやディレクトリを **.zip に圧縮** できます - `unzip` で **.zip を解凍** できます(Windows で作られた zip も開けます) - 解凍前に **中身を確認する** 安全な手順がわかります - **どこに展開されるか・上書きされるか** を先にたしかめられます **対象読者**:ファイル作成・基本コマンドを習得し、Windowsとファイルをやり取りしたい方 ::: tip **言葉の整理(先に取りちがえを防ぎます)** - **圧縮**:ファイルを小さくすることです。 - **解凍 / 展開**:小さくしたものを元に戻すことです。2 つはほぼ同じ意味で使われます。この記事では「解凍」にそろえます。 - **アーカイブ / 書庫**:いくつかのファイルを 1 つにまとめたもの、またはその形です。`.zip` はアーカイブのなかまです。 - **カレントディレクトリ**:いま自分がいる場所です。`pwd` でたしかめられます。なにも指定しないと、ここに解凍されます。 - **`~`(チルダ)**:自分専用の置き場(ホームディレクトリ)を表す短い書き方です。`/home/ユーザー名` と同じ意味です。 ::: ## 導入:Windowsから届いたzipファイル {#intro} > **結論**: WindowsはZIP形式が標準。Linux側もzip / unzipを使えば、お互いに圧縮ファイルを送り合える。 ::: dialogue @lina: ライニー先輩、Windows を使っている人から `report.zip` というファイルをもらいました。Linux で開けますか。 @linny: もちろん開けるよ。Windows で「右クリック → 圧縮」して作るのが、まさにこの `.zip` 形式。Linux では `unzip` コマンドで開いて、`zip` コマンドで作れるんだ。 @lina: 前に習った `tar` とは違うのですか。 @linny: いい質問。`tar`(`.tar.gz`)は Linux 同士でよく使う形式。`.zip` は **Windows が標準で開ける** のが強み。だから「Windows の人とやり取りするなら zip」と覚えておくといいよ。 ::: ## 1. そもそも zip / unzip とは?tar との違いは? {#what} > **結論**: zipは圧縮とまとめを1つで行う形式。Windowsが標準対応するため、相手がWindowsならzipが無難。 ::: dialogue @lina: `zip` と `tar`、どちらを使えばいいか迷います。 @linny: 2 つの基準で選べばいいよ。 ::: | 形式 | 得意な場面 | 備考 | | --------- | --------------------- | ------------------------ | | `.zip` | Windowsとやり取りする | Windowsが標準で開ける | | `.tar.gz` | Linux / Mac 同士 | 権限や記号を正確に保てる | ::: tip **迷ったらこれ**:相手が **Windows なら zip**、相手が **Linux / Mac なら tar.gz** です。最初はそれだけ覚えれば十分です。 ::: ::: dialogue @lina: 相手の OS で選べばいいのですね。 @linny: そう。ちなみに `zip` は「まとめる」と「圧縮する」を 1 つのコマンドでやってくれる。`tar` は本来「まとめるだけ」で、圧縮は `gzip` と組み合わせる。この違いは、最初は気にしなくて大丈夫だよ。 ::: ## 2. zip / unzip をインストールする {#install} > **結論**: Ubuntuでは初期状態でzip / unzipが入っていないことがある。`sudo apt install zip unzip`で導入する。 ::: dialogue @lina: さっそく使おうとしたら `unzip: command not found` と出ました。 @linny: あるある。Ubuntu だと最初は入っていないことがあるんだ。一度だけインストールすれば大丈夫。 ::: ```bash $ sudo apt install zip unzip ``` ::: dialogue @lina: `zip` と `unzip`、両方まとめて入れるのですね。 @linny: そう。`zip`(作る側)と `unzip`(開く側)は別パッケージだから、両方入れておくと安心だよ。入っているか確認したいときは `which` を使おう。 ::: ```bash $ which zip unzip ``` ```output /usr/bin/zip /usr/bin/unzip ``` 2 行とも出れば両方入っています。1 行しか出なければ、出ていない方がまだ入っていません。 ## 3. zip で圧縮する {#zip} > **結論**: ファイルは`zip 名前.zip ファイル`、ディレクトリは`-r`を付けて`zip -r 名前.zip ディレクトリ`で固める。 ::: dialogue @linny: ここから先はファイルを作ったり上書きしたりする。まず練習用の場所と、練習用のファイルを用意しよう。 ::: ```bash # 練習用ディレクトリを作って移動する $ mkdir -p ~/zip-practice $ cd ~/zip-practice # 練習用ファイルを作る $ echo "id,name" > data.csv $ echo "メモ" > memo.txt ``` この記事の例は、すべてこの `~/zip-practice` の中で実行します。こうすれば、もとからあるファイルを巻きこみません。 ### 3-1. ファイルをまとめて圧縮 {#zip-files} ```bash $ zip report.zip data.csv memo.txt ``` ```output adding: data.csv (deflated 62%) adding: memo.txt (deflated 20%) ``` ::: dialogue @lina: `deflated 62%` とは何ですか。 @linny: 「62% 小さくなった」という意味だよ。zip は圧縮率も教えてくれる。最初に書く `report.zip` が **作られるファイル名**、その後ろが **詰め込むファイル** だ。 ::: ::: warning **どこに作られるか・すでにある zip はどうなるか** - `report.zip` は **いま自分がいるディレクトリ** に作られます。`pwd` でたしかめられます - 同じ名前の zip がすでにあると、`zip` は **その中に足したり書きかえたり** します。作り直しにはなりません - 中身を入れかえたいときは、先に古い zip を消すか、べつの名前を付けてください ::: ### 3-2. ディレクトリを丸ごと圧縮(-r が必須) {#zip-dir} ```bash $ zip -r project.zip project/ ``` ::: warning **ここが最大の注意点**:ディレクトリを圧縮するときは `-r`(recursive = 中身も全部)が必須です。`-r` を付け忘れると、中身が入らず空に近い zip ができます。 ::: ::: dialogue @lina: 先輩、`zip backup.zip myfolder` でバックアップを作って、元のフォルダを消しました。あとで開いたら、中身が入っていませんでした。 @linny: `-r` を付けていないね。ディレクトリは `zip -r backup.zip myfolder/` で固める。 @lina: えっ。コマンドは成功したように見えたのに、中身は空だったのですか。 @linny: そう。だからバックアップを作ったら、**消す前に `unzip -l` で中身を数える**。この確認を挟めば、消してから気づく事故は起きないよ。 @lina: 作って終わりではなく、中を見てから消す。順番が大事なんですね。 ::: ::: dialogue @lina: `tar` のときも「ディレクトリは特別扱い」でしたね。 @linny: そう、よく覚えていたね。`zip` では `-r` がその役割。ディレクトリを固めるときは反射的に `-r` を付ける、と体で覚えよう。 ::: ## 4. unzip で解凍する {#unzip} > **結論**: `unzip 名前.zip`でカレントディレクトリに展開。`-d`で展開先を指定すれば散らからず安全。 ### 4-1. カレントディレクトリに展開(基本) {#unzip-basic} ```bash $ unzip report.zip ``` ```output Archive: report.zip inflating: data.csv inflating: memo.txt ``` ::: dialogue @lina: `inflating` は「膨らませる」、つまり解凍中ということですね。 @linny: その通り。圧縮が `deflate`(しぼませる)、解凍が `inflate`(膨らませる)。対になっているんだ。 ::: ### 4-2. 展開先を指定する(-d で事故防止) {#unzip-d} ```bash $ unzip report.zip -d ~/work/report ``` ::: tip **散らかり防止のコツ**:`unzip` は何も指定しないと **今いる場所** にファイルをばらまきます。中身が多い zip は、`-d` で専用ディレクトリを指定すると安全です。指定したディレクトリが無ければ自動で作ってくれます。zip の中にディレクトリが入っていれば、`-d` で指定した場所の下にそのディレクトリが作られます。 ::: ### 4-3. 同じ名前のファイルがあったら {#overwrite} ::: warning **上書きの先回り** `unzip` は同じ名前のファイルを見つけると、**その場でたずねてきます**。 ```output replace data.csv? [y]es, [n]o, [A]ll, [N]one, [r]ename: ``` - `y` は 1 つだけ上書き、`A` は **すべて上書き** します - `n` は 1 つだけ残す、`N` は **すべて残す**(上書きしない)です - `r` はべつの名前を付けて展開します - 上書きされた中身は元に戻せません。ゴミ箱にも残りません - 迷ったら `N` をえらび、`-d` でべつのディレクトリに出しなおしてください - `zip` と `unzip` は本サイトの仮想ターミナルでは動きません。実行は自分のパソコンのターミナルで行います - 練習は 3 章で作った `~/zip-practice` の中だけで行ってください。大事なファイルのある場所では練習しないでください ::: たずねられずに動かしたいときは、次の 2 つを使い分けます。**まず安全な方から覚えてください。** ```bash # 安全側:たずねずに、もとからあるファイルを残す(上書きしない) $ unzip -n report.zip # 危険側:たずねずに全部上書きする $ unzip -o report.zip ``` ::: warning `-o` は確認を一切しません。中身を見ずに `-o` を使うと、同じ名前のファイルがだまって消えます。`-o` を使う前に、必ず `unzip -l` で中身をたしかめてください。迷ったら `-n` か `-d` を使ってください。 ::: ## 5. 解凍前に中身を確認する {#list} > **結論**: `unzip -l 名前.zip`で展開せずに中身一覧を確認できる。見知らぬzipは展開前に必ず中身を見る。 ::: dialogue @lina: もらった zip を、いきなり開くのは少し怖いです。 @linny: いい感覚だね。`-l`(list)を付ければ、**展開せずに中身の一覧だけ** 見られるよ。 ::: ```bash $ unzip -l report.zip ``` ```output Archive: report.zip Length Date Time Name --------- ---------- ----- ---- 1024 2026-06-05 10:00 data.csv 256 2026-06-05 10:00 memo.txt --------- ------- 1280 2 files ``` `Length` はファイルの大きさ(バイト)です。`Name` が中に入っているファイル名です。 ::: warning **安全のために**:知らない相手からの zip は、必ず `unzip -l` で中身を確認してから展開してください。`/etc/...` のような変なパスや、見覚えのないファイルが無いかを見る習慣が事故を防ぎます。 ::: ## 6. 日本語ファイル名が文字化けしたら {#mojibake} > **結論**: Windows製zipの日本語名が文字化けしたら、Ubuntuでは`unzip -O CP932 名前.zip`で正しく展開できる。 ::: dialogue @lina: Windows の人からもらった zip を開いたら、ファイル名が `\203t\202@...` のような文字になっていました。 @linny: それは Windows とのやり取りで一番ハマるところ。Windows は日本語ファイル名を **CP932(Shift-JIS 系の文字コード)** で zip に保存することがある。Linux の UTF-8 とずれるので文字化けするんだ。 ::: Ubuntu や Debian の `unzip` には、文字コードを指定する `-O` オプションがあります。 ```bash $ unzip -O CP932 windows.zip ``` ::: tip **なぜ直るのか**:`-O CP932` は「この zip のファイル名は CP932 で書かれている」と unzip に教えるオプションです。これで Linux 側が正しく UTF-8 に変換し、日本語ファイル名がそのまま表示されます。展開前の確認にも使えます(`unzip -O CP932 -l windows.zip`)。 ::: ::: dialogue @lina: `-O CP932` を足すだけで直りました。 @linny: `-O` は Ubuntu / Debian の `unzip` に入っている便利オプション。環境によっては無いこともあるけれど、Windows とやり取りするなら覚えておいて損はないよ。 ::: ## 7. パスワード付きzipを扱う {#password} > **結論**: `zip -e`で暗号化zipを作り、解凍時はunzipがパスワードを尋ねる。zipの暗号は強くないため過信しない。 ### 7-1. パスワードを付けて圧縮 {#password-zip} ```bash $ zip -e secret.zip data.csv ``` ```output Enter password: Verify password: adding: data.csv (deflated 62%) ``` ### 7-2. パスワード付きzipを解凍 {#password-unzip} ```bash $ unzip secret.zip ``` パスワードを尋ねられます。入力すると展開されます。 ::: warning **過信は禁物**:従来の zip 暗号は強度が高くありません。本当に機密性が必要なファイルは、zip のパスワードだけに頼らないでください。暗号化ツールや安全な転送経路を検討しましょう。なお、パスワードを忘れると中身は開けなくなります。控えを安全な場所に残してください。 ::: ## ミニ課題で手を動かそう {#practice} > **結論**: 圧縮 → 中身確認 → 展開先指定で解凍、の一連の流れを自分の手で通して定着させる。 ::: dialogue @linny: じゃあ、今日の流れを通しでやってみよう。「固める → 中を見る → 安全に開く」の 3 ステップだよ。 ::: 3 章で作った `~/zip-practice` の中で試します。今いる場所を `pwd` でたしかめてから始めてください。 ```bash $ pwd ``` ```output /home/user/zip-practice ``` ### 課題1:ディレクトリを圧縮しよう {#challenge-1} **やること**:`mydata` というディレクトリを作って中にファイルを置き、`mydata.zip` に圧縮しよう。 :::details ヒント 1(方向づけ)を見る ディレクトリを固めるときは、「中身も全部入れる」という合図が要ります。これを忘れると、からっぽに近い zip ができます。 ::: :::details ヒント 2(コマンド名)を見る `mkdir` と `touch` で用意し、`zip -r` で固めます。 ::: :::details 答えを見る ```bash $ mkdir mydata $ touch mydata/a.txt mydata/b.txt $ zip -r mydata.zip mydata/ ``` ```output adding: mydata/ (stored 0%) adding: mydata/a.txt (stored 0%) adding: mydata/b.txt (stored 0%) ``` `adding:` の行に 2 つのファイルが出ていれば成功です。並ぶ順番は環境によって変わります。 ::: ### 課題2:中身を確認してから展開しよう {#challenge-2} **やること**:作った `mydata.zip` の中身を一覧表示し、`extract` というディレクトリを展開先に指定して解凍しよう。 :::details ヒント 1(方向づけ)を見る まず、開かずに中身だけを見ます。次に、今いる場所ではなくべつの場所に出します。 ::: :::details ヒント 2(コマンド名)を見る `unzip -l` で確認し、`unzip -d` で展開先を指定します。 ::: :::details 答えを見る ```bash $ unzip -l mydata.zip $ unzip mydata.zip -d extract $ ls extract/mydata ``` ```output a.txt b.txt ``` `extract` は無ければ自動で作られます。中身は `extract/mydata/` の下に出ます。課題 1 の `adding: mydata/a.txt` のとおり、zip には `mydata/` というディレクトリ名ごと入っているためです。`-d` は「どこに広げるか」を決めるもので、zip の中の階層はそのまま再現されます。 ::: ::: dialogue @lina: 「固める → 中を見る → 開く」、できました。この流れなら安心です。 @linny: 完璧。この 3 ステップを習慣にすれば、どんな zip が来ても落ち着いて対応できるよ。 ::: ## よくあるミスと対処法 {#mistakes} > **結論**: -r忘れ・command not found・展開先散らかり・文字化けの4つが定番。原因と対処をセットで押さえる。 ### ミス1:ディレクトリを圧縮したのに中身が空 {#mistake-1} ::: dialogue @lina: `zip backup.zip myfolder` とやったのに、中身がほとんど入っていませんでした。 @linny: `-r` の付け忘れだね。ディレクトリは `zip -r backup.zip myfolder/` で固める。作ったら `unzip -l` で中を見ておこう。 ::: ### ミス2:unzip: command not found {#mistake-2} ::: dialogue @lina: `unzip` と打ったら command not found でした。 @linny: パッケージがまだ入っていないね。`sudo apt install unzip` で入れよう。`zip` を使うなら `zip` のパッケージも別に要るよ。 ::: ### ミス3:展開したらカレントが散らかった {#mistake-3} ::: dialogue @lina: たくさん入った zip を開いたら、今いるところがファイルだらけになりました。 @linny: `unzip` は何も言わないと今いるところに広げるからね。次からは `unzip xxx.zip -d 展開先/` で専用のところに出そう。先に `unzip -l` でいくつ入っているかを見るのもおすすめだよ。 ::: ### ミス4:日本語ファイル名が文字化けする {#mistake-4} ::: dialogue @lina: Windows で作られた zip のファイル名が文字化けします。 @linny: Ubuntu / Debian なら `unzip -O CP932 xxx.zip` で直ることが多いよ。Windows が日本語の名前を CP932 でしまっているのが理由だね。 ::: ## 今日の 3 行まとめ {#summary} > **結論**: zip -rで固め、unzip -lで中を見て、unzip -dで安全に展開する。 1. ディレクトリを固めるときは `zip -r` を使います。作ったら `unzip -l` で中身をかぞえます 2. 解凍は、なにも指定しないと今いる場所に広がります。`unzip -d 展開先/` で出す場所をきめます 3. 同じ名前があると `unzip` はたずねてきます。迷ったら `N` で残し、べつの場所に出しなおします ::: tip **コピペ用の型** ```bash # 作る(ディレクトリは -r 必須) zip -r archive.zip dir/ # 中身を見る(展開前に確認) unzip -l archive.zip # 開く(散らからない) unzip archive.zip -d 展開先/ # 文字化け(Ubuntu / Debian) unzip -O CP932 windows.zip ``` ::: ## 次に読む {#next} - [tarコマンドの使い方(Linux同士の圧縮)](/articles/tutorials/tar-basics) - [ファイルをリモートへ転送する(scp / rsync)](/articles/tutorials/scp-rsync-basics) - [「command not found」と言われたら](/articles/tutorials/command-not-found) # 「Address already in use」の解決 - ポート競合の特定と解放 Source: https://penguin-gym-linux.com/articles/troubleshooting/address-already-in-use ## この記事で解決できること {#intro} - `Address already in use`(EADDRINUSE)が**なぜ出るのか**を理解できます - ポートを**占有しているプロセスを特定**して安全に解放できます - 「さっき止めたのにまた出る」TIME_WAIT 由来の競合を切り分けられます ::: tip **結論(最短ルート)** 1. **誰が掴んでいるか特定**:`sudo ss -lntp | grep ':PORT '`(または `sudo lsof -i :PORT`) 2. **正しく停止**:サービスなら `systemctl stop`、単発プロセスなら `kill PID` 3. **プロセスが無いのに出る**:TIME_WAIT が原因 → アプリ側 `SO_REUSEADDR` か数十秒待つ ::: ::: warning **前提(対象環境)** - OS:Ubuntu(systemd 環境) - 対象:自前のサーバ / アプリを起動して `Address already in use` に当たった人 - 例として `8080` 番ポートを使用(読み替え可) ::: ## 「Address already in use」とは何か? {#what} > **結論**: アプリが `bind()` で確保しようとしたポートを別のプロセスが既に握っているため、OS が確保を拒否しているエラー。 サーバ系プロセスは起動時に「このポートで待ち受ける」と OS に宣言する(`bind()` システムコール)。同じ IP・ポートの組を二重に確保することは原則できないため、既に使われていると `bind()` が `EADDRINUSE` を返す。 典型的なメッセージ: ```output Error: listen EADDRINUSE: address already in use :::8080 bind: Address already in use OSError: [Errno 98] Address already in use ``` `Errno 98` は Linux における `EADDRINUSE` の番号。言語・フレームワークが違っても原因は同じ。 ## なぜポートが競合するのか? {#causes} > **結論**: 原因は「①前のプロセスが生きている」「②多重起動」「③直前に落としたプロセスの TIME_WAIT」のほぼ 3 種に集約される。 - **前のプロセスが残っている**:再起動したつもりが旧プロセスが生存。デタッチ起動(`&` / nohup / コンテナ)で見落としやすい - **多重起動**:同じアプリを 2 回起動、または別アプリが同じポートを使用 - **TIME_WAIT**:直前に終了したプロセスのコネクションが TCP の `TIME_WAIT` 状態で残り、`SO_REUSEADDR` 未設定のアプリが再バインドに失敗 ①②は「プロセスを止める」で解決する。③だけはプロセスが存在しないため別アプローチになる(後述)。 ## 誰がポートを使っているか特定するには? {#identify} > **結論**: `ss -lntp` で待受プロセスと PID を確認するのが最短。`lsof` / `fuser` も同じ情報を別表現で出す。 ### ss で待受プロセスを特定(推奨) ```bash sudo ss -lntp | grep ':8080 ' ``` ```output LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=12345,fd=18)) ``` - `users:(("node",pid=12345,...))` の **pid=12345** が占有元 - `-l` 待受 / `-n` 数値表示 / `-t` TCP / `-p` プロセス(`sudo` 必須) ### lsof で確認 ```bash sudo lsof -i :8080 ``` ```output COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME node 12345 hide 18u IPv4 98765 0t0 TCP *:8080 (LISTEN) ``` ### fuser で PID だけ素早く ```bash sudo fuser 8080/tcp ``` ```output 8080/tcp: 12345 ``` ::: tip バインド先が `127.0.0.1:8080` か `0.0.0.0:8080` かも `ss` で確認できる。同じポートでも `127.0.0.1` と `0.0.0.0` は別扱いになるため、競合の有無の判断材料になる。 ::: ## プロセスを止めてポートを解放するには? {#release} > **結論**: サービス管理下なら `systemctl stop`、手動起動プロセスなら `kill PID`。`kill -9` は最終手段。 ### サービスとして動いている場合 ```bash sudo systemctl stop myapp.service ``` systemd 管理下のプロセスを `kill` で直接落とすと、自動再起動(`Restart=` 設定)で即復活しポートを掴み直すことがある。必ず `systemctl stop` を使う。 ### 手動起動のプロセスの場合 特定した PID に対して、まず通常のシグナル(SIGTERM)で停止を試みる: ```bash sudo kill 12345 ``` 数秒待っても残る場合のみ、強制終了(SIGKILL): ```bash sudo kill -9 12345 ``` ::: warning `kill -9` はプロセスに後始末(接続のクローズ・一時ファイル削除)の機会を与えず即座に殺す。データ破損やロックファイル残留の原因になるため、まず `kill`(SIGTERM)を試し、それでも落ちないときの最終手段とする。 ::: 停止後、ポートが解放されたか確認: ```bash sudo ss -lntp | grep ':8080 ' ``` 何も出力されなければ解放済み。 ## プロセスが無いのに出る(TIME_WAIT)ときは? {#time-wait} > **結論**: 直前に落としたプロセスの接続が `TIME_WAIT` で残っている状態。アプリが `SO_REUSEADDR` を設定すれば即再起動でき、未設定なら数十秒で自然解消する。 `ss -lntp` で待受プロセスが**見つからない**のに `Address already in use` が出る場合、TIME_WAIT が疑わしい。確認: ```bash ss -tan state time-wait | grep ':8080' ``` `TIME_WAIT` は TCP が「遅延パケットの取り違え」を防ぐための正常な状態で、通常 60 秒前後で消える。対処は次のいずれか。 - **アプリ側で `SO_REUSEADDR` を有効化(推奨・恒久対策)**:多くのサーバ実装はデフォルトで設定済み。自作サーバなら listen ソケットに設定する - **数十秒待ってから再起動**:暫定対応 ::: danger `net.ipv4.tcp_tw_reuse` や `tcp_tw_recycle` を安易に変更しないこと。これらは**発信(outbound)側**の TIME_WAIT 再利用に関する設定で、待受ポートの `Address already in use` の正しい対策ではない。`tcp_tw_recycle` は NAT 環境で接続障害を引き起こすためカーネルから既に削除されている。listen 側の対策は `SO_REUSEADDR` が正解。 ::: ## 再発を防ぐには? {#prevent} > **結論**: アプリに graceful shutdown と `SO_REUSEADDR` を実装し、起動スクリプトで旧プロセスの停止を確実にする。 - **`SO_REUSEADDR` を設定**:再起動時の TIME_WAIT 由来の失敗を防ぐ - **graceful shutdown**:SIGTERM を受けたら接続を閉じてからプロセス終了する。`kill -9` 依存を減らす - **プロセス管理を systemd / コンテナに任せる**:手動の `&` 起動は旧プロセスの取り残しを生みやすい - **起動前チェック**:起動スクリプト冒頭で `ss -lntp | grep ':PORT '` を確認し、占有があれば停止してから起動 ## やってはいけないこと {#mistakes} > **結論**: 「いきなり `kill -9`」「ポート番号を変えて逃げる」「`tcp_tw_recycle` を有効化」は再発と別障害の温床。 ### やってはいけない1:原因を見ずに `kill -9` を連打する 占有元が systemd 管理サービスなら、`kill` しても再起動で復活する。まず `ss` で正体を確認し `systemctl stop` する。 ### やってはいけない2:ポート番号を変えて回避する `8080` が競合するから `8081` にする、を繰り返すと「どのポートで何が動いているか」が崩壊する。占有元を特定して解放するのが筋。 ### やってはいけない3:`tcp_tw_recycle` を有効化する 過去のネット記事に残る対策だが、NAT 環境で接続不能を引き起こす危険な設定で、現行カーネルでは削除済み。listen 側は `SO_REUSEADDR` を使う。 ::: tip **コピペ用:特定から解放まで** ```bash # 1. 占有プロセスの特定 sudo ss -lntp | grep ':8080 ' sudo lsof -i :8080 # 2-a. サービスなら sudo systemctl stop # 2-b. 手動プロセスなら(SIGTERM → 最終手段 SIGKILL) sudo kill sudo kill -9 # 3. 解放確認 sudo ss -lntp | grep ':8080 ' # プロセスが居ないのに出る場合(TIME_WAIT) ss -tan state time-wait | grep ':8080' ``` ::: ## 次に読む {#next} - [ポート疎通の確認(ss / lsof / nc / curl)](/articles/troubleshooting/port-connectivity) - [systemctl の基本](/articles/tutorials/systemctl-basics) - [ゾンビプロセスの正体と対処](/articles/troubleshooting/zombie-process) # "Argument list too long" の対処 — rm * が失敗するとき Source: https://penguin-gym-linux.com/articles/troubleshooting/argument-list-too-long ## この記事で解決できること {#intro} - `rm *` / `cp * dest/` が `Argument list too long` で失敗する**原因**が分かる - `find + xargs` / `find -delete` / シェルループで大量ファイルを**安全に処理**できる - スペースや特殊文字を含むファイル名にも対応できる ::: tip **結論(即解決)** ```bash # rm * の代替 — 最も安全 find . -maxdepth 1 -name "*.log" -type f -delete # または xargs 経由 find . -maxdepth 1 -name "*.log" -print0 | xargs -0 rm ``` ::: ## なぜ "Argument list too long" が出るのか? {#cause} `rm *` を実行したとき、ワイルドカードの展開はコマンドではなくシェルが行う。ファイルが数万個あると、展開後の引数リストがカーネルの上限値(`ARG_MAX`)を超え、カーネルが `E2BIG` エラーを返す。 ```bash $ rm *.log -bash: /bin/rm: Argument list too long ``` `ARG_MAX` の確認: ```bash $ getconf ARG_MAX 2097152 ``` ```output 2097152 ``` Linux の一般的な値は 2MB(2,097,152 バイト)。1 ファイルあたりのパス長 × ファイル数がおおむね 200 万文字を超えると発生しやすい。 ::: warning `ls *.log`、`cp *.log dest/`、`cat *.txt`、`chmod 644 *.php` など、**ワイルドカードを使うすべてのコマンドで同じエラーが発生する**。 ::: `find` コマンド自体はファイルを逐次処理するため、この制限を受けない。これが解決策の核心になる。 ## find -delete で解決する方法 {#find-delete} 削除だけなら `find -delete` が最もシンプル。xargs が不要で安全性も高い。 ```bash find . -maxdepth 1 -name "*.log" -type f -delete ``` - `-maxdepth 1`:カレントディレクトリのみ(`rm *` と同等の範囲) - `-type f`:ファイルのみ(ディレクトリの誤削除を防ぐ) - `-delete`:マッチしたファイルを削除 サブディレクトリ以下も対象にする場合は `-maxdepth` を外す: ```bash find /path/to/dir -name "*.log" -type f -delete ``` ::: tip 削除前に対象を確認したい場合は `-delete` を外して実行する。 ```bash # dry-run 代わりに件数と一覧を確認 find . -maxdepth 1 -name "*.log" -type f find . -maxdepth 1 -name "*.log" -type f | wc -l ``` ::: ## find + xargs で解決する方法 {#xargs} `rm` 以外の操作にも使える汎用パターン。 ### 基本パターン ```bash find . -maxdepth 1 -name "*.log" | xargs rm ``` `xargs` は自動的に引数を分割してコマンドに渡すため、`ARG_MAX` を超えない。 ### ファイル名にスペース・特殊文字が含まれる場合 {#xargs-null} デフォルトの xargs は空白区切りを使うため、スペースを含むファイル名が誤動作する。`-print0` と `xargs -0` を組み合わせると NUL 文字区切りになり安全: ```bash find . -maxdepth 1 -name "*.log" -print0 | xargs -0 rm ``` 実際の運用では `-print0 | xargs -0` を常に使うのが無難。 ### 並列処理で高速化する ```bash find . -name "*.log" -print0 | xargs -0 -P4 rm ``` `-P4` で 4 並列実行。大量ファイルの削除が高速化する。 ## cp / mv でも同じエラーが出る {#cp-mv} `cp * dest/` や `mv * dest/` も同様に対処できる。 ```bash # cp の場合 find . -maxdepth 1 -name "*.log" -print0 | xargs -0 -I{} cp {} /dest/ # mv の場合 find . -maxdepth 1 -name "*.log" -print0 | xargs -0 -I{} mv {} /dest/ ``` `-I{}` で各ファイルパスを `{}` に置換する。 `-exec` を使う方法もある: ```bash find . -maxdepth 1 -name "*.log" -exec cp {} /dest/ \; ``` `-exec` はファイルごとにプロセスを起動するため、大量ファイルには xargs のほうが高速。 ## シェルループで対処する方法 {#shell-loop} `find` が使えない場面や、削除前に一件ずつ確認したい場合の代替手段。 ```bash for f in *.log; do rm "$f" done ``` シェルが 1 ファイルずつ `rm` を呼ぶため `ARG_MAX` の制限を受けない。 ::: warning ダブルクォート `"$f"` を省略すると、スペースを含むファイル名で誤動作する。必ず囲む。 ::: ## 解決策まとめ {#summary} | 方法 | コマンド例 | 向いている場面 | | --------------------- | --------------------------------------------- | ------------------------ | | `find -delete` | `find . -name "*.log" -type f -delete` | 削除のみ(最もシンプル) | | `find \| xargs rm` | `find . -name "*.log" \| xargs rm` | 標準的な削除 | | `-print0 \| xargs -0` | `find . -name "*.log" -print0 \| xargs -0 rm` | 特殊文字ファイル名 | | シェルループ | `for f in *.log; do rm "$f"; done` | 逐次処理・確認しながら | | `-exec` | `find . -name "*.log" -exec cp {} /dest/ \;` | 削除以外の操作 | ::: tip **判断フロー** 1. 削除だけ → `find -delete` 2. コピー・移動など → `find ... -print0 | xargs -0 コマンド` 3. 一件ずつ確認しながら → シェルループ ::: ## 次に読む {#next} - [find で安全にファイル削除する方法](/articles/troubleshooting/find-safe-delete) - [No space left on device の対処法](/articles/troubleshooting/no-space-left-on-device) - [inode 枯渇の対処](/articles/troubleshooting/inode-exhaustion) # 「bad interpreter」エラーの直し方 - shebang と CRLF 改行 Source: https://penguin-gym-linux.com/articles/troubleshooting/bad-interpreter-shebang ## 「bad interpreter」エラーとは何か? {#intro} > **結論**: シェルがスクリプト1行目の shebang を解釈できないエラー。原因のほぼ全ては「CRLF 改行の混入」か「shebang のパス誤り」の2つ。 シェルスクリプトを実行しようとして、次のようなエラーが出た経験はないだろうか。 ```output $ ./deploy.sh -bash: ./deploy.sh: cannot execute: required file not found ``` ファイルは存在し、実行権限もあるのに動かない。このエラーは **shebang 行(1行目の `#!...`)に書かれたインタプリタをカーネルが起動できない** ときに発生する。 なお上記は bash 5.1 以降(Ubuntu 22.04+ / Debian 11+ / RHEL 9+ など)の表示。bash 5.0 以前(Ubuntu 20.04 / Debian 10 / RHEL 8 など)では `-bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directory` のように、インタプリタのパスと `^M`(CR)が見える形になる。どちらも原因は同じ。 ::: tip **まず原因を2つに絞る** 原因はほぼ次の2つ。 - **CRLF 改行**の混入(最頻出) - **shebang のパスが存在しない** bash 5.1 以降はどちらも `cannot execute: required file not found` と表示され、エラー文だけでは区別できない。エラーメッセージで判断しようとせず、`file` と `cat -A`(後述)でファイルを直接観測して切り分ける。 ::: なお `Permission denied` は実行ビット(`chmod +x`)の問題で、原因の層が異なる。混同しやすいので、権限側の切り分けは [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) を参照。 ## なぜ「インタプリタが見つからない」と言われるのか? {#causes} > **結論**: カーネルは shebang 行を文字列のまま interpreter のパスに使う。CRLF の `\r` が付くと `/bin/bash\r` という存在しないパスを探すため失敗する。表示は bash 5.1 以降では `cannot execute: required file not found`、5.0 以前では `No such file or directory` になる。 Linux カーネルは `#!` で始まる1行目を読み、`#!` の直後を **インタプリタの絶対パスとしてそのまま** 使う。ここがエラーの本質。 Windows や一部のエディタで保存したファイルは、改行が `LF`(`\n`)ではなく `CRLF`(`\r\n`)になる。すると shebang 行はこう解釈される。 ```output #!/bin/bash\r ↑ この \r(CR)まで含めてパス扱い ``` つまりカーネルは `/bin/bash` ではなく **`/bin/bash` + 復帰文字** という名前のファイルを探す。そんなファイルは存在しないので失敗する。bash 5.1 以降は `cannot execute: required file not found` と表示され、5.0 以前は `No such file or directory` と表示されたうえ CR が `^M` として見える(`^M` 表示は 5.0 以前のエラーメッセージに限った話)。 ::: warning ファイル名やパスが正しくても、**目に見えない `\r` 1文字** で失敗する。これが「見た目は正しいのに動かない」の正体。 ::: shebang のパスそのものが間違っているケースもある。例えば `#!/usr/local/bin/python3` と書いたが、その環境では Python が `/usr/bin/python3` にしかない場合などだ。 ## CRLF 改行が原因か、どう見分けるか? {#diagnose} > **結論**: `file` で改行コード種別、`cat -A` で行末の `^M` を確認する。どちらも CRLF を一目で判定できる。 推測で直す前に、必ず原因を観測する。 ### file コマンドで改行種別を見る ```bash $ file deploy.sh ``` CRLF が混入していると、こう表示される。 ```output deploy.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators ``` `with CRLF line terminators` が出れば改行コードが原因で確定。正常なら `ASCII text executable` のみで CRLF の表記は出ない。 ### cat -A で行末を可視化する ```bash $ cat -A deploy.sh | head -3 ``` ```output #!/bin/bash^M$ ^M$ echo "deploy start"^M$ ``` `cat -A` は行末を `$`、CR を `^M` で表示する。各行末が `^M$` になっていれば CRLF。正常なファイルは `$` だけになる。 ### shebang 行だけを厳密に見る ```bash $ head -1 deploy.sh | od -c ``` ```output 0000000 # ! / b i n / b a s h \r \n ``` `\r \n` が並んでいれば CRLF。`\n` だけなら LF(正常)。 ## CRLF 改行をどう修正するか? {#fix-crlf} > **結論**: `dos2unix` が最も確実。無ければ `sed -i 's/\r$//'` で全行の末尾 CR を除去する。修正後は再び `file` で確認する。 ### 方法1: dos2unix(推奨) ```bash $ dos2unix deploy.sh ``` ```output dos2unix: converting file deploy.sh to Unix format... ``` CRLF を LF に変換する専用ツール。インストールは Ubuntu/Debian なら `sudo apt install dos2unix`、RHEL 系なら `sudo dnf install dos2unix`。 ### 方法2: sed(追加インストール不要) dos2unix が入っていない環境では `sed` で代用できる。 ```bash $ sed -i 's/\r$//' deploy.sh ``` `s/\r$//` は「各行末の CR を削除」の意味。`-i` でファイルを直接書き換える。心配なら `-i.bak` でバックアップを残せる。 ```bash $ sed -i.bak 's/\r$//' deploy.sh # deploy.sh.bak を残す ``` ::: warning `tr -d '\r'` を使う場合、`tr` は標準入力専用なので **同じファイルへの上書きリダイレクトは不可**(中身が消える)。必ず別ファイルに出す。 ```bash $ tr -d '\r' < deploy.sh > deploy.unix.sh # OK $ tr -d '\r' < deploy.sh > deploy.sh # NG: 空になる ``` ::: ### 方法3: vim で変換 エディタで開いている最中なら、その場で直せる。 ```bash :set fileformat=unix :w ``` ### 修正できたか必ず確認する ```bash $ file deploy.sh deploy.sh: Bourne-Again shell script, ASCII text executable $ ./deploy.sh deploy start ``` `CRLF line terminators` の表記が消えていれば成功。 ## shebang のパス間違いをどう直すか? {#fix-shebang} > **結論**: shebang のパスにインタプリタが実在するか `which` で確認する。移植性を上げるなら `#!/usr/bin/env bash` を使う。 shebang のパスにインタプリタが実在しない場合だ。bash 5.0 以前なら `bash: ./run.sh: /usr/local/bin/python3: bad interpreter` のように問題のパスが表示されるが、bash 5.1 以降はパス不在も CRLF も区別なく `cannot execute: required file not found` と表示され、パスは出ない。どちらの版でも、エラー文に頼らず `file` / `cat -A` で CRLF を否定したうえで、`which` でインタプリタの実在を確認する。 ### インタプリタの実在を確認する ```bash $ head -1 run.sh #!/usr/local/bin/python3 $ which python3 /usr/bin/python3 ``` shebang は `/usr/local/bin/python3` を指しているが、実体は `/usr/bin/python3`。パスが食い違っている。 ### env を使って解決する shebang を実パスにベタ書きする代わりに、`env` で `PATH` から探させると移植性が上がる。 ```bash #!/usr/bin/env python3 ``` ```bash #!/usr/bin/env bash ``` `env` は `PATH` を走査してインタプリタを見つけるため、`/usr/bin` と `/usr/local/bin` のどちらにあっても動く。環境差で壊れにくい、現代的な書き方だ。 ::: tip ただし `env` 経由ではインタプリタへの引数指定に制約がある(古い環境では複数引数を渡せない)。`set -euo pipefail` 等はスクリプト本体に書けばよく、shebang に詰め込む必要はない。 ::: ## 再発をどう防ぐか? {#prevent} > **結論**: `.gitattributes` で `.sh` を LF 固定、エディタを LF 保存に設定する。この2点で CRLF の混入をほぼ封じられる。 一度直しても、保存・コミットのたびに CRLF が戻ると意味がない。発生源を断つ。 ### Git で改行を固定する リポジトリ直下に `.gitattributes` を置き、シェルスクリプトを LF に固定する。 ```output *.sh text eol=lf ``` これでチェックアウト時も常に LF になり、Windows 環境から触られても壊れにくい。 `core.autocrlf` の設定も確認しておく。Linux/macOS では `input` が無難だ。 ```bash $ git config --global core.autocrlf input ``` ::: warning `core.autocrlf=true` は Windows 開発者がよく設定するが、チェックアウト時に CRLF を付けるため、シェルスクリプトでは `bad interpreter` の温床になる。スクリプトを扱うなら `.gitattributes` の `eol=lf` で明示的に上書きするのが確実。 ::: ### エディタを LF 保存に設定する - **VS Code**: 画面右下の `CRLF` 表示をクリックして `LF` に変更。プロジェクトに `.editorconfig` を置き `end_of_line = lf` を指定すると自動化できる - **vim**: `:set fileformat=unix` で保存 - **Windows のメモ帳**: シェルスクリプトの編集には使わない(CRLF を付けがち) ### 仕上げのチェックリスト 1. `file script.sh` に `CRLF line terminators` が出ない 2. `head -1 script.sh` の shebang のパスに `which` でインタプリタが実在する 3. `ls -l script.sh` で実行ビット(`x`)が立っている この3点が揃えば `bad interpreter` は再発しない。 ## 次に読む {#next} - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) - [終了ステータスと exit code の読み方](/articles/tutorials/exit-codes-and-status) - [command not found の対処](/articles/tutorials/command-not-found) # 自作スクリプトが「command not found」になる - PATHと実行権限 Source: https://penguin-gym-linux.com/articles/troubleshooting/bash-command-not-found-path ## この記事で解決できること {#intro} - 自作スクリプトだけ `command not found` になる **理由** が分かる - `command not found` と `Permission denied` を **正しく見分けられる** - `./` 実行・`chmod +x`・PATH 追加・`hash -r` を **順に切り分け** できる ::: tip **結論(切り分けの型)** `command not found` は「PATH 上にそのコマンドが見つからない」サイン。自作スクリプトでは次の順で潰す。 1. **カレントで動かすなら `./script.sh`**(裸の名前は PATH しか探さない) 2. **実行権限を付ける** `chmod +x script.sh` 3. **常用するなら PATH に登録**(`~/bin` や `~/.local/bin`) 4. **移動・入替後に古い場所を掴む** ときは `hash -r` ::: ::: warning **前提(対象環境)** - シェル: bash(Ubuntu / Debian 系の既定) - 対象: 自分で書いた `.sh` スクリプトや自前ビルドのコマンド - zsh でも考え方は同じ。`hash -r` 等の細部のみ差がある ::: ## なぜ自作スクリプトは command not found になるのか? {#why} > **結論**: bash は裸のコマンド名を PATH 上のディレクトリだけから探す。カレントディレクトリは既定で PATH に含まれないため、`script.sh` と打っても見つからない。 ターミナルで `script.sh` と入力したとき、bash は次の順で実体を探す。 1. シェル関数・エイリアス・ビルトイン 2. `hash` にキャッシュ済みのパス 3. **環境変数 `PATH` に列挙されたディレクトリ**(先頭から順に) つまり PATH のどこにも `script.sh` が無ければ、たとえ目の前のディレクトリにファイルがあっても `command not found` になる。 ```bash $ ls script.sh $ script.sh bash: script.sh: command not found ``` カレントディレクトリ(`.`)が PATH に入っていないのは **既定の安全設計** である。`.` を PATH 先頭に入れると、`ls` などの正規コマンドと同名の悪意あるファイルをカレントに置かれたとき、それを誤実行してしまう。だから「いま居る場所のスクリプト」は明示的に指定する必要がある。 ## command not found と Permission denied はどう違うのか? {#two-errors} > **結論**: `command not found` は「見つからない」、`Permission denied` は「見つかったが実行権限が無い」。出るメッセージで切り分けの方向が変わる。 この 2 つは原因が別物で、対処も異なる。 | メッセージ | 意味 | 主な対処 | | ------------------- | -------------------------------- | --------------------------- | | `command not found` | PATH 上に該当コマンドが無い | `./` を付ける / PATH に登録 | | `Permission denied` | ファイルはあるが実行ビットが無い | `chmod +x` | | `bad interpreter` | shebang のパス誤り / CRLF 改行 | shebang 修正 / `dos2unix` | ```bash # パスは合っているが実行権限が無い場合 $ ./script.sh bash: ./script.sh: Permission denied ``` `./` を付けて初めて `Permission denied` に変わったなら、PATH 問題は解消し、次は権限の問題だと分かる。`bad interpreter` が出る場合は shebang か改行コードが原因なので、[「bad interpreter」エラーの直し方](/articles/troubleshooting/bad-interpreter-shebang)を参照。 ## まず ./ を付けて実行する {#run-relative} > **結論**: カレントにあるスクリプトは `./script.sh` で実行する。`./` はカレントディレクトリを明示するパス指定で、PATH 検索を経由しない。 ```bash # NG: PATH しか探さないので見つからない $ script.sh bash: script.sh: command not found # OK: カレントのファイルを直接指定 $ ./script.sh Hello ``` 別ディレクトリにあるなら絶対パスや相対パスで指定してもよい。 ```bash $ /home/alice/tools/script.sh $ ~/tools/script.sh $ bash script.sh # bash に渡せば実行ビット無しでも動く ``` ::: tip `bash script.sh` のようにインタプリタへ明示的に渡す方法なら、実行権限が無くても動く。「PATH 問題」と「権限問題」を一時的に切り分けたいときに有効。 ::: ## 実行権限が無いとどうなるのか? {#chmod} > **結論**: 実行ビットが無いと `./script.sh` は `Permission denied` で止まる。`chmod +x` で実行権限を付与する。 新規作成・ダウンロード・別環境からコピーしたスクリプトは、実行権限が落ちていることが多い。 ```bash $ ls -l script.sh -rw-r--r-- 1 alice alice 42 Jun 6 10:00 script.sh $ ./script.sh bash: ./script.sh: Permission denied ``` `-rw-r--r--` には `x`(実行ビット)が無い。付与する。 ```bash $ chmod +x script.sh $ ls -l script.sh -rwxr-xr-x 1 alice alice 42 Jun 6 10:00 script.sh $ ./script.sh Hello ``` ::: warning `chmod 777` は不要かつ危険。実行権限が欲しいだけなら `chmod +x`(または `chmod 755`)で十分。誰でも書き込み可能にする `777` はセキュリティリスクになる。 ::: ## PATH に自分のディレクトリを登録するには? {#path} > **結論**: 毎回 `./` を打ちたくないなら、スクリプト置き場を `PATH` に追加する。`~/.bashrc` に `export PATH` を書けば恒久化できる。 まず現在の PATH を確認する。 ```bash $ echo $PATH /usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin ``` スクリプトを置くディレクトリ(例: `~/bin`)を PATH に追加する。 ```bash # その場限り(現在のシェルのみ有効) $ export PATH="$HOME/bin:$PATH" ``` 恒久化するには `~/.bashrc`(ログインシェルでは `~/.profile`)に追記する。 ```bash # ~/.bashrc の末尾に追加 export PATH="$HOME/bin:$PATH" ``` ```bash # 追記後、現在のシェルに反映 $ source ~/.bashrc $ which script.sh /home/alice/bin/script.sh $ script.sh # ./ 無しで実行できる Hello ``` ::: tip **`~/bin` と `~/.local/bin`** Ubuntu の既定 `~/.profile` は、ログイン時に `~/bin` と `~/.local/bin` が存在すれば自動で PATH に追加する。この 2 つにスクリプトを置けば、設定追記なしで PATH が通ることが多い(反映には再ログインが必要な場合がある)。 ::: ::: warning PATH の区切りは `:`、追加位置で優先順が決まる。`$HOME/bin:$PATH` なら自分のスクリプトが優先、`$PATH:$HOME/bin` なら既存コマンドが優先。同名コマンドの上書き事故を避けたいなら末尾に足すのが無難。 ::: ## それでも見つからない・古い実体を掴むときは? {#hash} > **結論**: bash は一度見つけたコマンドのパスを `hash` にキャッシュする。スクリプトを移動・入れ替えた直後は古いパスを掴むことがある。`hash -r` でキャッシュを消す。 PATH も権限も正しいのに挙動がおかしいときは、`type` で「bash が何をどこから実行しようとしているか」を確認する。 ```bash $ type -a script.sh script.sh is /home/alice/bin/script.sh ``` `type` の結果が古い場所を指している、あるいは消したはずのコマンドがまだ動く場合はキャッシュが残っている。 ```bash # コマンドパスのキャッシュを消去 $ hash -r # 再確認 $ type -a script.sh ``` ::: tip `which` はファイルシステム上の PATH を実際に検索するが、`type` はエイリアス・関数・ビルトイン・hash キャッシュまで含めて「bash が実際にどう解決するか」を示す。原因切り分けには `type -a` が確実。 ::: ## チェックリストまとめ {#summary} > **結論**: 「`./` で実行 → `chmod +x` → PATH 登録 → `hash -r`」の順にたどれば、自作スクリプトの `command not found` はほぼ解決する。 上から順に確認する。 - [ ] カレントのスクリプトは `./script.sh` で実行している - [ ] `ls -l` で実行ビット(`x`)があり、無ければ `chmod +x` - [ ] `Permission denied` なら PATH ではなく権限の問題 - [ ] 常用するなら `~/bin` / `~/.local/bin` に置くか `export PATH` を `~/.bashrc` に追記 - [ ] `echo $PATH` に置き場が含まれているか確認 - [ ] 移動・入替後に挙動が変なら `hash -r` でキャッシュ消去 - [ ] `type -a script.sh` で実際の解決先を確認 次に読む記事: - [crontabが動かない時のチェックリスト](/articles/troubleshooting/crontab-not-running) - [「bad interpreter」エラーの直し方](/articles/troubleshooting/bad-interpreter-shebang) - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) # 「Cannot allocate memory」の対処 - overcommit と swap Source: https://penguin-gym-linux.com/articles/troubleshooting/cannot-allocate-memory ## 「Cannot allocate memory」とは何のエラーか? {#intro} > **結論**: メモリ確保の system call(`malloc`/`brk`/`mmap`/`fork`)がカーネルに拒否され `ENOMEM` が返った状態。プロセスは kill されず、確保「だけ」が失敗する。OOM killer とは別物で、`free` に空きがあっても出る。原因は物理メモリ不足とは限らず、overcommit 会計・`ulimit -v`・`max_map_count` など「上限」側にあることが多い。 `Cannot allocate memory` は errno `ENOMEM` の標準メッセージだ。アプリのログ・`dmesg`・`strace` などで次のように現れる。 ```output $ ./myapp fork: Cannot allocate memory $ python3 -c 'x = bytearray(8*1024**3)' MemoryError $ strace -f ./myapp 2>&1 | grep ENOMEM mmap(NULL, 1073741824, ...) = -1 ENOMEM (Cannot allocate memory) ``` ここで重要なのは、**OOM killer による強制終了とは挙動が違う**点だ。OOM killer は「メモリを使い切った後」にプロセスを kill してログを残す。`ENOMEM` は「確保しようとした瞬間」にカーネルが要求を**断る**だけで、プロセスは生きたままエラーを受け取る。だから `dmesg` に `Out of memory: Killed process` が無いのに `Cannot allocate memory` が出る、という食い違いが起きる。 | 観点 | Cannot allocate memory (ENOMEM) | OOM killer | | -------------- | ------------------------------- | ------------------------------- | | 発生タイミング | 確保 syscall の実行時 | 物理メモリ枯渇後 | | プロセス | 生存(エラーを受け取る) | SIGKILL で強制終了 | | dmesg ログ | 残らない | `Out of memory: Killed process` | | 主因 | overcommit 会計・各種上限 | 実メモリ + swap の枯渇 | ::: warning **前提(対象環境)** - OS: Ubuntu / 一般的な Linux - 症状: `malloc`/`fork`/`mmap` が `Cannot allocate memory` で失敗する - `free` には空きがあるように見えるケースを含む - `/proc/meminfo` / `ulimit` / `sysctl` を参照できる前提(恒久設定は `sudo` 必須) ::: ## なぜ空きメモリがあるのに出るのか?(overcommit の仕組み) {#overcommit} > **結論**: Linux はメモリ確保時に「確保を約束(commit)」する量を会計しており、`vm.overcommit_memory=2`(厳格モード)では commit 合計が `CommitLimit` を超えた瞬間に `ENOMEM` を返す。物理メモリの空きとは別の「約束済み総量(`Committed_AS`)」が上限に達すると、実際にはメモリが余っていても確保が断られる。 プロセスがメモリを確保しても、実際にページが割り当てられるのは初めて書き込んだ瞬間(demand paging)だ。確保の時点でカーネルが管理するのは「将来使うと約束した量」= commit である。この commit をどこまで許すかを決めるのが `vm.overcommit_memory` の 3 モード。 | モード | 値 | 挙動 | | ------ | ---- | ----------------------------------------------------------------------- | | 0 | 既定 | ヒューリスティック。明らかに過大な確保のみ拒否 | | 1 | - | 常に許可。`malloc` はほぼ NULL を返さない(後で OOM killer が走りうる) | | 2 | - | 厳格会計。commit 合計が `CommitLimit` 超過で即 `ENOMEM` | 厳格モード(2)の上限は `/proc/meminfo` で見える。 ```bash $ grep -i commit /proc/meminfo ``` ```output CommitLimit: 6029308 kB Committed_AS: 5980124 kB ``` `CommitLimit` は「約束してよい総量」、`Committed_AS` は「現在約束済みの総量」。後者が前者に迫ると新規確保が `ENOMEM` で弾かれる。`CommitLimit` は次式で決まる。 ```output CommitLimit = swap 総量 + 物理メモリ × (vm.overcommit_ratio / 100) ``` `vm.overcommit_ratio` の既定は 50。つまりモード 2 では既定で「swap + 物理メモリの半分」しか約束できない。物理メモリが空いていても、この会計上限に達すれば確保は失敗する——これが「空きがあるのに出る」典型パターンだ。 ::: tip モード 0(既定)でも、単発で巨大すぎる確保(例: 物理メモリ+swap を超える `mmap`)はヒューリスティックで拒否される。`Cannot allocate memory` を見たらまず `cat /proc/sys/vm/overcommit_memory` で今どのモードかを確認する。 ::: ## まず何を確認するのか?(切り分けの起点) {#triage} > **結論**: ①どの操作で出たか(`fork` か `malloc`/`mmap` か)、②`dmesg` に OOM が無いか、③`free -h` の実残量、④`grep -i commit /proc/meminfo` の会計残量、⑤`ulimit -v` のプロセス上限——をこの順で確認する。実メモリ枯渇・overcommit 会計・プロセス上限のどれが効いているかが、この 5 点でほぼ確定する。 最初に切り分けるべきは「本当にメモリが無いのか、それとも上限で断られたのか」だ。 ```bash # 1. OOM killer が走った形跡があるか(あれば実メモリ枯渇側) $ dmesg -T | grep -i -E 'out of memory|killed process' # 2. 実際の物理メモリ / swap 残量 $ free -h # 3. overcommit の会計残量(モード 2 で重要) $ grep -i -E 'commitlimit|committed_as' /proc/meminfo # 4. このシェル / プロセスの仮想メモリ上限 $ ulimit -v ``` 判断の早見表。 | 観測 | 疑う原因 | 進む先 | | ----------------------------------------------- | ------------------------ | ----------------------------------- | | `free` の available が極小 / OOM ログあり | 実メモリ枯渇 | swap 増設・メモリ削減 | | overcommit が 2 で `Committed_AS ≒ CommitLimit` | overcommit 会計上限 | [overcommit 調整](#overcommit-tune) | | `ulimit -v` が `unlimited` でない | プロセスの仮想メモリ上限 | [ulimit / cgroup](#limits) | | `mmap` だけが失敗 / 多数の領域を持つ | `max_map_count` 到達 | [max_map_count](#max-map-count) | ::: warning `free` の `available` を見ること(`free` 列ではない)。`available` はキャッシュ回収後に確保できる現実的な空き量で、実メモリ枯渇かどうかの一次指標になる。詳細は[メモリ不足の調べ方](/articles/troubleshooting/memory-troubleshooting)を参照。 ::: ## overcommit 設定が原因か?(vm.overcommit_memory) {#overcommit-tune} > **結論**: モードが 2 で `Committed_AS` が `CommitLimit` に張り付いているなら overcommit 会計が犯人。`vm.overcommit_ratio` を上げる、または swap を足すと `CommitLimit` が広がる。ただし会計を緩めるほど実際の OOM リスクは上がるため、根本はメモリ使用量の見直し。 現在のモードと比率を確認する。 ```bash $ cat /proc/sys/vm/overcommit_memory $ cat /proc/sys/vm/overcommit_ratio ``` ```output 2 50 ``` `CommitLimit` を広げる選択肢は 2 つ。比率を上げるか、swap を足すかだ。 ```bash # 比率を 80% に(物理メモリの 80% + swap まで約束可能にする) $ sudo sysctl -w vm.overcommit_ratio=80 $ grep -i commitlimit /proc/meminfo # 反映を確認 ``` 恒久化する。 ```bash $ echo 'vm.overcommit_ratio = 80' | sudo tee /etc/sysctl.d/99-overcommit.conf $ sudo sysctl --system ``` ::: warning モード 2 は「OOM killer を絶対に走らせたくない」用途(DB・組込み等)で意図的に使われる設定だ。比率を闇雲に上げると、せっかくの厳格会計の保護が薄れる。`Committed_AS` がなぜ大きいのか(過大なヒープ予約・プロセス数過多)を先に調べ、それでも足りないなら比率調整・swap 増設へ進む。 ::: ::: tip 逆に「`malloc` が NULL を返して困る」だけならモード 1(常に許可)も選択肢だが、確保は通っても書き込み時にメモリが無ければ OOM killer が走る。エラーの形が「`Cannot allocate memory`」から「突然 kill」へ移るだけなので、安易な変更は避ける。OOM 側の挙動は[OOM killer 発動時の対処](/articles/troubleshooting/oom-killer-handling)を参照。 ::: ## プロセスやユーザーの上限が原因か?(ulimit -v / cgroup) {#limits} > **結論**: `ulimit -v`(`RLIMIT_AS`)が `unlimited` でなければ、そのプロセスの仮想アドレス空間が頭打ちになり `ENOMEM` を返す。systemd 配下なら `MemoryMax` / cgroup の上限も同じく確保を断つ。システム全体は余裕でも、プロセス単位の上限で失敗しているケース。 ログイン中のシェルと、実際に動いているプロセスの両方を見る。 ```bash # 現在のシェルの仮想メモリ上限(KB、unlimited なら無制限) $ ulimit -v # 既に動いているプロセスの上限を直接確認(PID 指定) $ cat /proc//limits | grep -i 'address space' ``` ```output Max address space 2147483648 2147483648 bytes ``` `ulimit -v` がアプリの必要量より小さければ、それが直接の原因だ。systemd サービスとして動かしているなら、unit 側の上限も確認する。 ```bash $ systemctl show -p MemoryMax -p LimitAS myapp.service ``` サービスにメモリを与え直す場合は drop-in で設定する。 ```bash $ sudo systemctl edit myapp.service ``` ```output [Service] MemoryMax=4G LimitAS=infinity ``` ```bash $ sudo systemctl daemon-reload $ sudo systemctl restart myapp.service ``` ::: warning `ulimit -v` を `.bashrc` 等で絞っていると、そのシェルから起動した全プロセスに継承される。コンテナ(Docker の `--memory`)や cgroup の上限も同様に `ENOMEM` の原因になる。「特定ユーザー・特定サービスでだけ出る」なら、まず上限の継承元を疑う。 ::: ## mmap が ENOMEM を返すなら?(vm.max_map_count) {#max-map-count} > **結論**: メモリには余裕があるのに `mmap` だけが `Cannot allocate memory` で失敗するなら、1 プロセスが持てるメモリマップ領域数の上限 `vm.max_map_count`(既定 65530)に達した可能性が高い。Elasticsearch・大量スレッド・多数の共有ライブラリ読込で起きる。上限を引き上げて対処する。 `mmap` が原因かは `strace` で確定できる。 ```bash $ strace -f -e trace=mmap ./myapp 2>&1 | grep ENOMEM ``` ```output mmap(NULL, 262144, PROT_READ|PROT_WRITE, ...) = -1 ENOMEM (Cannot allocate memory) ``` 対象プロセスの現在のマップ数と上限を比べる。 ```bash # 現在のマップ領域数 $ wc -l < /proc//maps # システムの上限 $ sysctl vm.max_map_count ``` ```output 65530 65530 ``` 数が上限に張り付いていれば、上限を引き上げる。 ```bash $ sudo sysctl -w vm.max_map_count=262144 $ echo 'vm.max_map_count = 262144' | sudo tee /etc/sysctl.d/99-max-map-count.conf $ sudo sysctl --system ``` ::: tip `262144` は Elasticsearch などが公式に要求する代表値。値はワークロード依存なので、まず現在のマップ数(`/proc//maps` の行数)の伸びを観測し、それを上回る余裕を持たせる。引き上げ自体のメモリコストはわずかで、副作用は小さい。 ::: ## fork が失敗する場合は? {#fork} > **結論**: `fork: Cannot allocate memory` は、子プロセス用に親と同量の commit 予約が必要になり、overcommit 会計(モード 2)や実メモリ不足で予約できないときに出る。大きなプロセスから `fork` する設計が引き金。`posix_spawn` / `vfork` への置換、または overcommit・swap の見直しで対処する。 `fork` は copy-on-write でメモリを共有するが、会計上は「親の書き込み可能ページぶんの commit」を子のために予約しようとする。親が数 GB を抱えていると、その瞬間に commit が `CommitLimit` を超えて `ENOMEM` になる。 ```output $ ./big_parent fork: Cannot allocate memory ``` 対処の方向は 3 つ。 - **設計**: 巨大プロセスから直接 `fork`+`exec` せず、`posix_spawn` や小さなヘルパープロセス経由で子を起こす - **会計**: overcommit がモード 2 なら [比率引き上げ・swap 増設](#overcommit-tune) で `CommitLimit` を広げる - **緩和**: `vm.overcommit_memory=1`(常に許可)で予約チェックを外す(OOM リスクとのトレードオフ) ::: warning 同じ `fork` でも、エラーが `Resource temporarily unavailable`(`EAGAIN`)ならメモリではなく**プロセス数・スレッド数の上限**が原因で、対処が全く異なる。`ulimit -u` / `TasksMax` / `pid_max` 側の問題は[「fork: Resource temporarily unavailable」の対処](/articles/troubleshooting/fork-resource-unavailable)を参照。メッセージの末尾(`Cannot allocate memory` か `Resource temporarily unavailable` か)で進む先を分けること。 ::: ## 恒久対処はどう進めるか?(チェックリスト) {#checklist} > **結論**: `Cannot allocate memory` は「実メモリ不足」と「上限で断られた」の 2 系統に大別できる。OOM ログの有無で系統を分け、overcommit 会計(`CommitLimit`/`Committed_AS`)→ プロセス上限(`ulimit -v`/cgroup)→ `max_map_count` の順に潰す。会計や上限の緩和は対症療法であり、根本はメモリ使用量とのバランス。 - [ ] `dmesg` で OOM killer のログを確認したか(あれば実メモリ枯渇系統) - [ ] `free -h` の `available` で実残量を確認したか - [ ] `cat /proc/sys/vm/overcommit_memory` で現在のモードを確認したか - [ ] モード 2 なら `Committed_AS` が `CommitLimit` に迫っていないか確認したか - [ ] `ulimit -v` / `/proc//limits` の address space 上限を確認したか - [ ] systemd / cgroup / コンテナの `MemoryMax`・`LimitAS` を確認したか - [ ] `mmap` 失敗なら `vm.max_map_count` とマップ数を比べたか - [ ] `fork` 失敗なら errno が `ENOMEM` か `EAGAIN` か切り分けたか - [ ] 緩和(比率・swap・上限引き上げ)の前に、過大なメモリ使用の真因を調べたか ## 次に読む {#next} - [OOM killer 発動時の対処 - メモリ不足でプロセスが落ちた](/articles/troubleshooting/oom-killer-handling) - [メモリ不足の調べ方:free/top/ps と OOM Killer の見分け](/articles/troubleshooting/memory-troubleshooting) - [swap スラッシング(thrashing)の診断と対処 - メモリ逼迫で遅い](/articles/troubleshooting/swap-thrashing) # 「No such file or directory」なのにファイルがある時の原因 Source: https://penguin-gym-linux.com/articles/troubleshooting/cannot-stat-no-such-file ## この記事で解決できること {#intro} - `ls` で見えているのに `No such file or directory` が出る原因を切り分けられます - ファイル名の不可視文字・CRLF・壊れたシンボリックリンク・動的リンカ欠落を見分けられます - 「実体はあるのにアクセスできない」状態を、コマンドで確実に特定して直せます ::: tip **結論(最短の切り分け)** 「あるのに無い」と言われる原因はだいたい次の 4 つです。 1. **ファイル名に見えない文字が混入** → `ls -b` で可視化 2. **スクリプトの shebang が CRLF / 誤パス** → `cat -A` で行末確認 3. **シンボリックリンクのリンク先が消えている** → `ls -la` で赤表示・`readlink -f` 4. **実行バイナリの動的リンカ(ELF interpreter)が無い** → `file` で形式確認 まず `ls -b`(不可視文字)と `ls -la`(リンク切れ)の 2 つを見れば 8 割は切り分きます。 ::: ::: warning **前提(対象環境)** - OS:Ubuntu / Debian 系を想定(他ディストリでも考え方は同じ) - シェル:bash - 例では対象を `target` / `./run` 等の名前で示します ::: ## なぜファイルがあるのに「No such file or directory」が出るのか? {#why} > **結論**: 多くは「あなたが見ているファイル名」と「カーネルが探しているパス文字列」が一致していないか、ファイルの実体(リンク先・インタプリタ)が欠けているためです。 `ls` の表示は人間向けに整形されており、末尾の空白や制御文字、リンク切れの状態までは一目で分かりません。一方 `open()` / `stat()` といったシステムコールはパス文字列を 1 バイトも違わず照合します。つまり画面上は同じに見えても、実際のバイト列が違えば `ENOENT`(No such file or directory)が返ります。 このエラーは大きく 2 系統に分かれます。 - **パス文字列の不一致型**:名前に不可視文字、相対パスのずれ、CRLF 混入 - **実体欠落型**:シンボリックリンクのリンク先消失、実行バイナリの動的リンカ欠落 以降、頻度順に切り分けていきます。 ## 原因1: ファイル名に見えない文字が混入している {#hidden-chars} > **結論**: `ls -b` でファイル名をエスケープ表示し、末尾スペース・タブ・全角空白などの不可視文字を炙り出します。 コピー&ペーストやスクリプト生成で、ファイル名の末尾に半角スペースや改行、全角スペース(` `)が混入することがあります。見た目は `target` でも実体は `target `(末尾スペース付き)なら、`cat target` は当然 `No such file or directory` になります。 ```bash ls -b ``` ```output target\ ``` 末尾に `\ `(バックスラッシュ+スペース)が見えれば、名前に空白が含まれている証拠です。タブは `\t`、全角空白は `\343\200\200` のように 8 進数で表示されます。 確実に対象を指定するには、補完(Tab キー)を使うか、`find` で実バイト列を拾ってリネームします。 ```bash find . -maxdepth 1 -name 'target*' -print mv -i 'target ' target ``` ::: tip 迷ったら `ls | cat -A` も有効です。`cat -A` は行末を `$` で示すため、ファイル名末尾の余分な空白が `$` の手前に見えます。 ::: ## 原因2: スクリプトの shebang が CRLF / 誤パスになっている {#shebang} > **結論**: `./script.sh: No such file or directory` で本体は存在する場合、shebang 行の CRLF 改行か、存在しないインタプリタパスを疑います。 `./deploy.sh` を実行して `No such file or directory` が出るのに `ls -l deploy.sh` では確かに存在する——これは典型的な shebang 問題です。Windows で編集したファイルは行末が CRLF(`\r\n`)になり、`#!/bin/bash` が `#!/bin/bash\r` と解釈されます。カーネルは `/bin/bash\r` というパスのインタプリタを探して失敗します。 ```bash cat -A deploy.sh | head -1 ``` ```output #!/bin/bash^M$ ``` 行末に `^M`(CR)が見えれば CRLF 確定です。`sed` か `dos2unix` で除去します。 ```bash sed -i 's/\r$//' deploy.sh ``` shebang のパス自体が間違っているケースもあります(例:`#!/usr/local/bin/python` だが実体は `/usr/bin/python3`)。`head -1` でパスを確認し、`command -v` で実在を照合します。 ```bash head -1 deploy.sh command -v python3 ``` 詳しい手順は [「bad interpreter」エラーの直し方](/articles/troubleshooting/bad-interpreter-shebang) を参照してください。 ## 原因3: シンボリックリンクのリンク先が消えている {#dangling-symlink} > **結論**: リンク自体は `ls` に出ても、リンク先が消えた dangling symlink はアクセス時に `No such file or directory` を返します。`ls -la` の色表示と `readlink -f` で確認します。 シンボリックリンクは「別のパスへの参照」です。参照先が削除・移動されると、リンクファイル自体は残ったまま中身だけが消えます。`ls` には名前が出るのに開けない、という状態の典型です。 ```bash ls -la target ``` ```output lrwxrwxrwx 1 user user 18 Jun 6 10:00 target -> /opt/app/current/bin ``` カラー表示の端末では壊れたリンクが赤く点滅・反転表示されます。リンク先の実在は `readlink -f`(最終的な実体パスを解決)と、その結果への `ls` で確認します。 ```bash readlink -f target ls -la "$(readlink -f target)" ``` リンク先の `ls` が `No such file or directory` を返せば、リンク切れ確定です。リンクを張り直すか、正しい実体を復旧します。 ```bash ln -sfn /opt/app/releases/2026-06-06/bin target ``` ::: warning `cp file target` のようにリンク経由で書き込もうとすると、リンク先のディレクトリが無い場合に `cannot create ... No such file or directory` となります。エラーがリンク先を指していないか必ず確認してください。 ::: ## 原因4: 実行バイナリの動的リンカ(ELF interpreter)が無い {#missing-interpreter} > **結論**: コンパイル済みバイナリで `./run: No such file or directory` が出る場合、ファイル自体ではなく、それが要求する動的リンカ(ld-linux)や ABI が無いことが原因です。`file` で形式を確認します。 ELF 実行ファイルは、起動時に「動的リンカ」というローダーを必要とします。バイナリのアーキテクチャ(32bit/64bit、arm/x86)がホストと異なる、または必要な動的リンカが存在しないと、カーネルはローダーを `No such file or directory` として拒否します。混乱しやすいのは、**存在しないのはバイナリ本体ではなくローダー**である点です。 ```bash file ./run ``` ```output ./run: ELF 32-bit LSB executable, Intel 80386, dynamically linked, interpreter /lib/ld-linux.so.2, ... ``` `interpreter /lib/ld-linux.so.2` が指すローダーがホストに無ければ起動できません。要求するローダーの実在を確認します。 ```bash ls -l /lib/ld-linux.so.2 ``` 64bit ホストで 32bit バイナリを動かす場合は 32bit ランタイムを導入します。 ```bash sudo dpkg --add-architecture i386 sudo apt update && sudo apt install libc6:i386 ``` arm 用バイナリを x86 で実行しようとした、といったアーキテクチャ不一致なら、そもそも実行できません。`file` の出力でホストの `uname -m` と一致するか確認してください。 ## 原因5: パスの途中や相対パスがずれている {#path} > **結論**: 末端ファイルではなく、パスの途中ディレクトリが欠けている / 入れない場合も `No such file or directory` になります。`namei -l` で経路を 1 段ずつ追跡します。 `/opt/app/data/config.yml` を開けないとき、欠けているのは `config.yml` とは限りません。途中の `data` ディレクトリが存在しない、あるいはリンク切れの可能性があります。`namei -l` はパスを root から順に解決し、どこで失敗したかを示します。 ```bash namei -l /opt/app/data/config.yml ``` ```output f: /opt/app/data/config.yml drwxr-xr-x root root / drwxr-xr-x root root opt drwxr-xr-x root root app data - No such file or directory ``` `No such file or directory` が出た段(この例では `data`)が原因箇所です。 相対パスのずれも頻出です。`cd` した先が想定と違えば、同じ `./config.yml` でも別の場所を指します。`pwd` で現在地を確認し、迷ったら絶対パスで指定します。 ```bash pwd cat /opt/app/data/config.yml ``` ::: tip クォート内のチルダは展開されません。`cat "~/notes.txt"` はホームではなくカレントの `~/notes.txt` を探して失敗します。`cat ~/notes.txt`(クォートなし)か `cat "$HOME/notes.txt"` を使ってください。 ::: ## まとめ:上から順に試すチェックリスト {#next} 「あるのに無い」と言われたら、次の順で潰します。 - `ls -b target` — ファイル名の不可視文字を可視化([原因1](#hidden-chars)) - `cat -A script.sh | head -1` — shebang の CRLF / 行末を確認([原因2](#shebang)) - `ls -la target` / `readlink -f target` — シンボリックリンク切れを確認([原因3](#dangling-symlink)) - `file ./run` — 動的リンカ・アーキテクチャを確認([原因4](#missing-interpreter)) - `namei -l /path/to/file` — パスのどの段で落ちるか追跡([原因5](#path)) 関連記事も合わせてどうぞ。 - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) - [「bad interpreter」エラーの直し方](/articles/troubleshooting/bad-interpreter-shebang) - [自作スクリプトが command not found になる](/articles/troubleshooting/bash-command-not-found-path) # 時刻ずれによる証明書・ビルドエラー - clock skew の解決 Source: https://penguin-gym-linux.com/articles/troubleshooting/clock-skew-certificate ## この記事で解決できること {#intro} - `make` の **「Clock skew detected」** 警告と、TLS の **「certificate is not yet valid」** が同じ原因であることが分かる - 時計のずれが原因かどうかを `date` / `openssl` で **即切り分け** できる - 時計を正し、ビルドと証明書検証を **正常化する手順** が身につく ::: tip **結論(切り分けの型)** - 症状はバラバラでも、根本は **「system clock が実際の時刻からずれている」** の 1 点 - まず `timedatectl` と `date -u` で **本当にずれているか** を確認する - ずれていれば NTP で正し、ビルドは未来 mtime のファイルを `touch` し直す ::: ::: warning **前提(対象環境)** - OS: Ubuntu / Debian / RHEL 系(systemd + chrony もしくは systemd-timesyncd) - VM・コンテナ・Raspberry Pi など **時計が狂いやすい環境** を主に想定 ::: ## clock skew とは何か?なぜ証明書とビルドが壊れるのか {#what} > **結論**: clock skew は system clock が実時刻からずれた状態。ビルドはファイル mtime が未来に、証明書は有効期間(notBefore)が未来に見えるため、どちらも「未来の時刻」として弾かれる。 clock skew(クロックスキュー)とは、マシンの system clock が**正しい時刻からずれている**状態を指す。ずれの方向で症状が変わる。 - **時計が遅れている(過去)** → 取得したばかりの証明書が「まだ有効になっていない(not yet valid)」と判定される - **時計が進んでいる(未来)** → 生成したファイルの mtime が「未来」になり、ビルドツールが警告を出す 一見無関係なビルドエラーと証明書エラーが、**同じ「時刻の食い違い」**から生まれる点がこの問題の本質。証明書は `notBefore`(有効開始)/ `notAfter`(有効終了)という時刻の窓を持ち、`make` はファイルの新旧を mtime で比較する。どちらも「現在時刻」を基準にするため、基準そのものが狂うと連鎖的に壊れる。 ::: highlight **よくある発生源** - VM のサスペンド / 復帰、スナップショット復元で時計が巻き戻る - コンテナ起動時にホストの時計が狂っている - RTC(ハードウェアクロック)の電池切れで再起動のたびに時刻リセット - NFS でサーバとクライアントの時計がずれている(ファイル mtime が未来扱い) ::: ## まず時計がずれているかをどう確認するか {#diagnose} > **結論**: `timedatectl` で同期状態、`date -u` で現在の UTC を確認し、信頼できる外部時刻と比較する。数十秒以上ずれていれば clock skew が原因。 最初の診断は同期状態と現在時刻の確認。 ```bash $ timedatectl ``` ```output Local time: Sat 2026-06-06 12:00:00 JST Universal time: Sat 2026-06-06 03:00:00 UTC RTC time: Sat 2026-06-06 03:00:00 Time zone: Asia/Tokyo (JST, +0900) System clock synchronized: no NTP service: active ``` `System clock synchronized: no` は未同期のサイン。次に外部の信頼できる時刻と突き合わせる。HTTP レスポンスの `Date` ヘッダは手軽な基準になる。 ```bash $ date -u $ curl -sI https://www.google.com | grep -i '^date:' ``` 2 つの時刻が**数十秒〜数分以上ずれていれば clock skew が原因**と判断してよい。タイムゾーンの違い(JST と UTC)と取り違えないよう、比較は `date -u`(UTC)で行うのが安全。 ::: warning タイムゾーン設定(`Time zone`)のずれは clock skew ではない。`date -u` の UTC 値が正しければ、表示時刻の差はタイムゾーンの問題であり、証明書・ビルドには影響しない。 ::: ## ビルドの「Clock skew detected」をどう直すか {#build} > **結論**: 時計を正した後、未来 mtime のファイルを `find . -newermt now` で洗い出し `touch` で現在時刻に直す。`make clean` で作り直すのが最も確実。 `make` 実行時に次のような警告が出るのが典型。 ```output make: Warning: File 'main.o' has modification time 9876 s in the future make: warning: Clock skew detected. Your build may be incomplete. ``` これはソースやオブジェクトファイルの mtime が、現在の system clock より**未来**になっているために起きる。`make` は mtime の新旧で再ビルド要否を判断するため、未来のファイルがあると依存解決が壊れ、ビルドが不完全になる恐れがある。 手順は「①時計を正す → ②未来 mtime を直す」の順。先に時計を直さないと、`touch` しても再びずれた現在時刻が刻まれる。 ```bash # 1. 時計を正す(詳細は後述の #fix-clock) $ sudo timedatectl set-ntp true # 2. 未来 mtime のファイルを検出 $ find . -newermt now -type f # 3. 現在時刻に揃える $ find . -newermt now -type f -exec touch {} + ``` 最も確実なのは中間生成物を捨てて作り直す方法。 ```bash $ make clean && make ``` ::: tip NFS 上のソースで起きる場合は、**NFS サーバとクライアントの両方**の時計を同期する。片方だけ直しても、もう一方が刻んだ未来 mtime が残り再発する。 ::: ## 証明書の「not yet valid / expired」をどう直すか {#cert} > **結論**: `openssl x509 -noout -dates` で notBefore/notAfter を確認し、現在時刻が窓の外なら時計を疑う。時計を正せば証明書を再発行せずに解消することが多い。 時計が狂うと、正常な証明書でも検証に失敗する。ツール別の典型的なメッセージは次の通り。 ```output # curl curl: (60) SSL certificate problem: certificate is not yet valid # git clone (https) fatal: unable to access '...': server certificate verification failed # Go 製ツール x509: certificate has expired or is not yet valid # apt update E: Release file for ... is not valid yet (invalid for another 5h 30min ...) ``` `apt` の「is not valid yet」は clock skew の分かりやすい証拠。証明書側の有効期間を `openssl` で確認する。 ```bash # ローカルの証明書ファイル $ openssl x509 -in cert.pem -noout -dates # 接続先サーバの証明書 $ echo | openssl s_client -connect example.com:443 2>/dev/null \ | openssl x509 -noout -dates ``` ```output notBefore=Jun 5 00:00:00 2026 GMT notAfter=Sep 3 23:59:59 2026 GMT ``` 現在の UTC(`date -u`)が `notBefore` より**前**なら「not yet valid」、`notAfter` より**後**なら「expired」。どちらも証明書自体は正常で、**時計を正せば解消する**。 ::: warning 証明書を再発行したり `--insecure` で検証を無効化する前に、必ず時計を確認すること。原因が clock skew なら証明書の入れ替えは無意味で、検証無効化はセキュリティを損なうだけになる。CA 証明書やチェーン不備が原因の場合は [「certificate verify failed」の解決](/articles/troubleshooting/ssl-certificate-verify-failed) を参照。 ::: ## 時計を正しく直すには? {#fix-clock} > **結論**: `timedatectl set-ntp true` で NTP 同期を有効化し、`chronyc makestep` で即時補正。再起動後も維持するため `hwclock --systohc` で RTC へ書き戻す。 NTP 同期を有効化するのが基本。 ```bash $ sudo timedatectl set-ntp true $ timedatectl # System clock synchronized: yes を確認 ``` 大きくずれている場合、chrony は安全のため徐々に補正する。即時に合わせたいときは `makestep` を使う。 ```bash $ sudo chronyc makestep $ chronyc tracking # オフセットが 0 付近に収束したか確認 ``` 再起動でまた時刻がリセットされる(RTC 電池切れなど)場合は、正した system clock をハードウェアクロックへ書き戻す。 ```bash $ sudo hwclock --systohc ``` NTP 同期の仕組み・`systemd-timesyncd` と `chrony` の使い分けは [サーバ時刻ズレの対処(NTP 同期)](/articles/troubleshooting/ntp-time-skew) で詳しく扱う。 ::: highlight **VM / コンテナの注意点** - **コンテナ**はホストのカーネル時計を共有する。コンテナ内で NTP は動かせないため、**ホスト側**の時計を直す - **VM** はサスペンド復帰やスナップショット復元で時計が飛ぶ。ハイパーバイザのゲスト時刻同期を有効にするか、ゲスト内で NTP を常駐させる ::: ## 再発させないためのチェックリスト {#prevent} > **結論**: NTP 常駐・RTC 書き戻し・VM/NFS の時刻同期を恒久設定にすれば、clock skew 由来のビルド・証明書エラーはほぼ防げる。 | 対策 | コマンド / 設定 | 効果 | | -------------------------------- | ----------------------------------- | -------------------------- | | NTP 同期を常時有効 | `timedatectl set-ntp true` | 時刻ドリフトを自動補正 | | RTC へ正しい時刻を保存 | `hwclock --systohc` | 再起動後の時刻リセット防止 | | 監視で早期検知 | `chronyc tracking` のオフセット監視 | ずれを障害化する前に検知 | | NFS はサーバ・クライアント両同期 | 両ホストで NTP 有効化 | 未来 mtime の再発防止 | ::: tip **コピペ用:clock skew 診断ワンライナー** ```bash # 同期状態・UTC・外部時刻を一括確認 timedatectl; echo "--- local UTC ---"; date -u; \ echo "--- remote ---"; curl -sI https://www.google.com | grep -i '^date:' ``` ::: ## 次に読む {#next} - [サーバ時刻ズレの対処(NTP 同期の仕組み)](/articles/troubleshooting/ntp-time-skew) - [「certificate verify failed」の解決(CA 証明書・チェーン)](/articles/troubleshooting/ssl-certificate-verify-failed) - [DNS名前解決トラブルシューティング](/articles/troubleshooting/dns-troubleshooting) # 「Connection refused」の切り分け - サービス停止かポート閉塞か Source: https://penguin-gym-linux.com/articles/troubleshooting/connection-refused ## 「Connection refused」とは何を意味するのか? {#intro} > **結論**: 相手ホストには届いているが、そのポートで接続を**能動的に拒否**された状態。「届かない」のではなく「届いた上で断られた」点が重要で、原因の大半は接続先でサービスが起きていないこと。 `Connection refused` は、TCP 接続要求(SYN)に対して相手から **RST(リセット)** が返ってきたときに出る。つまりパケットは相手ホストに**到達している**。ホスト自身、またはその手前の機器が「このポートは受け付けない」と明示的に応答した結果だ。 ```output $ curl http://192.0.2.10:8080 curl: (7) Failed to connect to 192.0.2.10 port 8080 after 3 ms: Couldn't connect to server ``` ```output $ ssh user@192.0.2.10 ssh: connect to host 192.0.2.10 port 22: Connection refused ``` システムコールレベルでは `ECONNREFUSED` というエラーになる。アプリによって表示は異なり、ssh は「Connection refused」、curl は「Couldn't connect to server」と表示する。応答が**即座に**返ってくる(数ミリ秒)のも特徴で、これが後述の timeout との大きな違いになる。 ::: warning **前提(対象環境)** - OS: Ubuntu / 一般的な Linux - 接続先: TCP で待ち受けるサービス(SSH / Web / DB 等) - クライアント・サーバ双方にシェルで入れる前提で進める ::: ## Connection refused と timeout はどう違うのか? {#vs-timeout} > **結論**: refused は RST が**即返る**(ホストは生きている)、timeout は応答が**全く返らない**(パケットが捨てられている)。この違いだけで、原因がサービス側かネットワーク経路側かをほぼ二分できる。 最初に見るべきは「拒否されたのか」「無応答なのか」だ。エラーメッセージと**応答までの時間**で見分ける。 | 症状 | 返ってくるもの | 主な原因 | | -------------------- | -------------------- | ----------------------------------------------- | | Connection refused | TCP RST(即時) | サービス停止 / ポート違い / REJECT ルール | | Connection timed out | 無応答(数十秒待つ) | DROP するファイアウォール / 経路不通 / 機器停止 | ```bash $ time nc -zv 192.0.2.10 22 ``` ```output nc: connect to 192.0.2.10 port 22 (tcp) failed: Connection refused real 0m0.004s ``` 応答が `0.0xx` 秒で返れば refused(ホストは応答している)。十数秒〜数十秒待たされた末に失敗するなら timeout で、これは [ファイアウォールの DROP](#firewall) や経路の問題を疑う。本記事は **refused** 側を扱う。timeout 寄りの切り分けは [ポート疎通の確認](/articles/troubleshooting/port-connectivity)も参照。 ::: tip 「ホストは生きているがポートで断られている」のが refused。ping が通るかどうかは判断材料にならない(ICMP と TCP は別物で、ping 不可でも SSH は通ることがある)。 ::: ## なぜ Connection refused が出るのか? {#causes} > **結論**: 9 割は「接続先ポートで誰も Listen していない」。サービス停止・ポート番号違い・Listen アドレス違い・REJECT ルールの 4 つに整理でき、上から順に潰すのが速い。 refused が返る経路を原因別に並べると次のとおり。 | 原因 | 起きやすい状況 | 確認の起点 | | ------------------------- | ------------------------------------------------ | ------------------ | | サービスが停止している | 落ちた / 起動失敗 / 再起動後に上がっていない | `systemctl status` | | ポート番号が違う | 設定変更・デフォルト以外で待ち受けている | `ss -tlnp` | | Listen アドレスが違う | `127.0.0.1` だけで待ち受け、外から繋がらない | `ss -tlnp` | | ファイアウォールが REJECT | nftables/iptables が RST/ICMP で能動拒否している | `ss` + ルール確認 | 切り分けは **サーバ側で「本当に Listen しているか」を見る** ところから始めるのが確実だ。クライアント側のエラーだけ見ても、サービス停止とポート違いは区別できない。 ## サービスが起きているか確認するには? {#check-service} > **結論**: サーバ上で `ss -tlnp` を見て、目的のポートが LISTEN にあるか確認する。無ければサービス停止かポート違い。`systemctl status` と `journalctl` で起動状態と理由を裏取りする。 まずサーバにログインし、待ち受けているポート一覧を見る。 ```bash $ sudo ss -tlnp ``` ```output State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=812,fd=3)) LISTEN 0 511 127.0.0.1:8080 0.0.0.0:* users:(("node",pid=1043,fd=18)) ``` `ss` のオプションは `-t`(TCP)`-l`(LISTEN のみ)`-n`(名前解決せず数値表示)`-p`(プロセス表示、要 root)。目的のポートがこの一覧に**無ければ**、そのポートでは誰も待ち受けていない。クライアントから見れば即 refused になる。 ポートが見当たらないときは、対応するサービスの状態を確認する。 ```bash $ systemctl status nginx ``` ```output × nginx.service - A high performance web server Loaded: loaded (/lib/systemd/system/nginx.service; enabled) Active: failed (Result: exit-code) since ... ``` `Active: failed` や `inactive (dead)` なら停止している。起動を試み、失敗するなら理由をログで追う。 ```bash $ sudo systemctl start nginx $ journalctl -u nginx -n 30 --no-pager ``` ::: tip サービスが上がっているのに refused が出る場合は、ほぼ **Listen アドレスかポート番号の食い違い**。次のセクションへ進む。 ::: ## Listen アドレスの落とし穴(127.0.0.1 問題) {#listen-address} > **結論**: `127.0.0.1:ポート` で待ち受けるサービスは、同じサーバ内からは繋がるが**外部からは必ず refused** になる。`ss -tlnp` の Local Address 列が `127.0.0.1` か `0.0.0.0`(または `::`)かを必ず確認する。 これは「サービスは動いているのに外から繋がらない」典型パターンだ。先ほどの出力をもう一度見る。 ```output LISTEN 0 128 0.0.0.0:22 ... sshd LISTEN 0 511 127.0.0.1:8080 ... node ``` - `0.0.0.0:22` → **全インターフェース**で待ち受け。外部から接続可能。 - `127.0.0.1:8080` → **ループバックのみ**。サーバ内 `curl localhost:8080` は通るが、外部や別ホストからは refused。 サーバ内では成功し、外からは refused になるなら、ほぼこれが原因だ。アプリの待ち受けアドレス設定(多くは `bind` / `listen` / `host` 等の項目)を `0.0.0.0` や対象 IP に変更し、再起動する。 ```bash # サーバ内で成功(ローカルなので届く) $ curl -sS http://127.0.0.1:8080/ >/dev/null && echo OK # 外部・別ホストからは refused になる $ curl http://192.0.2.10:8080/ curl: (7) Failed to connect ... Couldn't connect to server ``` ::: warning DB(PostgreSQL / MySQL 等)はデフォルトで `127.0.0.1` 待ち受けの製品が多い。外部接続させる設計変更は、認証・ファイアウォールの見直しとセットで行う。安易に `0.0.0.0` へ開けない。 ::: ## ファイアウォールが原因の場合(REJECT と DROP) {#firewall} > **結論**: ファイアウォールが `REJECT` だと RST/ICMP を返すため **Connection refused** に、`DROP` だとパケットを捨てるため **timeout** になる。refused が出るなら REJECT ルールの有無を疑う。 ファイアウォールの拒否方法は 2 種類あり、クライアントに出る症状が変わる。 - **REJECT**: 拒否を明示的に通知(TCP RST または ICMP port-unreachable)→ クライアントは即 `Connection refused` - **DROP**: 黙ってパケットを破棄 → クライアントは応答待ちの末 `timeout` つまり refused が出ている時点で、DROP 型のファイアウォール(ufw のデフォルト deny は DROP)は主犯ではないことが多い。それでも明示 REJECT ルールが入っていないかは確認する。 ```bash # nftables のルール確認 $ sudo nft list ruleset | grep -i -E 'reject|dport' # iptables を使う環境 $ sudo iptables -L -n -v --line-numbers ``` ```output Chain INPUT (policy ACCEPT) num target prot ... destination 1 REJECT tcp ... tcp dpt:8080 reject-with tcp-reset ``` 上記のように `REJECT ... reject-with tcp-reset` が該当ポートに入っていれば、それが refused の発生源だ。ルールの要否を判断し、不要なら削除する。ufw を使っているなら許可ルールの確認と復旧手順は [ufw でSSHが繋がらない時](/articles/troubleshooting/ufw-ssh-troubleshooting)を参照。 ::: tip 切り分けの近道: **サーバ内のループバック接続**(`curl 127.0.0.1:ポート`)が通り、外部だけ refused なら、原因は Listen アドレスかファイアウォール。サーバ内でも refused なら、サービス停止かポート違いに絞れる。 ::: ## クライアント側からの切り分け手順 {#client-side} > **結論**: サーバに入れないときは `nc -zv` で疎通だけを確認し、refused か timeout かを切り分ける。`ss -tn` で接続が確立まで進んでいるかも見える。 サーバにすぐ入れない状況では、クライアント側から最小限の確認をする。 ```bash # ポートに繋がるかだけを確認(-z: 送信せず接続のみ, -v: 詳細) $ nc -zv 192.0.2.10 22 ``` `Connection refused` が即返れば、ホストは応答しているがポートが閉じている。`timed out` なら経路かファイアウォール DROP を疑い、本記事ではなく [ポート疎通の確認](/articles/troubleshooting/port-connectivity)へ。 接続を試みた後の TCP 状態を見ると、ハンドシェイクがどこまで進んだか分かる。 ```bash $ ss -tn dst 192.0.2.10 ``` `SYN-SENT` のまま留まるなら応答が返っていない(timeout 系)。refused では接続自体が成立せず、即座に失敗する。 ::: warning ポートスキャンや疎通確認は、**自分が管理する、または許可を得たホストに対してのみ**行う。第三者のホストへの無断スキャンは避ける。 ::: ## それでも直らないときのチェックリスト {#checklist} > **結論**: refused は「ホスト生存・ポート拒否」が確定した状態。サービス → ポート → Listen アドレス → REJECT ルールの順に潰せば、原因はほぼこの 4 つのどれかに収束する。 - [ ] エラーは `refused`(即時)か `timed out`(待たされる)か区別したか - [ ] サーバ上で `ss -tlnp` し、目的ポートが LISTEN にあるか確認したか - [ ] LISTEN が無いなら `systemctl status` / `journalctl -u` で停止理由を見たか - [ ] Local Address が `127.0.0.1` になっていないか(外部接続なら `0.0.0.0` 等が必要) - [ ] ポート番号は接続側と待ち受け側で一致しているか - [ ] `nft list ruleset` / `iptables -L` に該当ポートの REJECT ルールが無いか - [ ] サーバ内ループバック(`curl 127.0.0.1:ポート`)は通るか(通れば原因は外側) ## 次に読む {#next} - [ポート疎通の確認:ss / lsof / nc / curl で原因切り分け](/articles/troubleshooting/port-connectivity) - [「Address already in use」の解決 - ポート競合の特定と解放](/articles/troubleshooting/address-already-in-use) - [ufw でSSHが繋がらなくなった場合](/articles/troubleshooting/ufw-ssh-troubleshooting) # 「Connection timed out」の切り分け - ファイアウォール・経路 Source: https://penguin-gym-linux.com/articles/troubleshooting/connection-timed-out ## 「Connection timed out」とは何を示すのか? {#intro} > **結論**: 接続要求を送ったのに**何も応答が返ってこない**状態。相手や経路上の機器がパケットを黙って捨てている(DROP)か、相手が落ちていて、カーネルが再送を繰り返した末に諦める。エラーが返るまで**数秒〜十数秒待たされる**のが最大の特徴。 `Connection timed out` は、接続先へ SYN を送ったのに SYN+ACK が返らず、カーネルが規定回数の再送を尽くして諦めたときに出る。システムコールレベルでは `connect()` が `ETIMEDOUT` を返し、アプリはこれを「Connection timed out」と表示する。 ```output $ ssh user@192.0.2.10 ssh: connect to host 192.0.2.10 port 22: Connection timed out ``` ```output $ curl http://192.0.2.10/ curl: (28) Failed to connect to 192.0.2.10 port 80 after 129000 ms: Connection timed out ``` `refused`(即座に弾かれる)や `No route to host`(即座に到達不能と返る)と決定的に違うのは、**長い沈黙のあとに失敗する**点。これは「誰かがパケットを黙って捨てている」サインで、ほとんどの場合**ファイアウォールの DROP**か**相手の不在**を意味する。 ::: warning **前提(対象環境)** - OS:Ubuntu / 一般的な Linux(`ss` / `traceroute` / `tcpdump` を使用) - 対象:IP・ポートを指定して接続したいホスト - 一部の確認は `sudo`(`tcpdump` / `nft` 等)が必要 ::: ## refused・No route to host とどう違うのか? {#vs-others} > **結論**: refused は「届いたがポートで拒否(即 RST)」、No route to host は「経路が無い(即 ICMP)」、timed out は「応答が一切無い(長い待ち)」。同じ「接続できない」でも、**応答が返るまでの時間**で原因の層が分かる。 接続エラーは応答の返り方で 3 系統に分かれる。まずどれなのかを確定させる。 | エラー | 返ってくるもの | 原因の層 | | -------------------- | --------------------- | ------------------------------- | | Connection refused | TCP RST(即座) | サービス / ポート(L4・アプリ) | | No route to host | ICMP 到達不能(即座) | 経路 / ARP / REJECT(L2〜L3) | | Connection timed out | 無応答(長い待ち) | DROP / 沈黙する経路(L3〜L4) | ```bash $ time curl -sS http://192.0.2.10/ ``` ```output curl: (28) Failed to connect to 192.0.2.10 port 80 after 129000 ms: Connection timed out real 2m9.012s ``` 失敗まで**数秒以上**かかったなら timed out 系で、誰かがパケットを黙って捨てている。即座に弾かれたなら別記事へ。サービス停止・ポート閉塞は [Connection refused の切り分け](/articles/troubleshooting/connection-refused)、経路が無い場合は [No route to host の切り分け](/articles/troubleshooting/no-route-to-host) を参照。本記事は **Connection timed out** を扱う。 ::: tip refused は「ホストは生きているがポートが拒否した」、timed out は「そもそも誰も返事をしない」。前者は RST という明確な返事があり、後者は沈黙。沈黙は DROP(破棄)の特徴で、ファイアウォールが `-j DROP` で捨てているか、相手が居ない。 ::: ## なぜ「Connection timed out」は起きるのか? {#causes} > **結論**: 原因は 4 つに集約される。①ファイアウォールの DROP(最頻)、②クラウドのセキュリティグループ未開放、③相手ホストの停止・ポート未待受、④経路途中のブラックホール。自分のホストから相手へ向かって順に切り分ける。 ETIMEDOUT を生む経路を原因別に整理する。 | 原因 | 何が起きているか | 最初に見る場所 | | -------------------------- | ---------------------------------------------------- | -------------------- | | ファイアウォール DROP | サーバ / 経路上の機器が SYN を黙って破棄 | `nft list ruleset` | | セキュリティグループ未開放 | クラウドの SG / NACL が該当ポートを許可していない | クラウド管理画面 | | 相手ホスト停止・未待受 | サーバ自体が落ちている / サービスが起動していない | サーバ側 `ss -ltn` | | 経路途中のブラックホール | ルータ設定ミス / VPN・ルーティングの穴でパケット消失 | `traceroute` / `mtr` | 切り分けは**自分のホストに近い層から外側へ**進める。まず応答時間で timed out だと確定し、次に経路のどこまで届くか、最後にサーバ側とファイアウォールを見る。 ::: warning DROP と REJECT は対照的。**DROP** はパケットを黙って捨てるため client は無応答で `timed out` になる。**REJECT** は ICMP や RST を返すため `No route to host` や `refused` になる。つまり「長い沈黙=DROP」「即エラー=REJECT」と、症状からファイアウォールの捨て方が逆算できる。 ::: ## 最初に何を確認するのか?(応答時間を測る) {#first} > **結論**: `curl --connect-timeout` や `nc -w` で待ち時間を区切り、接続だけを試す。指定秒数いっぱい待ってから失敗するなら無応答(timed out)が確定する。デフォルトの長いタイムアウトを待つ必要はない。 まず短いタイムアウトを明示して、接続フェーズだけを試す。デフォルト(curl は約 2 分)を待つのは時間の無駄。 ```bash # 接続確立だけに 5 秒の制限をかける $ curl -sS --connect-timeout 5 http://192.0.2.10/ ``` ```output curl: (28) Failed to connect to 192.0.2.10 port 80 after 5001 ms: Connection timed out ``` `nc`(netcat)なら到達確認をさらに端的に行える。 ```bash $ nc -vz -w3 192.0.2.10 80 ``` ```output nc: connect to 192.0.2.10 port 80 (tcp) failed: Connection timed out ``` 指定した秒数いっぱい(上では 3 秒)待ってから失敗したら、相手は SYN に何も返していない。即座に `refused` と出たならポートは閉じているがホストは応答しており、これは timed out ではない。 ::: tip `--connect-timeout` は**接続確立まで**の制限で、`-m`(`--max-time`)は**通信全体**の制限。切り分けでは接続フェーズだけを見たいので `--connect-timeout` を使う。`nc -w` のタイムアウトも同様に接続待ちを区切る。 ::: ## 経路のどこで止まっているのか?(traceroute / mtr) {#path} > **結論**: `traceroute` / `mtr` で、自分のホストから相手まで ICMP/UDP/TCP がどのホップまで届くかを見る。特定のホップから先がすべて `* * *` になる地点が、パケットが消えている(DROP されている)境界。 経路上のどこでパケットが消えているかを可視化する。 ```bash $ traceroute -n 192.0.2.10 ``` ```output 1 10.0.0.1 0.4 ms 0.3 ms 0.3 ms 2 100.64.0.1 1.2 ms 1.1 ms 1.0 ms 3 * * * 4 * * * ``` ホップ 2 までは応答があり、3 以降がすべて `* * *` なら、その境界でパケットが黙って捨てられている。ここから先がブラックホールで、ファイアウォールやルーティングの穴が疑わしい。 ただし `traceroute` は ICMP/UDP を使うため、**TCP は通るが ICMP だけ遮断**されている環境では誤って「途中で止まる」ように見える。実際に接続したいポートで試すには TCP モードを使う。 ```bash # 宛先ポート 80 へ TCP SYN で経路を辿る $ sudo traceroute -T -p 80 -n 192.0.2.10 ``` 連続して状態を見たいときは `mtr` が便利。 ```bash $ mtr -n -T -P 80 192.0.2.10 ``` ::: warning 最終ホップだけが `* * *` で手前まで届く場合、相手のファイアウォールが ICMP は落とすが TCP は通すケースもある。「traceroute が途中で切れる」だけで DROP と断定せず、必ず**接続したいプロトコル・ポート**(`-T -p`)でも確認する。 ::: ## SYN に応答が無いことをどう確認するのか?(tcpdump) {#tcpdump} > **結論**: `tcpdump` で実際のパケットを観測すると、SYN を送っているのに SYN+ACK が返っていないことが直接見える。client 側で「送るだけで返事が無い」、server 側で「SYN すら届いていない」のどちらかで、DROP の位置が絞れる。 確証が欲しければパケットを直接見る。client 側でキャプチャしながら接続を試す。 ```bash # 別端末で実行してから接続を試す $ sudo tcpdump -ni any host 192.0.2.10 and port 80 ``` ```output 10:00:00.100 IP 10.0.0.42.51000 > 192.0.2.10.80: Flags [S], seq 1, ... 10:00:01.100 IP 10.0.0.42.51000 > 192.0.2.10.80: Flags [S], seq 1, ... 10:00:03.100 IP 10.0.0.42.51000 > 192.0.2.10.80: Flags [S], seq 1, ... ``` `Flags [S]`(SYN)だけが等間隔(1s, 2s, 4s…)で再送され、`Flags [S.]`(SYN+ACK)が返っていない。これが timed out の正体で、相手まで届いていないか、届いても黙殺されている。 もしサーバにログインできるなら、**サーバ側でも**同時にキャプチャすると決定的になる。 ```bash # サーバ側で実行 $ sudo tcpdump -ni any tcp port 80 ``` - サーバ側に SYN が**届いていない** → 経路上(途中のルータ / クラウド SG)で DROP - サーバ側に SYN は**届くが応答しない** → サーバのローカルファイアウォール DROP か、ポート未待受 ::: tip SYN の再送間隔(約 1, 2, 4, 8 秒…の指数バックオフ)が見えたら、それ自体が「無応答」の動かぬ証拠。再送回数は `net.ipv4.tcp_syn_retries`(既定 6)で決まり、これが client 側の総待ち時間を左右する。 ::: ## ファイアウォール DROP をどう見極めるのか? {#firewall} > **結論**: timed out かつ経路が相手まで届いているなら、`-j DROP` 系のルールが最有力。サーバ側で `nft list ruleset` / `iptables -L` を確認し、該当ポートが許可されているかを見る。クラウドでは OS のファイアウォールより**手前のセキュリティグループ**が原因のことが多い。 サーバ側でフィルタリングルールを確認する。 ```bash # nftables のルールを確認 $ sudo nft list ruleset | grep -i -E 'drop|policy' # iptables の場合 $ sudo iptables -L -n -v --line-numbers ``` ```output Chain INPUT (policy DROP) pkts bytes target prot ... destination ... ... ACCEPT tcp ... tcp dpt:22 ``` `policy DROP` で、許可されているのが 22 番だけなら、80 番への SYN は黙って捨てられ client は timed out になる。`ufw` を使っているなら許可状況を確認する。 ```bash $ sudo ufw status verbose ``` ufw で SSH まで巻き込んで締め出した場合の復旧は [ufw でSSHが繋がらない時](/articles/troubleshooting/ufw-ssh-troubleshooting) を参照。 ::: warning **クラウド環境(AWS / GCP / Azure 等)では、OS のファイアウォールより手前のセキュリティグループ / ネットワーク ACL が DROP していることが圧倒的に多い。** `nft list ruleset` が空でも timed out なら、まずクラウド側の SG で該当ポート(と送信元 IP レンジ)が許可されているかを管理画面で確認する。SG の既定は「受信全拒否(= DROP 相当)」。 ::: ## サーバ側で待ち受けているか? {#listen} > **結論**: ファイアウォールが開いていても、サービスが起動していなければ繋がらない。サーバ側で `ss -ltn` を見て、該当ポートが LISTEN しているか、待受アドレスが `127.0.0.1` に閉じていないかを確認する。 サーバにログインできるなら、ポートが待ち受けているかを直接見る。 ```bash $ ss -ltn ``` ```output State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 127.0.0.1:80 0.0.0.0:* LISTEN 0 128 0.0.0.0:22 0.0.0.0:* ``` ここで 80 番が `127.0.0.1:80` で待ち受けていると、**ローカルからしか接続できず**外部からは届かない(ファイアウォールを開けても無駄)。`0.0.0.0:80`(全インターフェース)または該当 IP で待ち受けるよう、サービスの bind 設定を直す。 該当ポートが一覧に**無い**なら、サービスが起動していない。起動状態を確認する。 ```bash $ systemctl status nginx ``` ::: tip `127.0.0.1` 待受は timed out ではなく即 `refused` になるのが本来だが、その前段のファイアウォールが DROP していると症状が timed out で覆い隠される。「ファイアウォールを開けたのに繋がらない」ときは bind アドレスを必ず疑う。 ::: ## まだ繋がらないときのチェックリスト {#checklist} > **結論**: timed out は「無応答」を意味する。自分のホストから外側へ、応答時間 → 経路 → SYN 応答 → ファイアウォール → 待受、の順に追えば、原因は DROP・SG・サービス停止・経路ブラックホールのいずれかに収束する。 - [ ] 失敗まで**数秒以上**かかったか(即エラーなら refused / No route to host) - [ ] `curl --connect-timeout` / `nc -w` で接続フェーズだけを区切って確認したか - [ ] `traceroute -T -p ` で、接続したいポートでも経路を辿ったか - [ ] どのホップから先が `* * *` になるか(DROP 境界)を特定したか - [ ] `tcpdump` で SYN が再送され SYN+ACK が返らないことを確認したか - [ ] サーバ側にも SYN が届いているか(届かない=経路 / SG、届く=ローカル FW / 未待受) - [ ] `nft list ruleset` / `iptables -L` に該当ポートを落とす `DROP` が無いか - [ ] クラウドのセキュリティグループ / NACL で該当ポートと送信元が許可されているか - [ ] `ss -ltn` で該当ポートが `0.0.0.0`(外部公開)で LISTEN しているか ## 次に読む {#next} - [「No route to host」の切り分け:ルーティングとファイアウォール](/articles/troubleshooting/no-route-to-host) - [「Connection refused」の切り分け:サービス停止 vs ポート閉塞](/articles/troubleshooting/connection-refused) - [ポート疎通の確認(ss / lsof / nc / curl)](/articles/troubleshooting/port-connectivity) # CPU100%の調べ方:top/ps/load average で原因プロセスを特定する Source: https://penguin-gym-linux.com/articles/troubleshooting/cpu-high-load ## この記事で解決できること {#intro} - CPUが100%近く張り付いたときに、原因プロセスを最短で特定できます - 「CPUが原因」なのか、「I/O待ち」や「スワップ地獄」なのかを切り分けできます - 一時復旧(再起動)だけで終わらせず、次に繋がる証拠(ログ・値)を残せます ::: tip **結論(最短ルート)** CPUが高いときは、次の順で見れば迷いません。 1. **まず全体状況**:`uptime`(load average) 2. **topで犯人を見る**:`top`(CPU順、%us/%sy/%wa も見る) 3. **psで上位を確定**:`ps aux --sort=-%cpu | head` 4. **サービスならログへ**:`systemctl status` / `journalctl -u` 5. **CPUに見えて実は…を除外**:I/O待ち(wa)/メモリ不足(swap) ::: ::: warning **前提(対象環境)** - OS:Ubuntu - 対象:サーバ触り始めた新人 - `sudo` 可能 - 目的:切り分け → 原因特定 → 再発防止の入口 ::: ## 1. 「CPU100%」は2種類ある(ここが盲点) {#types} > **結論**: CPU高負荷は計算が重いタイプAとI/O待ち・メモリ不足のタイプBに分類しまず%waを確認して切り分ける。 CPUが高いとき、原因はだいたい次のどちらかです。 ### タイプA:本当にCPU計算が重い - 計算/ループ/暗号化/変換/集計 - topで特定プロセスがCPUを食っている - `%us`(ユーザーCPU)が高いことが多い ### タイプB:CPUが高いように見えるが、実はI/O待ち・メモリ不足 - ディスクが遅い(ログ肥大、DB、EBS劣化、I/O上限) - Swapが増えて激重(メモリ不足) - `%wa`(I/O wait)が高いことが多い ::: warning "CPU100%だからCPUを増やす" は早計です。まず `%wa` と swap を見て「本当にCPUか?」を確定します。 ::: ## 2. まず uptime で load average を見る(全体の圧を知る) {#uptime} > **結論**: uptimeのload averageはCPUコア数と比較して全体の圧を把握するが次のtopで中身を確認するまで原因は断定しない。 ```bash $ uptime ``` 出力例: ```output 14:05:12 up 10 days, 3:20, 2 users, load average: 3.52, 3.10, 2.90 ``` **load average とは(新人向けに最小限):** - CPUコア数が4なら、loadが **4前後** で "混んでる" - loadが **10** とかなら、かなり詰まってる ::: tip ただし load はCPUだけでなくI/O待ちも含むので、次の top で中身を見る必要があります。 ::: ## 3. top で「犯人」と「CPU内訳」を見る {#top} > **結論**: topのP順ソートで犯人プロセスを特定し%us/%wa/%syのCPU内訳でタイプAかタイプBかを判断する。 ```bash $ top ``` ### 3-1. top の操作(必須) - `P`:CPU順に並べ替え - `M`:メモリ順(メモリ不足が疑わしいとき) - `1`:CPUコアごとの表示(マルチコアで偏りを見る) - `q`:終了 ### 3-2. CPU内訳(%us / %sy / %wa)を読む topの上部に出ます。 - `%us`:アプリが計算してCPUを使っている - `%sy`:カーネル/システム処理が多い(ネットワーク/ディスク/割り込みなど) - `%wa`:I/O待ち(ディスクが遅い・詰まってる) **判断の例:** - `%us` が高い → アプリ処理が重い(タイプA) - `%wa` が高い → I/Oが詰まってる(タイプB) - `%sy` が高い → システム側の処理が多い ## 4. ps でCPU上位を確定する(証拠を残す) {#ps} > **結論**: ps aux --sort=-%cpu | head -n 20でCPU上位プロセスの証拠を記録してから再起動などの判断を行う。 topは一瞬の状態ですが、psで一覧を残せます。 ```bash $ ps aux --sort=-%cpu | head -n 20 ``` 特に見るべき列: - `COMMAND`:何がCPUを食っているか - `%CPU`:どれが突出しているか もし同じ種類が大量なら(ワーカー増殖)、上限設定が無い/負荷が高すぎる方向に寄ります。 ## 5. プロセスがサービスなら systemctl / journalctl {#systemctl} > **結論**: CPUを食うプロセスがサービスならsystemctl statusとjournalctl -u -n 200でエラーログや再起動ループを確認する。 プロセスが nginx や apache2 ではなく、アプリ(php-fpm、node、gunicorn等)なら、ログに原因が出ていることが多いです。 ### 5-1. サービス状態 ```bash $ sudo systemctl status nginx $ sudo systemctl status apache2 $ sudo systemctl status php8.1-fpm ``` ### 5-2. ログ(直近200行) ```bash $ sudo journalctl -u nginx -n 200 $ sudo journalctl -u php8.1-fpm -n 200 ``` ::: tip CPUが高いときは、同時に「大量リクエスト」「エラー連発」「再起動ループ」が起きがちです。まずはログで "何が連発しているか" を見ます。 ::: ## 6. 「CPUに見えて実は…」を潰す(重要) {#actually} > **結論**: free -hでSwapを確認しiostat -x 1 5でI/O待ちを確認してCPU増強前にメモリ・I/O問題を除外する。 ここを飛ばすと、対策を間違えます。 ### 6-1. メモリ不足(Swap)の有無を確認 ```bash $ free -h ``` Swapが増えている/availableが小さい場合、CPU問題に見えても根がメモリの可能性があります。 ### 6-2. I/O待ち(%wa)が高いなら、ディスクI/Oを疑う - ログ肥大 - DB - Dockerのレイヤ増殖 - ストレージ性能不足 ```bash $ iostat -x 1 5 ``` ::: warning I/O待ちが原因なら、CPUを増やしても改善しないことが多いです。 ::: ## 7. よくある原因パターン(現場で多い順) {#patterns} > **結論**: 無限ループ・ボット攻撃・ワーカー過多・I/O詰まりが現場で多い原因でパターンを把握すると対処が速くなる。 ### パターン1:無限ループ/バグ/暴走 - 1プロセスがずっとCPUを占有(%us高) - 対処:ログ・直前のデプロイ・最近の変更点を確認 ### パターン2:ボット/スキャン/アクセス集中 - nginx/apacheのaccessが爆増 - 対処:Webログを見る、レート制限/WAF/キャッシュ検討 ### パターン3:ワーカー過多(php-fpm等) - 子プロセスが増え続けてCPUもメモリも食う - 対処:ワーカー数の上限設定(MaxChildrenなど) ### パターン4:I/Oが遅くて詰まり、結果CPUが高く見える - %waが高い - 対処:ディスクI/O調査 ## 8. やってはいけないこと {#mistakes} > **結論**: 証拠を取らない再起動・%waを見ないCPU増強・ワーカー上限なしの放置がCPU高負荷対応の典型的失敗パターン。 ### やってはいけない1:証拠を取らずに再起動 最低限、これを残してから判断してください。 - `uptime` - `free -h` - `ps aux --sort=-%cpu | head` - `top`(可能なら画面キャプチャ) ### やってはいけない2:%waを見ずにCPU増強 I/O待ちならCPU増強は無駄打ちになりがちです(機会損失)。 ### やってはいけない3:ワーカー上限なしを放置 アクセスが増えた瞬間に壊れます。上限を設けるのが先です。 ::: tip **コピペ用:CPU高負荷 調査テンプレ** ```bash # 1) 全体 uptime # 2) メモリも同時に(CPUに見えて実は…を潰す) free -h # 3) CPU上位(証拠) ps aux --sort=-%cpu | head -n 20 # 4) topで内訳(%us/%sy/%wa)と犯人確認 top # 5) サービスならログ sudo systemctl status sudo journalctl -u -n 200 ``` ::: ::: tip **まとめ** - CPU高負荷は「本当にCPU」か「I/O待ち/メモリ不足」をまず分ける - `uptime → top → ps → journalctl` の順が最短 - 再起動は最後。証拠を残してから判断する ::: ## 次に読む {#next} - [システムログの確認方法](/articles/tutorials/journalctl-basics) - [サービスの管理と状態確認も重要](/articles/tutorials/systemctl-basics) - [I/O待ちが原因の場合](/articles/troubleshooting/disk-io-troubleshooting) - [メモリ不足の調べ方](/articles/troubleshooting/memory-troubleshooting) # crontabが動かない時のチェックリスト - PATH・環境・ログ Source: https://penguin-gym-linux.com/articles/troubleshooting/crontab-not-running ## この記事で解決できること {#intro} - 手動では動くスクリプトが **cron だと動かない** 理由が分かる - `cron` の失敗を **ログから確認** する手順が分かる - PATH・環境変数・時刻書式・パーミッションの **定番ハマりどころ** を順に潰せる ::: tip **結論(切り分けの型)** cron が動かない原因はほぼ次の 5 つに収束する。上から順に確認する。 1. **cron デーモンが動いていない** 2. **ログに実行記録がない**(=そもそも起動されていない) 3. **PATH が違う**(対話シェルと cron は別環境) 4. **環境変数が無い**(`.bashrc` は読まれない) 5. **時刻書式・パーミッション・改行コードのミス** ::: ::: warning **前提(対象環境)** - OS: Ubuntu / Debian 系(パッケージ名 `cron`、ログは `/var/log/syslog`) - RHEL / CentOS 系はサービス名 `crond`、ログは `/var/log/cron` - ユーザー crontab(`crontab -e`)を主対象とする ::: ## なぜ手動では動くのに cron だと動かないのか? {#why} > **結論**: cron は対話ログインと違い `.bashrc` / `.profile` を読まない非対話シェルで、最小 PATH のまま実行する。手動成功・cron 失敗の大半はこの環境差が原因。 手動実行(ログインシェル)と cron 実行では、シェルの起動経路が根本的に違う。 | 項目 | 対話ログインシェル | cron ジョブ | | -------------------- | --------------------------------- | ------------------------------ | | 読み込むファイル | `.bash_profile` / `.bashrc` 等 | なし | | `PATH` | フル(`/usr/local/bin` 等を含む) | 最小(多くは `/usr/bin:/bin`) | | `HOME` / `LOGNAME` | 設定済み | 一部のみ | | カレントディレクトリ | 任意 | 実行ユーザーの `$HOME` | | 標準出力 | 端末 | メール送信(または破棄) | この差を理解していれば、以降のチェックは「cron の最小環境で何が欠けているか」を 1 つずつ埋める作業になる。 ## cron デーモンは動いているか? {#daemon} > **結論**: まず `systemctl status cron` でデーモン稼働を確認する。停止していればジョブは一切実行されない。最初に潰すべき大前提。 ```bash # Ubuntu / Debian 系 $ systemctl status cron # RHEL / CentOS 系 $ systemctl status crond ``` `active (running)` でなければ起動・有効化する。 ```bash $ sudo systemctl enable --now cron ``` ::: warning サービス名はディストリで異なる。Ubuntu は `cron`、RHEL 系は `crond`。`status` で `Unit cron.service could not be found` が出たら、もう一方の名前を試す。 ::: ## 実行されたかをログで確認するには? {#log} > **結論**: cron は起動のたびに syslog へ記録を残す。`grep CRON /var/log/syslog` にエントリが無ければ、ジョブはそもそも起動されていない。 ログに行があるか無いかで、切り分けの方向が大きく変わる。 ```bash # Ubuntu / Debian 系 $ grep CRON /var/log/syslog | tail -20 # systemd journal で見る場合 $ journalctl -u cron --since "1 hour ago" # RHEL / CentOS 系 $ sudo grep CRON /var/log/cron | tail -20 ``` 実行されると次のような行が残る。 ```output Jun 5 10:00:01 host CRON[12345]: (alice) CMD (/home/alice/backup.sh) ``` 判定: - **ログにエントリがある** → cron は起動済み。原因はスクリプト側(PATH / 権限 / 環境変数)。次節以降へ。 - **ログにエントリが無い** → そもそも起動されていない。時刻書式の誤り・crontab の設置場所・デーモン停止を疑う。 ::: tip Ubuntu でログ自体が出ない場合、`rsyslog` が未導入・停止のことがある(`systemctl status rsyslog`)。その場合は `journalctl -u cron` を一次情報として使う。 ::: ## PATH が原因で動かないのはなぜか? {#path} > **結論**: cron の PATH は最小(多くは `/usr/bin:/bin`)。`/usr/local/bin` 等のコマンドは絶対パスで書くか、crontab 先頭で PATH を定義する。 「手動では動くのに cron で `command not found`」の典型はこれ。対処は 3 通り。 ```bash # 1) コマンドを絶対パスで書く(which で確認) $ which node /usr/local/bin/node # crontab では絶対パス指定 * * * * * /usr/local/bin/node /home/alice/job.js ``` ```bash # 2) crontab の先頭で PATH を定義する PATH=/usr/local/bin:/usr/bin:/bin 0 * * * * node /home/alice/job.js ``` ```bash # 3) スクリプト内で PATH を export してから処理する #!/bin/bash export PATH=/usr/local/bin:/usr/bin:/bin node /home/alice/job.js ``` ::: tip 実際の cron 環境を採取すると確実。一時的に次の行を仕込み、出力された `cronenv` を手元環境と `diff` する。 ```bash * * * * * env > /tmp/cronenv 2>&1 ``` ```bash $ diff <(env) /tmp/cronenv ``` ::: ## 環境変数が無いせいで失敗していないか? {#env} > **結論**: cron は `.bashrc` / `.profile` を読まない。`LANG` や言語ランタイムの環境変数(`NODE_ENV` / `JAVA_HOME` 等)が未設定で落ちることが多い。 対話シェルで暗黙に設定されていた変数が、cron では空になる。スクリプト側で明示的に定義するのが最も確実。 ```bash #!/bin/bash # cron 環境では未設定になりやすい変数を明示 export LANG=ja_JP.UTF-8 export HOME=/home/alice export NODE_ENV=production cd "$HOME/app" || exit 1 /usr/local/bin/node index.js ``` ::: warning `source ~/.bashrc` を cron スクリプト内で呼ぶ回避策は脆い。`.bashrc` 冒頭の「非対話シェルなら即 return」ガード(Ubuntu 既定)で何も読み込まれないことがある。必要な変数は個別に export する。 ::: ## 時刻指定・書式の間違いをどう見つけるか? {#schedule} > **結論**: フィールドは「分 時 日 月 曜日」の 5 つ。曜日と日の AND/OR、`%` のエスケープ漏れが定番のミス。ログに起動記録が無ければまず書式を疑う。 ```output ┌── 分 (0-59) │ ┌── 時 (0-23) │ │ ┌── 日 (1-31) │ │ │ ┌── 月 (1-12) │ │ │ │ ┌── 曜日 (0-7, 0と7は日曜) │ │ │ │ │ * * * * * 実行するコマンド ``` つまずきやすい点: - **`%` はリテラルにならない**: コマンド中の `%` は cron が改行に変換する。`date +%Y-%m-%d` は `date +\%Y-\%m-\%d` とバックスラッシュでエスケープする。 - **日と曜日の両指定**: `0 0 1 * 1`(毎月1日 _かつ_ 月曜ではなく)は「1日 _または_ 月曜」の OR で動く。意図とずれやすい。 - **コメントは行末に置けない**: `* * * * * cmd # メモ` は不可。コメントは独立行 `#` のみ。 ::: tip 書式の意味は [crontab.guru](https://crontab.guru/) で確認すると速い。登録後は `crontab -l` で意図どおり保存されたか必ず見返す。 ::: ## パーミッションと crontab の設置場所を確認する {#permission} > **結論**: スクリプトに実行権限が無い・shebang のパスが違うと起動直後に失敗する。`/etc/cron.d/` 配置時はファイルにユーザー欄が必要な点も忘れやすい。 ### スクリプトの実行権限と shebang ```bash $ chmod +x /home/alice/backup.sh $ head -1 /home/alice/backup.sh #!/bin/bash ``` Windows で編集したスクリプトは改行コード CRLF が混入し `bad interpreter` で失敗することがある。詳細は[「bad interpreter」エラーの直し方](/articles/troubleshooting/bad-interpreter-shebang)を参照。 ### crontab の種類で書式が違う | 設置場所 | ユーザー欄 | 用途 | | ------------------------ | ---------- | ------------------------ | | `crontab -e`(ユーザー) | 無し | 個人ジョブ | | `/etc/crontab` | **必要** | システム全体 | | `/etc/cron.d/` | **必要** | パッケージ等の追加ジョブ | ```bash # /etc/cron.d/ や /etc/crontab はユーザー欄が必須 # ┌分 ┌時 ┌日 ┌月 ┌曜 ┌ユーザー ┌コマンド 0 3 * * * alice /home/alice/backup.sh ``` ユーザー欄の有無を取り違えると、ログに `bad username` 等のエラーが出るか、無言で実行されない。 ## 出力をリダイレクトしてデバッグするには? {#debug} > **結論**: cron は標準出力・標準エラーをメール送信する。MTA が無い環境では出力が消えるため、ファイルへリダイレクトしてエラーを可視化するのが確実。 ```bash # 標準出力と標準エラーをログファイルへ * * * * * /home/alice/backup.sh >> /tmp/backup.log 2>&1 ``` `2>&1` で標準エラーも同じファイルに集約する。実行後に `/tmp/backup.log` を見れば、`command not found` や `Permission denied` の生メッセージが残る。 ::: tip **最小再現の型** 原因が絞れないときは、まず「1 分ごとに env と日時を吐くだけ」のジョブで cron 自体が動くか確認する。 ```bash * * * * * date >> /tmp/cron-test.log 2>&1 ``` このログが増えれば cron は正常。あとはスクリプト側の問題に切り分けられる。 ::: ## チェックリストまとめ {#summary} > **結論**: 「デーモン → ログ → PATH → 環境変数 → 書式 → 権限 → 出力」の順に上から潰せば、cron が動かない原因はほぼ特定できる。 上から順に確認する。 - [ ] `systemctl status cron`(RHEL は `crond`)が `active (running)` - [ ] `grep CRON /var/log/syslog` に起動エントリがある - [ ] コマンドは絶対パス、または crontab 先頭で `PATH=` を定義 - [ ] 必要な環境変数(`LANG` / ランタイム系)をスクリプトで export - [ ] 時刻フィールドは 5 つ、`%` はエスケープ、日と曜日の OR に注意 - [ ] スクリプトに実行権限、shebang 正常、改行は LF - [ ] `>> /tmp/job.log 2>&1` でエラーを可視化 次に読む記事: - [cron の基本](/articles/tutorials/cron-basics) - [systemd タイマー vs cron](/articles/tutorials/systemd-timer-vs-cron) - [journalctl の使い方](/articles/tutorials/journalctl-basics) - [「bad interpreter」エラーの直し方](/articles/troubleshooting/bad-interpreter-shebang) # 「device is busy」でアンマウントできない時の対処 Source: https://penguin-gym-linux.com/articles/troubleshooting/device-or-resource-busy ## 「device is busy」とは何が起きているのか? {#what} > **結論**: アンマウント対象のファイルシステムを、まだ誰か(プロセス・別マウント・swap 等)が使っている状態。カーネルが `EBUSY` を返して umount を拒否している。外す前に「何が掴んでいるか」を特定するのが先決。 `umount` が次のように弾かれる典型。 ```output umount: /mnt/data: target is busy. ``` 古いディストリや一部ツールでは同じ状態が `device is busy` / `device or resource busy`(`errno` の `EBUSY`)として現れる。メッセージは違っても原因は同じで、対象を参照しているものが残っている。 掴んでいる主体は大きく次の系統に分かれる。切り分けの優先順位もこの順。 - **A. プロセスがファイルを開いている**(最頻)— マウント配下のファイルを開いたまま / 実行中バイナリ / ログ書き込み中 - **B. カレントディレクトリがマウント内**— シェルや常駐プロセスの cwd が `/mnt/data` 配下にある - **C. マウントが入れ子**— その上に bind マウントや overlay が重なっている - **D. swap / loop など別用途で使用中**— 対象デバイスを swap や loop デバイスとして使っている ::: warning `device is busy` と `Read-only file system` は別物。前者は「使用中で外せない」、後者は「書き込み禁止状態」。エラー文を取り違えると対処を誤る。read-only については [「Read-only file system」の対処](/articles/troubleshooting/read-only-file-system) を参照。 ::: ## 誰が掴んでいるかを特定するには? {#find} > **結論**: まず `fuser -vm <マウントポイント>` で「そのファイルシステムを使う全プロセス」を一覧する。`ACCESS` 列で開いているファイル(f)・カレントディレクトリ(c)・実行中(e)・mmap(m) を区別できる。補完として `lsof` を使う。 ### fuser でマウント単位に洗い出す `-m` はマウントポイント(またはブロックデバイス)を指定し、そのファイルシステムを利用する全プロセスを返す。`-v` で詳細表示。 ```bash fuser -vm /mnt/data ``` ```output USER PID ACCESS COMMAND /mnt/data: root kernel mount /mnt/data alice 2314 ..c.. bash alice 2890 F.... tail ``` `ACCESS` 列の意味を読む。これが「なぜ busy か」の直接の答えになる。 - `c` … そのプロセスの **カレントディレクトリ** がマウント配下(→ B のパターン) - `e` … マウント配下のバイナリを **実行中** - `f` … ファイルを **オープン中**(大文字 `F` は書き込みオープン) - `r` … ルートディレクトリとして使用 - `m` … ファイルを **mmap**(共有ライブラリ等) 上の例なら、`bash`(cwd が中)と `tail`(ファイルを開いている)が原因。 ### lsof で個別ファイルまで追う どのファイルを掴んでいるか具体的に知りたいときは `lsof`。マウントポイント配下を再帰的に見るなら `+D`、デバイス単位なら `+f --`。 ```bash # マウントポイント配下で開かれているファイル一覧 lsof +D /mnt/data # ブロックデバイス単位で見る(マウントポイントが既に壊れている場合に有効) lsof +f -- /dev/sdb1 ``` ::: tip `lsof +D` はツリー全体を `stat` するため大きなディレクトリでは遅い。素早く原因を掴むなら `fuser -vm` を先に、詳細確認に `lsof` を使うのが実務的。 ::: ## 掴んでいるプロセスを止めて外すには? {#kill} > **結論**: まず原因プロセスを **正常終了**(cd で抜ける / サービス停止 / TERM)させてから umount。それでも残るなら `fuser -k` で強制終了するが、SIGKILL はデータ損失リスクがあるので最後の手段。 ### 1. 安全な順序:自分で抜ける・止める - 自分のシェルの cwd が中にあるだけなら、まず外に出る。 ```bash cd / ``` - サービスが掴んでいるなら、そのサービスを止める(kill より確実で安全)。 ```bash sudo systemctl stop myapp.service ``` - 個別プロセスは PID を確認して穏当なシグナルから。 ```bash kill 2890 # SIGTERM(後始末の余地を与える) ``` ### 2. 最後の手段:fuser -k で一括終了 正常終了で抜けないプロセスは `fuser -k` でまとめて殺せる。ただし `-k` は既定で **SIGKILL** を送るため、書き込み中なら破損し得る。 ```bash # 中身を確認してから(-i で対話確認) sudo fuser -kim /mnt/data ``` ```output /mnt/data: 2890c 2314c Kill process 2890 ? (y/N) y ``` - `-k` … 利用プロセスにシグナル送信(既定 SIGKILL) - `-i` … 1 件ずつ確認(事故防止に強く推奨) - `-m` … マウント単位 ::: danger `fuser -km /` のように **ルートや稼働中の重要 FS に対して打つと、システム全体のプロセスを SIGKILL** してサーバが落ちる。対象パスを必ず二度確認し、本番では `-i` を付けること。SIGKILL は書き込みバッファをフラッシュせず終了させるため、DB やログのデータ破損につながる。 ::: プロセスを片付けたら再度 umount する。 ```bash sudo umount /mnt/data ``` ## それでも外れない:lazy / force umount は使ってよいか? {#lazy} > **結論**: 即座にツリーから切り離したいなら `umount -l`(lazy)。応答しない NFS には `umount -f`(force)。どちらも「掴んでいる側を解決しない」回避策なので、原因を放置したまま常用しないこと。 ### lazy umount(`-l`) ファイルシステムを **今すぐツリーから外し**、参照が消え次第クリーンアップする。掴んでいるプロセスを落とせない/落としたくない場合の緊急避難。 ```bash sudo umount -l /mnt/data ``` 注意点: - マウントポイントは見えなくなるが、**開いていた FD は生きたまま**(プロセスは古いファイルを掴み続ける)。完全な解放はプロセス終了後。 - 同じデバイスをすぐ再マウントすると、裏で残った参照と競合することがある。 - 未書き込みデータのフラッシュ完了を保証しない。重要 FS では原因解決を優先する。 ### force umount(`-f`) 主に **応答しない NFS** 向け。サーバがダウンしてハングした NFS マウントを強制的に切り離す。 ```bash sudo umount -f /mnt/nfs ``` ローカルの ext4/xfs では `-f` はあまり効かない(busy の原因がローカルプロセスなので)。NFS のハングは [「Stale file handle」の対処](/articles/troubleshooting/stale-file-handle-nfs) も併読。 ## プロセスが見当たらないのに busy なときは? {#other} > **結論**: `fuser` / `lsof` に何も出ないのに busy なら、原因は「プロセスの開いたファイル」以外。入れ子マウント・swap・loop デバイス・NFS エクスポートの 4 つを順に疑う。 ### 1. 入れ子マウント(bind / overlay)を確認 対象の **上にさらに別マウント** が乗っていると busy になる。`findmnt -R` でツリーを見て、子マウントから順に外す。 ```bash findmnt -R /mnt/data ``` ```output TARGET SOURCE FSTYPE OPTIONS /mnt/data /dev/sdb1 ext4 rw,relatime └─/mnt/data/cache tmpfs tmpfs rw ``` この場合は子の `/mnt/data/cache` を先に umount する。Docker の overlay や `mount --bind` でよく起きる。 ### 2. swap として使われていないか 対象パーティションを swap に使っていると当然外せない。`swapon --show` で確認し、`swapoff` する。 ```bash swapon --show sudo swapoff /dev/sdb2 ``` ### 3. loop デバイスが掴んでいないか イメージファイルを loop マウントしている場合、loop デバイス経由の参照が残る。`losetup -a` で確認して解放する。 ```bash losetup -a sudo losetup -d /dev/loop0 ``` ### 4. NFS でエクスポート中でないか そのファイルシステムを NFS サーバとしてエクスポートしていると busy になる。`exportfs` で解除する。 ```bash sudo exportfs -ua # 全エクスポート一時解除 ``` ::: tip **最短フロー**: ① `fuser -vm /mnt/data` で誰が掴むか確認 → ② 正常終了(cd / systemctl stop / kill)→ ③ 再 umount → 残れば ④ `findmnt -R`・`swapon --show`・`losetup -a` で非プロセス要因を確認 → ⑤ 緊急時のみ `umount -l`。原因特定を飛ばさないのが事故防止の唯一の鍵。 ::: ## まとめ / 次に読む {#next} - [「Read-only file system」の対処 - 再マウントとfsck](/articles/troubleshooting/read-only-file-system) - [「Stale file handle」の対処 - NFSマウントの再接続](/articles/troubleshooting/stale-file-handle-nfs) - [「Too many open files」の対処 - ファイルディスクリプタ枯渇](/articles/troubleshooting/too-many-open-files) - [df は空きありなのに容量不足](/articles/troubleshooting/disk-full-but-df-normal) # dfは空きありなのに容量不足 — 削除済みファイル占有とinode枯渇の切り分け Source: https://penguin-gym-linux.com/articles/troubleshooting/disk-full-but-df-normal ## この記事で解決できること {#intro} - `df` には空きがあるのに `No space left on device` が出る理由が分かる - `df` は満杯なのに `du` の合計と合わない(削除済みファイル占有)を特定できる - `lsof` と `df -i` で **2 方向の乖離** を切り分け、安全に空きを回収できる ::: tip **結論(最短)** - `df -h` 満杯だが `du` は正常 → **削除済みファイルをプロセスが開いたまま**。`lsof +L1` で特定し、サービス再起動 or fd 切り詰めで回収 - `df -h` に空きがあるのに書けない → **inode 枯渇**(`df -i` で `IUse%` 確認)か **予約ブロック**(非 root 書き込み不可) ::: ::: warning **前提(対象環境)** - OS:Ubuntu / Debian 系(ext4 想定、`tune2fs` 等) - シェル:bash - 権限:`sudo` が使える想定 ::: ## なぜ df と実際の空き容量がズレるのか? {#why} > **結論**: df はファイルシステムのスーパーブロックが持つ集計値を読むだけで、開いたままの削除済み inode や inode テーブルの枯渇は容量(%)に現れないため乖離が起きる。 `df` はディレクトリを走査せず、ファイルシステムが保持する集計値(空きブロック数・空き inode 数)を読む。一方 `du` は実在するパスを辿ってサイズを合算する。この実装差が乖離の正体で、典型は次の 3 パターン。 | 症状 | 真因 | 確認コマンド | | ------------------------------------- | ------------------------------------ | ------------ | | `df` 満杯・`du` 少ない | 削除済みファイルをプロセスが open 中 | `lsof +L1` | | `df` に空き・書けない | inode 枯渇 | `df -i` | | `df` に約 5% 空き・非 root が書けない | 予約ブロック | `tune2fs -l` | ::: highlight キーワードは **「容量(ブロック)」と「inode」は別カウンタ** という点。どちらか一方でも尽きれば `No space left on device` になる。 ::: ## 削除済みファイルがディスクを占有しているか確認するには? {#deleted} > **結論**: lsof +L1 でリンク数 0(削除済み)かつ open 中のファイルを一覧でき、SIZE 列が大きいものが df と du の差を生んでいる正体。 ファイルは `rm` してもプロセスが開いている間は inode が解放されず、ブロックも回収されない。ログを `rm` したのにサービスが書き続けているケースが典型。 ```bash $ sudo lsof +L1 ``` `+L1` は「リンク数が 1 未満(= 0、削除済み)」のオープンファイルだけを表示する。`NLINK` 列が 0、`SIZE` が大きい行が原因。 deleted 文字列で探す方法も併用できる: ```bash $ sudo lsof -nP / 2>/dev/null | grep '(deleted)' ``` ```output COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nginx 1234 root 5w REG 259,1 8589934592 131 /var/log/nginx/access.log (deleted) ``` この例では nginx が 8 GB の削除済みログを開いたまま握っている。`df` は満杯、`du /var/log` は小さい、という状態になる。 ::: warning PID と FD(上記なら PID `1234` / FD `5`)を控えておく。次の解放手順で使う。 ::: ## 占有を解放するには? {#release} > **結論**: 原則は該当プロセスの再起動(または reload)。即時に空きが要る場合は /proc/PID/fd/N をリダイレクトで 0 バイトに切り詰めれば再起動なしで回収できる。 ### 方法 A:プロセスを再起動 / reload(推奨) 開いている fd が閉じれば inode が解放され、ブロックが即座に戻る。 ```bash $ sudo systemctl restart nginx ``` ログ系なら full restart でなく reload で開き直すサービスも多い(`systemctl reload nginx`)。 ### 方法 B:再起動できないとき fd を切り詰める サービスを落とせない場合、開いたままのファイル実体を `/proc//fd/` 経由で 0 バイトに切り詰める。 ```bash $ sudo truncate -s 0 /proc/1234/fd/5 ``` `: > /proc/1234/fd/5` でも同じ。データは失われるがプロセスは生きたまま、容量が即時回収される。 ::: danger 切り詰めるのは **削除済み(deleted)かつログ等の使い捨て** ファイルに限る。生きたデータベースファイルや未削除の実ファイルに対して行うと破損する。`lsof +L1` で deleted を確認してからにすること。 ::: ## inode 枯渇を確認するには? {#inode} > **結論**: df -i で IUse% が 100% なら容量が残っていても書けない。小さいファイルが大量にあるディレクトリを find で特定して削減する。 `df -h` に空きがあるのに書けないなら inode を疑う。 ```bash $ df -i ``` ```output Filesystem Inodes IUsed IFree IUse% Mounted on /dev/sda1 6553600 6553600 0 100% / ``` `IUse%` が 100% なら inode 枯渇。小さいファイルが大量にある場所を探す: ```bash $ sudo find / -xdev -type f 2>/dev/null | cut -d/ -f1-3 | sort | uniq -c | sort -n | tail ``` セッションファイル・メールキュー・キャッシュの断片(`/var/lib/php/sessions`、`/var/spool`、大量の小ファイル)が候補。詳しい削減手順は [inode 枯渇の対処](/articles/troubleshooting/inode-exhaustion) を参照。 ## df に約 5% の空きがあるのに書けないのはなぜ? {#reserved} > **結論**: ext4 は既定で 5% を root 専用の予約ブロックとして確保するため、非 root プロセスは df に空きが見えても書き込めない。tune2fs で予約率を確認・調整する。 ext4 は既定で全体の 5% を root 用に予約する(システム停止回避のため)。非 root のアプリは `df` に空きが見えても ENOSPC になる。 ```bash $ sudo tune2fs -l /dev/sda1 | grep -i 'reserved block' ``` ```output Reserved block count: 6553600 ``` データ専用パーティションなら予約率を下げて回収できる(root 領域では下げすぎ注意): ```bash $ sudo tune2fs -m 1 /dev/sda1 ``` `-m 1` で予約を 1% に変更。`/` では既定の 5% 維持を推奨。 ## 再発を防ぐには? {#prevent} > **結論**: ログは logrotate の copytruncate か正しい reload 連携で開き直し、inode はファイル数を監視する。df だけでなく df -i と lsof +L1 を定期確認に組み込む。 - **ログ肥大**:`logrotate` を `copytruncate`、またはローテーション後に該当サービスへ正しくシグナルを送る設定にする(rm だけは禁物) - **inode 監視**:`df -i` を監視対象に追加。容量(%)だけ見ていると inode 枯渇を見逃す - **削除前確認**:大きなファイルを消す前に `lsof ` で誰が開いているか確認 ::: tip 切り分けの定石は「`df -h` で容量、`df -i` で inode、`lsof +L1` で削除済み占有」の 3 点を必ずセットで見ること。 ::: ## 次に読む {#next} - [No space left on device の調べ方](/articles/troubleshooting/no-space-left-on-device) - [inode 枯渇の対処](/articles/troubleshooting/inode-exhaustion) - [安全にファイルを削除する方法](/articles/troubleshooting/find-safe-delete) # ディスクI/Oが遅いときの調べ方:iostat / vmstat でボトルネックを特定する Source: https://penguin-gym-linux.com/articles/troubleshooting/disk-io-troubleshooting ## この記事で解決できること {#intro} - 「サーバが重い」「レスポンスが遅い」の原因が **ディスクI/Oかどうか** を切り分けられます - `iostat` / `vmstat` を使って **CPU待ちなのか、ディスク詰まりなのか** を判断できます - 「何が起きているか分からない状態」から脱出できます ::: tip **結論(最短ルート)** ディスクI/Oを疑うときの最短手順はこれ。 1. **CPUが暇なのに遅い?** → `iostat -xz 1` 2. **I/O待ちが多い?** → `%iowait` を確認 3. **書き込み詰まり?** → `await / svctm / %util` を見る 4. **本当にディスクが原因か?** → `vmstat 1` で全体を見る ::: ::: warning **前提(対象環境)** - OS:Ubuntu - サーバ触り始めた新人〜実務初期 - root or sudo 権限あり - 物理サーバ / VM / クラウド(EC2等)すべて対象 ::: ## 1. ディスクI/Oが遅いとは何か(誤解しやすい点) {#what} > **結論**: ディスクI/O遅延はアプリの待ち・ログ書き込み詰まり・DB・バックアップなど複数原因がありCPU%だけ見ると必ず見誤る。 「ディスクが遅い」は、実際には次のどれかです。 - アプリが **ディスクの応答待ち** - ログ書き込みが詰まっている - DB / Docker / バックアップがI/Oを食っている - CPUは暇だが **I/O待ちで止まっている** ::: warning **CPU%だけ見ていると、必ず見誤ります。** ::: ## 2. iostat を使う準備(入っていない場合) {#install} > **結論**: iostatはsysstatパッケージに含まれるためsudo apt install sysstatでインストールしてから使用する。 ```bash $ sudo apt update $ sudo apt install -y sysstat ``` ## 3. まずは全体を見る(iostat -xz) {#iostat} > **結論**: iostat -xz 1で1秒ごとに詳細表示しI/O負荷があるデバイスとその規模を把握するのが最初の一手。 ```bash $ iostat -xz 1 ``` **オプションの意味:** - `-x`:詳細表示(必須) - `-z`:0行を省略(見やすく) - `1`:1秒ごとに更新 ## 4. 重要指標の読み方(ここが核心) {#metrics} > **結論**: %iowait20%超・%util80%超・await10ms超が同時に起きればディスクI/Oがボトルネックとほぼ確定できる。 ### 4-1. %iowait(CPU側) - **10%以上**:I/O待ちが目立つ - **20%以上**:ほぼ確実にディスクI/Oがボトルネック CPUは仕事をしたいが、ディスク待ちで止まっている状態。 ### 4-2. %util(ディスク側) - **70%以下**:余裕あり - **80〜90%**:詰まり始め - **100%張り付き**:完全に飽和 ::: warning 100%張り付き=これ以上処理できない ::: ### 4-3. await(待ち時間) - 数ms:正常 - **10ms超**:遅い - **50ms超**:体感で分かるレベル - **100ms超**:事故 レスポンス悪化の直接原因になりやすい。 ### 4-4. r/s w/s(読み書き量) - どちらが多いかで **読み詰まりか、書き詰まりか** を判断 - ログ肥大・DB書き込みは `w/s` が跳ねる ## 5. よくあるパターンと判断 {#patterns} > **結論**: CPU低いが遅い・CPUもI/Oも高い・%util低いのにawaitが高いの3パターンで原因の方向性が変わる。 ### パターンA:CPUは低いが遅い - `%iowait` 高い - `%util` 高い **典型的なディスクI/O詰まり** ### パターンB:CPUもI/Oも高い - `%user` / `%system` 高い - `%iowait` も高い **重い処理+ディスク書き込みの合わせ技** ### パターンC:%util低いのに遅い - `%util` 低 - `await` 高 **ストレージ自体が遅い**(ネットワークストレージ、EBS等) ## 6. vmstat で全体像を掴む(必須) {#vmstat} > **結論**: vmstat 1でbカラム(I/O待ちプロセス数)とwaが増えていればCPUではなくI/Oが原因と判断できる。 ```bash $ vmstat 1 ``` **見るべき列:** - `b`:I/O待ちプロセス数(増えるとヤバい) - `wa`:I/O wait(10%以上で注意) ::: tip `r` が少なく `b` が多い場合、**CPUではなくI/Oが原因**。 ::: ## 7. 「本当にディスクが犯人か?」を確定する {#confirm} > **結論**: %iowait高・%util高・await跳ね・vmstatのb増加が同時に揃えばディスクI/Oがボトルネックとほぼ確定する。 以下が **同時に当てはまる** と、ほぼ確定です。 - `%iowait` が高い - `%util` が高い - `await` が跳ねている - `vmstat` の `b` が増える ## 8. 原因になりやすい代表例 {#causes} > **結論**: Docker・DB・ログ肥大・バックアップ・cronが典型的なI/O原因でこの時点で「何がI/Oを食うか」に絞り込む。 - Docker(ログ・レイヤー・overlayfs) - DB(MySQL / PostgreSQL) - ログ肥大(access.log, error.log) - バックアップ(rsync, tar) - cronで回る重い処理 この時点で **「何がI/Oを食っているか」** を次の調査で潰す。 ## 9. やってはいけないこと {#mistakes} > **結論**: CPUだけ見て余裕と判断・証拠消える再起動・%utilだけ見て安心の3つがディスクI/O調査の典型的失敗。 ### NG1:CPUだけ見て「余裕」と判断 I/O待ちを見ていない典型的事故。 ### NG2:いきなり再起動 I/Oの証拠が消える。まず観測。 ### NG3:%utilだけ見て安心 `await` が死んでいることがある。 ::: tip **コピペ用:ディスクI/O調査テンプレ** ```bash # ディスク詳細(最重要) iostat -xz 1 # 全体俯瞰 vmstat 1 # ディスク一覧(どれを見るか) lsblk df -h ``` ::: ::: tip **まとめ** - 「遅い=CPU」ではない - `%iowait` と `%util` が判断軸 - `iostat` と `vmstat` はセット - 観測 → 原因特定 → 対処、の順を守る ::: ## 次に読む {#next} - [ディスク容量不足の場合](/articles/troubleshooting/no-space-left-on-device) - [DockerがI/Oを食っている場合](/articles/troubleshooting/docker-disk-usage) - [定期タスクがI/Oを発生させている可能性](/articles/tutorials/cron-basics) # DNSが引けない/遅い:dig/nslookup と resolv.conf/resolvectl の基本 Source: https://penguin-gym-linux.com/articles/troubleshooting/dns-troubleshooting ## この記事で解決できること {#intro} - 「ドメインが引けない」「急に遅い」を **DNSが原因かどうか** で切り分けできます - Ubuntuで実際に使われているDNS設定(systemd-resolved / /etc/resolv.conf)を確認できます - "DNSっぽい"症状(SSH/apt/curlが全部死ぬ)を最短で原因に寄せられます ::: tip **結論(最短ルート)** DNSが怪しいときは、次の順番で見れば迷いません。 1. **名前解決できるか**:`dig example.com +short` 2. **どのDNSを使っているか**:`resolvectl status`(なければ `/etc/resolv.conf`) 3. **DNSを指定して引いてみる**:`dig @8.8.8.8 example.com +short` 4. **遅い原因を分解**:`dig +stats` / `time dig ...` 5. **直す**(最小変更):DNSサーバ設定・systemd-resolved・ネットワーク側の問題を潰す ::: ::: warning **前提(対象環境)** - OS:Ubuntu - 対象:サーバ触り始めた新人 - 目的:トラブルシューティング(切り分けと復旧を優先) ::: ## 1. DNSが怪しい典型症状(まず疑うべきパターン) {#symptoms} > **結論**: SSH・curl・aptが全滅しIP直打ちは通るなどドメイン解決系が全滅するパターンがDNS障害の典型症状。 DNSが壊れると「全部が壊れた」ように見えます。 - `ssh user@hostname` が繋がらない(でもIP直打ちなら繋がる) - `curl https://example.com` が `Could not resolve host` で死ぬ - `apt update` が失敗する(名前解決できない) - サーバが外部APIにアクセスできない(でもpingは通ることがある) ::: tip **ポイント**:**IP直打ちで動くならDNS濃厚**。 逆にIP直打ちでもダメなら、DNS以外(経路/FW/待受)を疑います。 ::: ## 2. まずは「引けるか」を確認(dig) {#dig} > **結論**: `dig example.com +short`でIPが返れば名前解決できており、空/タイムアウトならDNSに問題がある。 ### 2-1. 最小確認(A/AAAAの結果) ```bash $ dig example.com +short ``` 出力例(IPが返る): ```output 93.184.216.34 ``` 出力が空・タイムアウトならDNS周りが怪しいです。 ### 2-2. エラーの読み方(よく見る3つ) - `NXDOMAIN`:その名前は存在しない(スペルミス/ゾーン設定ミス) - `SERVFAIL`:DNSサーバ側の障害/検証失敗(DNSSEC絡み含む) - `connection timed out; no servers could be reached`:DNSサーバに到達できない(ネットワーク・FW・設定) ## 3. どのDNSサーバを使っているか確認(Ubuntuはここが罠) {#dns-server} > **結論**: Ubuntuは`resolvectl status`を最優先で確認し、127.0.0.53はスタブなので実際のDNSは別コマンドで把握する。 Ubuntuは環境により、DNSが以下のどれかになります。 - **systemd-resolved** が管理(推奨されがち) - NetworkManager が管理 - /etc/resolv.conf を直接参照(ただし多くは "リンク") ### 3-1. resolvectl が使えるなら最優先で見る ```bash $ resolvectl status ``` 見るポイント: - `DNS Servers:`(実際に問い合わせる先) - `Current DNS Server:`(現在使っている先) ### 3-2. /etc/resolv.conf を確認(ただし"リンク"が多い) ```bash $ cat /etc/resolv.conf $ ls -la /etc/resolv.conf ``` 出力例: ```output nameserver 127.0.0.53 ``` これは **systemd-resolved のローカルスタブ** です。実際の上流DNSは `resolvectl status` に出てきます。 ## 4. DNSサーバを指定して引く(ここで原因が分解できる) {#specify-dns} > **結論**: `dig @8.8.8.8`などで引けるかを比較し、指定DNSだけOKなら自環境のDNS設定を変更することで復旧できる。 ### 4-1. Google Public DNSで引く(例) ```bash $ dig @8.8.8.8 example.com +short ``` ### 4-2. Cloudflareで引く(例) ```bash $ dig @1.1.1.1 example.com +short ``` ### 4-3. この結果の解釈が重要 - **指定DNSだと引ける** → あなたの環境が使っているDNSが壊れてる/到達できない - **指定しても引けない** → もっと手前(ネットワーク/経路/出口)に問題がある可能性 ::: tip 「自分のDNSはダメだが、8.8.8.8ならOK」→ DNSサーバ設定を変えると復旧する可能性が高いです。 ::: ## 5. "遅い"を測る(体感は嘘をつきます) {#slow} > **結論**: `dig +stats`でQuery timeを数値化し、複数回計測してキャッシュ有無や毎回遅いかを分解する。 DNSは「引けるけど遅い」が普通にあります。時間を測って分解します。 ### 5-1. digの統計を見る ```bash $ dig example.com +stats ``` 最後にこういう行が出ます: ```output Query time: 250 msec ``` ### 5-2. time で複数回測る(キャッシュの影響を見る) ```bash $ time dig example.com +short $ time dig example.com +short ``` - 1回目だけ遅い → キャッシュ/初回解決 - 毎回遅い → DNSサーバ品質/ネットワーク/MTU/出口 ## 6. よくある原因パターンと"次の一手" {#patterns} > **結論**: systemd-resolved停止・DNSサーバ不調・53番ポート封鎖・searchドメイン問題の4パターンに分けて対処する。 ### パターン1:systemd-resolved が死んでる ```bash $ systemctl status systemd-resolved $ sudo systemctl start systemd-resolved $ sudo journalctl -u systemd-resolved -n 200 ``` ### パターン2:DNSサーバが落ちている・遠い `dig @8.8.8.8 ...` が速い → DNSサーバが遅い/不調 ### パターン3:FW/出口制御で53/UDPが塞がれている `dig @8.8.8.8` がタイムアウト → そもそも外部DNSへ出られない可能性 ### パターン4:検索ドメイン(search)が悪さして遅い 短いホスト名で引こうとして、何度も検索して遅くなることがあります。 ## 7. 最小の復旧手段(安全優先) {#recovery} > **結論**: 現状を記録してから/etc/resolv.confを一時上書きする手順が即効性はあるが恒久対策ではないため注意が必要。 ### 7-1. まずは現状を記録(戻せるように) ```bash $ date $ resolvectl status || true $ cat /etc/resolv.conf ``` ### 7-2. どうしても緊急で直す:/etc/resolv.conf を直接書き換える(非推奨だが即効性) ::: warning **注意**:上書きされる可能性があります。恒久対策ではありません。 ::: ```bash $ sudo cp -a /etc/resolv.conf /etc/resolv.conf.bak $ printf "nameserver 1.1.1.1\nnameserver 8.8.8.8\n" | sudo tee /etc/resolv.conf $ dig example.com +short ``` 元に戻す: ```bash $ sudo cp -a /etc/resolv.conf.bak /etc/resolv.conf ``` ## 8. 失敗例と読み解き {#errors} > **結論**: NXDOMAINはドメイン不存在、外部DNSもタイムアウトは経路問題、引けるが遅い場合はDNS品質や検索ドメインを疑う。 ### 8-1. dig example.com が NXDOMAIN ドメイン名ミス、DNSゾーン未設定、移管ミス ### 8-2. dig @8.8.8.8 ... もタイムアウト そもそも外に出られていない/53が塞がれている ### 8-3. "引けるけど遅い" DNSサーバ品質、検索ドメイン、IPv6/AAAA待ち、経路 ## 9. やってはいけないこと {#mistakes} > **結論**: resolv.confの永続設定扱い・アプリ不具合と即断・遅さを測定せず放置の3つが典型的な失敗パターン。 ### やってはいけない1:原因不明のまま /etc/resolv.conf を永続設定扱いする 上書きされることが多く、再起動で戻って混乱します。 ### やってはいけない2:DNSの問題を "アプリの不具合" と決めつけて時間を溶かす IP直打ちで動くならDNSが濃厚です。先にDNSを潰すべきです。 ### やってはいけない3:遅いのに測らない `dig +stats` でQuery timeを見て、比較してください。体感は当てになりません。 ::: tip **コピペ用:DNS切り分けテンプレ** ```bash # 1) 引けるか dig example.com +short # 2) DNS設定(systemd-resolved想定) resolvectl status || true cat /etc/resolv.conf # 3) 指定DNSで引く(比較) dig @1.1.1.1 example.com +short dig @8.8.8.8 example.com +short # 4) 遅さを見る dig example.com +stats dig A example.com +stats dig AAAA example.com +stats ``` ::: ::: tip **まとめ** - DNSは「引けない」より「遅い」が厄介。測って分解する - `dig` → `resolvectl` → `dig @DNS` の順が最短 - まず復旧したいなら一時的にDNSを差し替える手もあるが、恒久対策は管理系で ::: ## 次に読む {#next} - [ポート疎通の確認方法](/articles/troubleshooting/port-connectivity) - [システムログの確認方法](/articles/tutorials/journalctl-basics) - [サービスの状態確認](/articles/tutorials/systemctl-basics) # ディスクが増える原因の特定(docker system df / ログ / ボリューム) Source: https://penguin-gym-linux.com/articles/troubleshooting/docker-disk-usage ## この記事で解決できること {#intro} - Docker環境でディスク使用量が増えたとき、どこが増えているか特定できます - `docker system df` で「イメージ/コンテナ/ボリューム」のどれが原因か当たりを付けられます - 削除で事故る前に、まず"安全に確認する手順"が分かります ::: tip **結論(最短)** Dockerのディスク増加は、まずこれで切り分けるのが最短です。 1. `df -h` で埋まっている領域を確認 2. `docker system df` で Docker がどれだけ使っているか確認 3. `docker system df -v` で内訳の当たりを付ける 4. ログ肥大(コンテナログ)とボリューム肥大のどちらかを疑う ※ この記事は「特定」までに寄せます。削除は事故りやすいので慎重に進めます。 ::: ::: warning **前提(対象環境)** - OS:Ubuntu - Dockerが導入済み - 権限:Docker操作のため `docker` が使えるユーザー、または `sudo` が使える想定 ::: ## 1. まず本当にディスクが埋まっているか確認(df) {#df} > **結論**: df -hでマウントポイントを特定してからDockerが原因かどうかを判断するのが最初の一手。 Dockerが原因かどうかの前に、どこが埋まっているかを確認します。 ```bash $ df -h ``` ### よくある例 - `/` が満杯(Dockerのデータがルート配下にあることが多い) - `/var` が別パーティションで満杯(`/var/lib/docker` が効いている可能性) ## 2. Dockerがどれくらい使っているかを見る {#docker-system-df} > **結論**: docker system dfでImages/Containers/Volumes/Build Cacheのどれが容量を食うかをまず把握する。 まずは全体の当たりを付けます。 ```bash $ docker system df ``` ### 注目する項目 - `Images`:イメージが溜まっていないか - `Containers`:停止コンテナが溜まっていないか - `Local Volumes`:ボリュームが肥大していないか - `Build Cache`:ビルドキャッシュが溜まっていないか ::: tip "どれが大きいか" が分かるだけでも、次の調査が一気に楽になります。 ::: ## 3. どのデータが増えているかの当たりを付ける {#location} > **結論**: /var/lib/dockerをdu -h -d 1で確認し増えているサブディレクトリを特定するのがDocker起因切り分けの定石。 Dockerのデータは典型的にはここにあります(環境により異なる場合があります)。 - Docker本体データ:`/var/lib/docker` - コンテナログ:`/var/lib/docker/containers//*.log` まずは `/var/lib/docker` のサイズを確認します。 ```bash $ sudo du -h -d 1 /var/lib/docker | sort -h ``` ::: tip ここで `/var/lib/docker` が大きいなら、Docker起因の可能性が高いです。 ::: ## 4. コンテナログ(jsonログ)が肥大していないか確認 {#container-log} > **結論**: containers配下のサイズとjsonログの巨大ファイルをfindで確認しログ肥大かどうかを判断する。 Dockerはデフォルトでコンテナの標準出力/標準エラーをログとして溜めます。ログが出続けると、ここが膨らみます。 ### 4-1. containers 配下のサイズを見る ```bash $ sudo du -h -d 2 /var/lib/docker/containers | sort -h | tail -n 20 ``` ### 4-2. 大きいログファイルを探す(上位20件) ```bash $ sudo find /var/lib/docker/containers -type f -name "*.log" -printf "%s %p\n" | sort -n | tail -n 20 ``` ::: tip ここで巨大な `*-json.log` が見つかったら、ログ肥大が原因候補です。 ::: ## 5. どのコンテナが原因か特定する {#identify} > **結論**: ログパスのコンテナIDをdocker ps -aとdocker inspectで名前に変換しコンテナを特定する。 ログパスに含まれるコンテナID(長い文字列)から、コンテナ名を引けます。 ### 5-1. コンテナ一覧を表示(IDと名前) ```bash $ docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Image}}" ``` ### 5-2. あるコンテナIDの詳細を見る ```bash $ docker inspect --format '{{.Name}}' ``` ※ `.Name` は先頭に `/` が付くことがあります。 ## 6. ボリュームが肥大しているか確認 {#volume} > **結論**: docker volume lsとdocker ps --format Mountsでどのコンテナがどのボリュームを使っているかを把握する。 DBやWordPressのアップロード、キャッシュなどはボリュームに溜まりがちです。 ### 6-1. ボリュームの一覧 ```bash $ docker volume ls ``` ### 6-2. どのボリュームを誰が使っているか ```bash $ docker ps -a --format "table {{.Names}}\t{{.Mounts}}" ``` ::: tip ここで「どのコンテナがどのボリュームを掴んでいるか」の方向性が見えます。 ::: ## 7. 事故らないための注意点(重要) {#caution} > **結論**: docker system pruneは一発事故の元になるためボリュームとログの原因を特定してから最小限の削除にとどめる。 ::: danger **いきなり docker system prune を叩かない** 想定外に消えることがあります。 ::: ::: danger **ボリュームは慎重に** ボリュームはデータ本体のことが多い(DB/WordPress等)。消すと復旧が大変です。 ::: ::: warning **ログ肥大は根本原因の調査が必要** ログを出している原因が残っていると再発します。 ::: ### 応急処置の考え方 もし原因がログ肥大だった場合の次の打ち手は、だいたいこのどれかです。 - ログローテーション設定(Dockerのlogging driver / log-opts で上限) - アプリ側のログ量の見直し(エラー連発やデバッグログ) - 監視/収集(必要なログは別経路に流す) ::: tip 削除での対処は "一時しのぎ" になりがちなので、原因を止める方が長期的に安定します。 ::: ## 確認(直ったかチェック) {#check} > **結論**: 調査や応急対応の後はdf -hとdocker system dfで空きが改善されているか確認する。 調査や応急対応の後は、まずここで改善を確認します。 ```bash $ df -h $ docker system df ``` ## 次に読む {#next} - [LinuxユーザーのためのDocker入門概論](/articles/guides/docker-for-linux-users) - [ディスク容量不足の全体調査](/articles/troubleshooting/no-space-left-on-device) - [DockerがI/O負荷を起こしている場合](/articles/troubleshooting/disk-io-troubleshooting) - [Dockerコンテナのログ確認](/articles/tutorials/journalctl-basics) # 「Could not get lock /var/lib/dpkg/lock」の解決 Source: https://penguin-gym-linux.com/articles/troubleshooting/dpkg-lock-held ## 「Could not get lock」とは何を意味するのか? {#intro} > **結論**: apt / dpkg は同時に 1 つしか動けない設計で、ロックファイルで排他制御している。このエラーは**他のプロセスが先にロックを握っている**状態を示す。原因の大半はバックグラウンドの自動更新。 `apt install` などを実行したとき、次のように出て止まることがある。 ```output $ sudo apt install nginx E: Could not get lock /var/lib/dpkg/lock-frontend. It is held by process 1234 (apt) E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), is another process using it? ``` apt と dpkg は、パッケージデータベースを壊さないために**同時に 1 つしか実行できない**。この排他制御はロックファイルで実現されており、誰かが処理中の間は他のコマンドがロックを取得できず、このエラーになる。 つまりこれは「壊れた」のではなく「**順番待ちで弾かれた**」状態だ。多くの場合、数分待てば解消する。焦ってロックファイルを削除するのが最も事故りやすい対応で、まずは「誰がロックを握っているか」を見るのが正しい。 ::: warning **前提(対象環境)** - OS: Ubuntu / Debian 系(apt / dpkg を使うディストリビューション) - `sudo` が使える前提で進める - いきなり `rm` でロックファイルを消さないこと(理由は後述) ::: ## apt が使うロックファイルはどれか? {#lock-files} > **結論**: ロックは 1 つではなく 4 つある。`lock-frontend` がフロント全体、`lock` が dpkg DB、`archives/lock` がダウンロード、`lists/lock` が `apt update` 用。エラーに出たパスでどの段階かが分かる。 apt の処理段階ごとに別々のロックファイルがある。エラーメッセージに出たパスを見れば、どの段階で詰まっているか分かる。 | ロックファイル | 役割 | 取得されるタイミング | | ------------------------------ | ----------------------------- | ------------------------ | | `/var/lib/dpkg/lock-frontend` | apt フロントエンド全体の排他 | apt 操作のほぼ全体 | | `/var/lib/dpkg/lock` | dpkg データベース本体 | パッケージの展開・設定中 | | `/var/cache/apt/archives/lock` | `.deb` ダウンロードキャッシュ | パッケージ取得中 | | `/var/lib/apt/lists/lock` | パッケージリスト | `apt update` 中 | 新しい apt(Ubuntu 18.04 以降)は `lock-frontend` を最初に取得する。エラーで `lock-frontend` が出ているなら、別の apt / dpkg がフロント全体を握っている状態だ。 ::: tip ロックファイルの**中身は空**で、ファイルの「存在」ではなく `flock`(ファイルロック)で排他している。だから「ファイルがあるから消す」という発想は誤り。ロックを握っているプロセスが終われば、ファイルが残っていても次の apt は問題なく動く。 ::: ## なぜロックが取得できないのか? {#causes} > **結論**: 9 割は「別の apt 系プロセスが動いている」。自動更新(unattended-upgrades)・GUI のソフトウェア更新・二重起動・前回の異常終了の 4 つに整理でき、自動更新が圧倒的に多い。 ロックを取得できない原因は次の 4 パターンに集約される。 | 原因 | 起きやすい状況 | 対処の方向 | | ------------------------ | ------------------------------------------- | -------------- | | 自動更新が裏で動いている | 起動直後・`apt-daily` のタイマー発火中 | 待つ | | GUI の更新ツール | 「ソフトウェアの更新」/ packagekit が起動中 | GUI を閉じる | | apt の二重起動 | 別ターミナルで apt を実行したまま | もう一方を待つ | | 前回の apt が異常終了 | install 中にターミナルを閉じた・電源断 | 復旧コマンド | 特に多いのが 1 番目だ。Ubuntu は `apt-daily.service` / `apt-daily-upgrade.service` というタイマーで、起動直後やランダムな時刻に裏で `apt update` や `unattended-upgrades` を走らせる。サーバを立てた直後に `apt install` すると、この自動更新とぶつかってロックエラーになることが非常に多い。 ## ロックを握っているプロセスを特定するには? {#identify} > **結論**: まず `lsof` か `fuser` でロックファイルを開いているプロセスを特定する。新しい apt はエラー本文に PID も出す。プロセスの正体(apt / unattended-upgrades 等)が分かれば対処方針が決まる。 最初にやるのは「誰がロックを握っているか」の確認だ。削除や kill はその後でいい。 新しい apt はエラーに `held by process 1234 (apt)` と PID を出してくれる。出ない環境では `lsof` で調べる。 ```bash $ sudo lsof /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/cache/apt/archives/lock ``` ```output COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME unattended 1234 root 5uW REG 8,1 0 ... /var/lib/dpkg/lock-frontend ``` `FD` 列の `W` は書き込みロックを意味する。上の例では `unattended-upgrades` がロックを握っている。`lsof` が無ければ `fuser` でも分かる。 ```bash $ sudo fuser -v /var/lib/dpkg/lock-frontend ``` apt 系プロセスの一覧で全体像をつかむのも有効だ。 ```bash $ ps aux | grep -iE 'apt|dpkg|unattended' | grep -v grep ``` ```output root 1234 ... /usr/bin/python3 /usr/bin/unattended-upgrade ``` ここで正体が **`unattended-upgrade` や `apt-daily`** なら、自動更新が走っているだけなので待つのが正解。**手動で起動した apt の二重実行**なら、もう一方の作業を終わらせる。 ## 自動更新が原因のときはどうするか? {#unattended} > **結論**: 自動更新(unattended-upgrades)は数分で終わるので**待つのが最善**。`kill` で中断すると更新が中途半端になる。どうしても待てない場合のみ、サービスを正しく止める手順を踏む。 `lsof` の結果が `unattended-upgrade` だった場合、それは害ではなくセキュリティ更新を適用しているプロセスだ。通常 1〜5 分で完了するので、そのまま待てば自動的にロックが解放される。 進捗を確認したいなら状態を見る。 ```bash $ systemctl status unattended-upgrades $ sudo journalctl -u unattended-upgrades -f ``` 待っても終わらない、あるいは確実に止めたい事情があるときは、`kill -9` で殺すのではなく、サービスとして正しく停止させる。 ```bash $ sudo systemctl stop unattended-upgrades ``` ::: warning 更新の途中で `kill -9` を使うと、パッケージが「展開済みだが未設定」の宙ぶらりんな状態になり、後で `dpkg --configure -a` での復旧が必要になる。止めるなら可能な限り `systemctl stop` か、プロセスへの通常の `kill`(SIGTERM)を使う。 ::: ## ロックファイルを削除してよいのはどんなときか? {#remove-lock} > **結論**: ロックファイルの削除は**最終手段**。`lsof` で「ロックを握るプロセスが 1 つも存在しない」ことを確認できたときだけ許される。動いているプロセスがある状態で消すと dpkg データベースが壊れる。 「ロックファイルを消せば直る」という情報が出回っているが、これは**動いている apt / dpkg が無いことを確認した後**にのみ有効な、最後の手段だ。プロセスが生きているのに消すと、2 つの apt が同時にデータベースを書き換えてしまい、パッケージ管理が壊れる。 まず、ロックを開いているプロセスが本当に無いことを確認する。 ```bash # 何も出力されなければ、握っているプロセスは存在しない $ sudo lsof /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/cache/apt/archives/lock /var/lib/apt/lists/lock $ ps aux | grep -iE 'apt|dpkg|unattended' | grep -v grep ``` 両方とも空(apt / dpkg が一切動いていない)と確認できて初めて、残った**ステイル(stale)なロックファイル**を削除してよい。 ```bash $ sudo rm /var/lib/dpkg/lock-frontend $ sudo rm /var/lib/dpkg/lock $ sudo rm /var/cache/apt/archives/lock $ sudo rm /var/lib/apt/lists/lock ``` ::: danger **プロセスが動いている状態でロックファイルを消すのは厳禁。** dpkg のデータベース(`/var/lib/dpkg/`)が破損し、最悪パッケージ管理が再起不能になる。`lsof` と `ps` で「無人」を確認するまでは絶対に `rm` しないこと。 ::: ## 中断した dpkg を復旧するには? {#configure} > **結論**: ロックを解放したら `sudo dpkg --configure -a` を実行する。中断で「展開済みだが未設定」になったパッケージの設定を完了させるコマンドで、その後 `apt update` / `apt -f install` で整合性を取り戻す。 ロックを解放しても、前回の apt が処理の途中で止まっていた場合は、パッケージが中途半端な状態で残っていることがある。これを正常化するのが `dpkg --configure -a` だ。 ```bash $ sudo dpkg --configure -a ``` このコマンドは「展開は済んだが設定が終わっていない」パッケージをすべて設定し直す。続けて依存関係の欠けを修復し、リストを更新する。 ```bash $ sudo apt-get install -f $ sudo apt update ``` `apt-get install -f`(`--fix-broken`)は、依存関係が壊れたパッケージを修復する。ここまで通れば、通常どおり `apt install` が使えるようになる。 ::: tip 復旧の定番セットは次の 3 つを順に実行すること。 ```bash sudo dpkg --configure -a sudo apt-get install -f sudo apt update ``` ::: ## 再発を防ぐには? {#prevent} > **結論**: スクリプトや自動化では `DPkg::Lock::Timeout` オプションでロックを「待つ」よう設定する。即エラーで落ちる代わりに指定秒数だけリトライしてくれる。新しい apt(2.x 以降)の標準機能。 手動操作なら「自動更新が終わるまで待つ」で十分だが、CI やプロビジョニングスクリプトでは、自動更新とぶつかって失敗すると面倒だ。apt 2.x には、ロックが取れないとき即エラーにせず**指定秒数だけ待つ**オプションがある。 ```bash # ロックが取れるまで最大 60 秒待つ $ sudo apt-get -o DPkg::Lock::Timeout=60 install nginx ``` ```bash # 取れるまで無制限に待つ(-1) $ sudo apt-get -o DPkg::Lock::Timeout=-1 install nginx ``` サーバ構築直後に `apt install` する自動化では、この `DPkg::Lock::Timeout` を付けておくだけで、起動直後の `apt-daily` とのロック競合による失敗をほぼ防げる。 ::: warning **やってはいけないこと** - プロセス確認せずにロックファイルを `rm` - 更新中の apt / dpkg を `kill -9` - 同じサーバで apt を 2 つ同時に走らせる ::: ## まとめ:チェックリスト {#checklist} > **結論**: 「特定 → 待つ → 復旧」が基本の順序。ロックファイル削除はプロセスが無いと確認できたときだけの最終手段。この順番を守れば dpkg を壊さずに解決できる。 - [ ] エラーに出た PID、または `lsof` でロックを握るプロセスを特定したか - [ ] それが `unattended-upgrade` / `apt-daily` なら、数分待ったか - [ ] GUI の「ソフトウェアの更新」/ packagekit が動いていないか確認したか - [ ] `kill` するなら `kill -9` を避け、`systemctl stop` か通常の `kill` を使ったか - [ ] ロックファイルを消す前に `lsof` と `ps` で「無人」を確認したか - [ ] 中断していたなら `dpkg --configure -a` → `apt-get install -f` → `apt update` で復旧したか - [ ] 自動化では `DPkg::Lock::Timeout` を設定したか ## 次に読む {#next} - [Permission denied の直し方 - Linux での原因切り分け(chmod / chown / sudo)](/articles/troubleshooting/permission-denied-fix) - [systemd サービスが起動しない - Failed to start の診断手順](/articles/troubleshooting/systemd-service-wont-start) - [ディスクがいっぱい(No space left on device)になったときの調べ方](/articles/troubleshooting/no-space-left-on-device) # ファイルシステム破損の修復 - fsck の安全な実行手順 Source: https://penguin-gym-linux.com/articles/troubleshooting/filesystem-corruption-fsck ## この記事で解決できること {#intro} - `fsck` を **データを壊さずに** 実行する手順が分かる - マウント中の `/`(root)を **どう検査・修復するか** が分かる - `-n` / `-y` / `-f` の **使い分け** と ext4 / XFS の違いが分かる ::: danger **最重要ルール**: マウント中(特に read-write)のファイルシステムに `fsck` を実行しない。稼働中のメタデータを書き換え、**正常なファイルシステムまで破壊する**。必ず先にアンマウントするか、読み取り専用で診断すること。 ::: ::: warning **前提(対象環境)** - ディストリ: Ubuntu / Debian 系を想定(RHEL 系もコマンドはほぼ共通) - ファイルシステム: ext4 を主対象(XFS / Btrfs は専用ツールを後述) - root 権限がある(`sudo` 実行) ::: ## fsck とは何か、いつ使うのか? {#what} > **結論**: `fsck` はファイルシステムの整合性を検査・修復するツール。`Input/output error`、`Read-only file system`、起動時の fsck 失敗など、メタデータ破損が疑われるときに使う。 `fsck`(file system check)は、実体としては各ファイルシステム用の検査プログラム(`fsck.<種別>`、ext 系なら `e2fsck`)を呼び分けるフロントエンド。スーパーブロック、inode テーブル、ディレクトリ構造、ブロックビットマップなどの整合性を検査し、矛盾を修復する。ただし XFS は例外で、`fsck.xfs` は何もせず終了するスタブのため、検査・修復には `xfs_repair` を直接使う。 使うべき典型的な状況: - ファイル操作で `Input/output error` が頻発する - 突然 `Read-only file system` に切り替わった(カーネルが破損を検知して保護した) - 起動時に「fsck failed」「Give root password for maintenance」で停止した - 不正なシャットダウン・電源断のあと、念のため整合性を確認したい ::: tip ジャーナリング FS(ext4 / XFS)は多くの軽微な不整合をマウント時に自動回復する。手動 `fsck` が要るのは、ジャーナル再生で直らない構造破損や、ハードウェア起因のケース。 ::: ## なぜマウント中に fsck を実行してはいけないのか? {#why-mounted} > **結論**: マウント中の FS はカーネルがメタデータをキャッシュ・更新し続けている。そこへ `fsck` が別経路で書き込むと整合性が二重に壊れ、修復どころか致命的な破損になる。 稼働中のファイルシステムは、カーネルがバッファキャッシュ上で inode やビットマップを更新している。`fsck` はブロックデバイスを直接読み書きするため、両者の認識がずれた状態で `fsck` が「修復」を書き込むと、生きていた構造まで壊れる。 このため修復モードの `fsck` は、対象がマウント中だと警告して中断するのが既定動作。安全な経路は次の 3 つ。 1. **アンマウントして実行**(データ用パーティション向け) 2. **読み取り専用(`-n`)で診断のみ**(書き込まないので比較的安全だが修復はしない) 3. **root FS は rescue mode / 起動時 fsck / live USB で実行**(後述) ```bash # まず対象がマウントされていないか確認 $ findmnt /dev/sdb1 $ lsblk -f ``` ## データ用パーティションを安全に検査・修復するには? {#unmounted} > **結論**: アンマウント → `-n` で診断 → 必要なら `-fy` で修復、の順。ハード障害が疑わしいなら修復前に `ddrescue` でイメージを退避する。 ### 1. アンマウントする ```bash $ sudo umount /dev/sdb1 ``` `target is busy` で外せない場合は、掴んでいるプロセスを特定する。 ```bash $ sudo fuser -vm /dev/sdb1 $ sudo lsof /dev/sdb1 ``` ### 2. まず読み取り専用で診断する(`-n`) `-n` はすべての質問に「no」と答え、**一切書き込まない**。被害状況の把握に使う。 ```bash $ sudo fsck -n /dev/sdb1 ``` ### 3. 障害が疑わしければイメージを退避する 物理障害(後述の `Input/output error` 多発など)が疑わしいときは、修復で状態が悪化する前に `ddrescue` でセクタを救出する。 ```bash $ sudo ddrescue /dev/sdb1 /mnt/backup/sdb1.img /mnt/backup/sdb1.log ``` ### 4. 強制チェック + 自動修復する(`-fy`) ```bash $ sudo fsck -fy /dev/sdb1 ``` - `-f`: ジャーナル的に「clean」と判定された FS でも **強制的に** 全チェック - `-y`: すべての修復質問に「yes」(対話なしで完走させる) ::: warning `-y` は対話を省く代わりに、判断を `fsck` に丸投げする。重要データでは、まず `-n` の出力を読み、被害が局所的なら個別に応答する `fsck`(オプションなし)を検討する。 ::: ## マウント中の root(/)はどう検査するのか? {#root-fsck} > **結論**: 稼働中の `/` はアンマウントできない。①次回起動時に自動 fsck を予約、②`fsck.mode=force` で起動、③live USB から検査、のいずれかを使う。 root ファイルシステムは使用中なので通常はアンマウントできない。3 つの迂回路がある。 ### 方法 A: 次回起動時に強制 fsck を予約する systemd 環境では、カーネルパラメータかフラグファイルで起動時チェックを予約できる。 ```bash # Ubuntu/Debian: 次回起動時に 1 度だけ強制チェック $ sudo touch /forcefsck $ sudo reboot ``` `/forcefsck` は systemd の `systemd-fsck` が読み取り、起動シーケンス中(root がまだ read-only の段階)に検査を実行して自動削除する。 ### 方法 B: GRUB でカーネルパラメータを渡す 起動時に GRUB メニューで `e` を押し、`linux` 行末に追記する。 ```text fsck.mode=force fsck.repair=yes ``` `fsck.repair=yes` は `-y` 相当(無人修復)、`preflight` 寄りに留めたいなら `fsck.repair=preen`(`-p` 相当、安全な自動修復のみ)を使う。 ### 方法 C: live USB / rescue メディアから検査する root が深刻に壊れて起動できない場合は、Live USB で起動し、**マウントせずに** 検査する。 ```bash # Live 環境で(対象を絶対にマウントしない) $ sudo fsck -fy /dev/sda2 ``` ::: tip 緊急モード(emergency.target)に落ちた場合、`/` が read-only でマウントされていることが多い。読み取り専用なら `fsck` の実行は比較的安全だが、確実を期すなら `mount -o remount,ro /` で read-only を明示してから実行する。 ::: ## fsck の主要オプションをどう使い分けるか? {#options} > **結論**: 診断は `-n`、無人修復は `-y` か `-p`、`clean` を無視して全検査するなら `-f`。これらを状況で組み合わせる。 | オプション | 意味 | 使いどころ | | ---------- | ---------------------------------------------- | -------------------------------- | | `-n` | 何も書かず、全質問に no | まず被害把握(読み取り専用診断) | | `-y` | 全質問に yes(無人修復) | 対話できない / 起動時 | | `-p` | preen。安全な範囲だけ自動修復 | 起動時の標準モード | | `-f` | clean でも強制的に全チェック | ジャーナルで隠れた破損を疑うとき | | `-c` | 不良ブロックを検査(badblocks 連携、ext のみ) | 物理メディア劣化の疑い | | `-A` | `/etc/fstab` の全 FS を順に検査 | 起動シーケンスが内部利用 | ```bash # ext 系は e2fsck が直接呼ばれる。明示する場合: $ sudo e2fsck -fy /dev/sdb1 # スーパーブロックが壊れたらバックアップスーパーブロックを使う $ sudo dumpe2fs /dev/sdb1 | grep -i superblock $ sudo e2fsck -b 32768 /dev/sdb1 ``` ::: warning `-p`(preen)と `-y` を **同時指定しない**。preen は「安全に直せるものだけ自動修復し、判断が要る破損が見つかったら中断してエラーコードを返す」設計。`-y` と混ぜると意図が衝突する。 ::: ## ext4 以外(XFS / Btrfs)ではどうするのか? {#other-fs} > **結論**: `fsck.xfs` は何もしない(XFS は `xfs_repair` を使う)。Btrfs は `btrfs check`。FS ごとに正しいツールを選ばないと修復にならない。 ファイルシステムの種類は `lsblk -f` か `blkid` で確認する。 ```bash $ lsblk -f /dev/sdb1 $ sudo blkid /dev/sdb1 ``` ### XFS の場合 XFS には伝統的な `fsck` は無い。`/sbin/fsck.xfs` は存在するが **何もしない**(起動を妨げないためのダミー)。実際の修復は `xfs_repair`。 ```bash # 必ずアンマウントしてから $ sudo umount /dev/sdb1 # まずドライラン(-n は変更しない) $ sudo xfs_repair -n /dev/sdb1 # 修復 $ sudo xfs_repair /dev/sdb1 ``` ログが壊れて `xfs_repair` が拒否する場合のみ、最終手段として `-L`(ログのゼロ化)を使う。データ損失リスクがあるため安易に使わない。 ### Btrfs の場合 ```bash $ sudo umount /dev/sdb1 $ sudo btrfs check /dev/sdb1 # 診断 $ sudo btrfs check --repair /dev/sdb1 # 修復(公式は最終手段と位置づけ) ``` ::: danger `xfs_repair -L` と `btrfs check --repair` はいずれもデータ損失を伴いうる最終手段。実行前にイメージ退避(`ddrescue`)を強く推奨する。 ::: ## 修復後に何を確認すればよいか? {#after} > **結論**: 終了コードを確認し、`lost+found` に救出されたファイルを点検、再マウントして読み書きを検証する。再発するなら `smartctl` でディスク健全性を疑う。 ### 1. 終了コードを読む `fsck` の終了コードはビットの組み合わせ。 ```bash $ sudo fsck -fy /dev/sdb1; echo "exit=$?" ``` | コード | 意味 | | ------ | ---------------------------- | | 0 | エラーなし | | 1 | エラーを修復した(問題なし) | | 2 | 修復したが **再起動が必要** | | 4 | 未修復のエラーが残っている | | 8 | 操作エラー | `0` か `1` なら正常完了。`4` 以上が残るなら再実行やイメージ退避を検討する。 ### 2. lost+found を点検する 親ディレクトリを失った inode は各 FS ルートの `lost+found/` に inode 番号名で回収される。 ```bash $ sudo mount /dev/sdb1 /mnt $ sudo ls -l /mnt/lost+found/ $ sudo file /mnt/lost+found/* ``` ### 3. 再マウントして読み書きを検証する ```bash $ sudo mount /dev/sdb1 /mnt $ touch /mnt/.write-test && rm /mnt/.write-test && echo "write OK" ``` ### 4. 再発するならハードを疑う ```bash $ sudo smartctl -a /dev/sdb | grep -iE 'reallocated|pending|uncorrect' $ sudo dmesg | grep -iE 'I/O error|ata|medium error' ``` `Reallocated_Sector_Ct` や `Current_Pending_Sector` が増えていれば物理障害。`fsck` での延命より、ディスク交換とリストアを優先する。 ## やってはいけないこと(チェックリスト) {#donts} > **結論**: マウント中の修復実行、FS 種別の取り違え、無検証の `-y`、退避なしの `-L` は事故の典型。 ::: warning **事故パターン** - マウント中の `/dev/...` にいきなり `fsck`(read-write)を実行する - XFS に `fsck`(実体 `fsck.xfs`)を打って「直った」と誤認する - `-n` の出力を読まずにいきなり `-y` で全修復する - 物理障害が疑わしいのにイメージ退避せず `xfs_repair -L` を実行する - スーパーブロック破損時にバックアップスーパーブロックを試さず諦める ::: ## まとめ {#next} - [「Read-only file system」の対処 - 再マウントと fsck](/articles/troubleshooting/read-only-file-system) - [「Input/output error」の対処 - ディスク・デバイス障害の兆候](/articles/troubleshooting/input-output-error) - [起動できない時の初動 - kernel panic と緊急モード](/articles/troubleshooting/kernel-panic-boot-failure) # find の安全な使い方:削除で事故らない(-print0 / xargs -0) Source: https://penguin-gym-linux.com/articles/troubleshooting/find-safe-delete ## この記事で解決できること {#intro} - `find` の基本的な探し方(名前、拡張子、更新日時、サイズ)が分かります - `find` で削除するときに事故らない手順(まず一覧→確認→削除)が分かります - `xargs` と組み合わせるときの安全策(`-print0` / `xargs -0`)が分かります ::: tip **結論(最短)** 削除系はこの順番が鉄則です。 1. **まず一覧だけ出す**(削除しない) 2. **件数を確認する**(思ったより多い時点で止まる) 3. **安全な形で削除する**(可能なら `-print0` / `xargs -0`) ::: ::: warning **前提(対象環境)** - OS:Ubuntu - シェル:bash - 権限:必要なら `sudo`(システム領域を触る場合など) ::: ## 1. find の基本(まずは探す) {#basic} > **結論**: findは-name/-type/-mtime/-sizeの条件を組み合わせてファイルを絞り込む検索コマンド。 ### 1-1. 名前で探す(完全一致) ```bash $ find . -name "error.log" ``` ### 1-2. 拡張子で探す(例:.log) ```bash $ find . -name "*.log" ``` ### 1-3. 大文字小文字を無視して探す ```bash $ find . -iname "*.log" ``` ### 1-4. ファイルだけ / ディレクトリだけ ```bash $ find . -type f -name "*.log" # ファイルだけ $ find . -type d -name "cache" # ディレクトリだけ ``` ## 2. よく使う条件:更新日時・サイズで絞る {#filter} > **結論**: -mtime +30で30日超の古いファイル、-size +100Mで大きいファイルを絞り込みディスク逼迫調査に使う。 ### 2-1. 直近7日以内に更新されたファイル ```bash $ find . -type f -mtime -7 ``` ### 2-2. 30日より古いファイル ```bash $ find . -type f -mtime +30 ``` ### 2-3. 100MBより大きいファイル ```bash $ find . -type f -size +100M ``` ::: tip ディスク逼迫調査で「重いファイル」を探すときに便利です。 ::: ## 3. 削除前の鉄則:まず「一覧」と「件数」 {#list} > **結論**: 削除前に必ずfind単体で一覧を出しwc -lで件数を確認してから削除操作に進むのが事故防止の鉄則。 ### 3-1. 一覧を出す(削除しない) 例:/var/log 配下の .log を探す(※必要なら sudo) ```bash $ sudo find /var/log -type f -name "*.log" ``` ### 3-2. 件数を確認する(思ったより多いならここで止める) ```bash $ sudo find /var/log -type f -name "*.log" | wc -l ``` ::: warning ここで件数が想定より多いなら、条件が広すぎます。`-mtime` や `-size` を足して絞ってください。 ::: ## 4. 「削除」の安全なやり方(初心者向けの型) {#delete} > **結論**: -print0とxargs -0を組み合わせることでスペース入りファイル名でも安全に削除できる定番パターン。 削除は一発事故が起きるので、手順を固定するのがおすすめです。 ### 4-1. まずは絞り込み(例:30日より古い .log) ```bash $ sudo find /var/log -type f -name "*.log" -mtime +30 ``` ### 4-2. 次に件数確認 ```bash $ sudo find /var/log -type f -name "*.log" -mtime +30 | wc -l ``` ### 4-3. それでもOKなら削除(安全策付き) **パスにスペースが混じっても壊れない**ように、`-print0` と `xargs -0` を使います。 ```bash $ sudo find /var/log -type f -name "*.log" -mtime +30 -print0 | sudo xargs -0 rm -f ``` - `-print0`:結果をヌル文字区切りで出す(スペースや改行が混じっても安全) - `xargs -0`:ヌル文字区切りを正しく受け取る ::: danger `rm -f` は強いので、最初は「削除せず表示」→「件数」→「削除」の順番を必ず守ってください。 ::: ### -delete は使っていい?(結論:慎重に) `find ... -delete` はシンプルですが、条件をミスると即事故ります。 ```bash # 理解している場合のみ使用 $ sudo find /tmp -type f -mtime +7 -delete ``` 初心者向けには、まず `-print0 | xargs -0 rm` の型で慣れるのが安全です。 ## 5. よくある落とし穴(ここで事故る) {#pitfalls} > **結論**: パスの間違い・条件の広すぎ・スペース入りファイル名の3点が削除事故の主な原因となる。 ::: danger **パスを間違える(/ と ./ の違い)** - `find / ...` はシステム全体に効くので、初心者のうちは慎重に - まずは対象ディレクトリを `pwd` と `ls` で確認してから実行 ::: ```bash $ pwd $ ls -la ``` ::: danger **条件が広すぎる** `*.log` だけだと多すぎることがあります。`-mtime` や `-size` を足して絞るのが基本です。 ::: 例:30日より古く、かつ 10MB 以上 ```bash $ sudo find /var/log -type f -name "*.log" -mtime +30 -size +10M ``` ::: danger **スペース/改行入りのファイル名で壊れる** これが `-print0` / `xargs -0` を使う最大の理由です。 ::: ## 6. 確認(削除後にチェック) {#check} > **結論**: 削除後はdf -hでディスク使用量が想定どおり改善されたかを必ず確認し、作業完了とする。 削除後はディスク使用量を確認します。 ```bash $ df -h ``` ## 次に読む {#next} - [ディスク容量不足の調査方法](/articles/troubleshooting/no-space-left-on-device) - [削除前にアーカイブする方法](/articles/tutorials/tar-basics) - [権限エラーで削除できない場合](/articles/troubleshooting/permission-denied-fix) # 「fork: Resource temporarily unavailable」の対処 - プロセス上限 Source: https://penguin-gym-linux.com/articles/troubleshooting/fork-resource-unavailable ## 「fork: Resource temporarily unavailable」とは何が起きているのか? {#intro} > **結論**: 新しいプロセス / スレッドを作る `fork(2)`(または `clone(2)`)が、上限に達して**一時的に拒否**された状態。カーネルが `EAGAIN` を返し、シェルやアプリは「Resource temporarily unavailable」と表示する。メモリ不足とは限らず、**プロセス数・スレッド数の上限**が原因のことが多い。 このエラーは、プログラムが子プロセスやスレッドを生成しようとした瞬間に出る。`fork()` / `clone()` が `EAGAIN`(errno 11)を返し、それが文字列化されて表示される。 ```output $ ./myapp fork: retry: Resource temporarily unavailable ``` ```output bash: fork: retry: Resource temporarily unavailable bash: fork: Resource temporarily unavailable ``` シェル自身が出すこともあり、その場合は新しいコマンドすら起動できない。重要なのは、これが**ディスクやネットワークの問題ではない**点だ。カーネルが管理する「同時に存在できるタスク数」の枠を使い切ったために、新規生成だけが弾かれている。既存プロセスは動き続けるため、症状は「新しいプロセスが作れない」に限定される。 ::: warning **前提(対象環境)** - OS: Ubuntu / 一般的な Linux(systemd 環境) - 症状: `fork` / `clone` が `Resource temporarily unavailable` で失敗する - `ulimit` / `/proc` / `systemctl` を参照できる前提(一部の恒久設定は `sudo` 必須) ::: ## なぜ fork が EAGAIN で失敗するのか? {#causes} > **結論**: 原因は「①ユーザー別のプロセス上限(`RLIMIT_NPROC` = `ulimit -u`)②システム全体の PID / スレッド上限(`pid_max` / `threads-max`)③cgroup のタスク上限(systemd `TasksMax`)④メモリ枯渇」の 4 つに整理できる。多くは①か③の cgroup 系。 `fork` が `EAGAIN` を返す経路を上限別に並べると次のとおり。 | 原因 | 上限の正体 | 確認の起点 | | -------------------------- | --------------------------------- | ------------------------------ | | ユーザー別プロセス上限 | `RLIMIT_NPROC`(`ulimit -u`) | `ulimit -u` / `ps -u ` | | システム全体の PID 上限 | `kernel.pid_max` | `/proc/sys/kernel/pid_max` | | システム全体のスレッド上限 | `kernel.threads-max` | `/proc/sys/kernel/threads-max` | | cgroup のタスク上限 | systemd `TasksMax`(pids cgroup) | `systemctl show -p TasksMax` | | メモリ枯渇 | プロセス構造体を割り当てられない | `free -h` / `dmesg` | 切り分けは **近い枠から外へ** 進めるのが速い。まず「そのユーザー自身に課された上限(`ulimit -u`)」を見て、次に「サービスを束ねる cgroup の `TasksMax`」、最後に「システム全体の `pid_max` / `threads-max`」の順だ。Web アプリやコンテナでスレッドを大量に作る構成では、③の cgroup 上限に最初に当たることが多い。 ::: tip `ulimit -u` は「プロセス数」と言われるが、Linux の内部ではスレッドも 1 タスクとして数える。マルチスレッドアプリでは「プロセス数は少ないのに上限に達する」ことがあるため、必ず**スレッド単位**で現在数を数える。 ::: ## 現在のプロセス / スレッド数と上限をどう確認するのか? {#count} > **結論**: まず `ulimit -u` で上限、`ps -eLf | wc -l`(または対象ユーザー分)で現在数を数え、両者を突き合わせる。現在数が上限に張り付いていれば、その枠が犯人と確定する。 最初に、いま自分のシェルに効いている上限を見る。 ```bash $ ulimit -u ``` ```output 4096 ``` 次に、現在のタスク(スレッド込み)数を数える。`ps -eLf` の `-L` がスレッドを 1 行ずつ展開するため、`fork`/`clone` が数える単位と一致する。 ```bash # システム全体のスレッド総数(概算、ヘッダ1行込み) $ ps -eLf | wc -l # 特定ユーザーのスレッド数だけを数える $ ps -L -u www-data | wc -l ``` ```output 3987 ``` 上限 `4096` に対して現在 `3987` のように張り付いていれば、その枠を使い切っている。どのプロセスがタスクを量産しているかは、スレッド数(`nlwp`)で降順に並べると一目で分かる。 ```bash $ ps -eo pid,nlwp,user,comm --sort=-nlwp | head ``` ```output PID NLWP USER COMMAND 2314 1820 www-data java 1190 214 mysql mysqld ``` `NLWP`(スレッド数)が突出したプロセスが原因の本体だ。意図せぬスレッドリーク(接続プールやワーカーの設定ミス)なら、上限を上げる前にアプリ側を疑う。 ::: warning 上限を上げる前に「正常な数なのに足りない」のか「リークで異常に増えている」のかを必ず見極める。リークを上限引き上げで覆い隠すと、いずれ同じ壁により高い位置で再発する。 ::: ## ユーザー別の上限(ulimit -u / nproc)を直すには? {#ulimit} > **結論**: 一時的には `ulimit -u <数>` で広げられるが、ログイン全体に効かせるには `/etc/security/limits.conf`(または `limits.d/`)の `nproc` を設定する。デーモンには limits.conf が効かないため systemd 側の設定が必要。 まず現在のシェルだけ一時的に広げて、症状が消えるかを確認する。 ```bash # ソフトリミットをハードリミットの範囲で引き上げる $ ulimit -u 8192 ``` 恒久設定はログイン経路で異なる。**対話ログイン(SSH / コンソール)** には `limits.conf` が効く。 ```bash $ sudo nano /etc/security/limits.conf ``` ```output # www-data soft nproc 8192 www-data hard nproc 16384 * soft nproc 4096 ``` `soft` は既定値、`hard` は引き上げの上限。これらは PAM の `pam_limits.so` 経由で適用されるため、設定後は**ログインし直す**必要がある。なお Ubuntu では `/etc/security/limits.d/*.conf` に分割して置くのが推奨で、メインファイルより後に読まれる。 ::: warning `limits.conf` が効くのは PAM を通る**ログインセッション**だけ。systemd が起動する**デーモン(Nginx・MySQL 等)には適用されない**。サービスのプロセス上限は次のセクションの cgroup / systemd 設定で行う。ここを取り違えると「設定したのに直らない」に陥る。 ::: ## cgroup / systemd の TasksMax が原因の場合は? {#cgroup} > **結論**: systemd 管理下のサービスやログインセッションには cgroup の `pids.max`(= `TasksMax`)が効く。`limits.conf` を直しても変わらないなら、ほぼこれが犯人。`systemctl show -p TasksMax` で現在値を確認し、ドロップインで引き上げる。 systemd は各サービス・各ユーザーセッションを cgroup で束ね、その中のタスク総数を `TasksMax` で制限する。まず効いている値を見る。 ```bash # 特定サービスの上限 $ systemctl show -p TasksMax nginx.service # システム既定値(UserTasksMax 等) $ systemctl show -p DefaultTasksMax ``` ```output TasksMax=4915 ``` `DefaultTasksMax` はカーネルの `pid_max` に対する割合(既定 15%)で決まることが多く、サービス個別指定が無ければこの既定値が効く。サービスの上限を上げるにはドロップインを作る。 ```bash $ sudo systemctl edit nginx.service ``` エディタに以下を書く(`[Service]` セクションに `TasksMax`)。 ```output [Service] TasksMax=infinity ``` 保存後に反映する。 ```bash $ sudo systemctl daemon-reload $ sudo systemctl restart nginx.service $ systemctl show -p TasksMax nginx.service ``` ログインユーザー側の枠は `user-.slice` に対する `UserTasksMax`(`/etc/systemd/logind.conf`)で決まる。多数のプロセスを動かす対話ユーザーで詰まる場合はこちらも確認する。 ::: tip `TasksMax=infinity` は「上限なし」を意味するが、無制限にするとリーク時にシステム全体(`pid_max`)を巻き込む。原因がリークでないと確認できた場合のみ使い、通常は必要数 + 余裕の具体値を設定するのが安全。 ::: ## システム全体の上限(pid_max / threads-max)を調整するには? {#system} > **結論**: 全ユーザー合計でも枠が足りないなら `kernel.pid_max` と `kernel.threads-max` を引き上げる。`sysctl` で一時変更し、`/etc/sysctl.d/` で恒久化する。多数のコンテナ / 大規模並行処理のホストで該当する。 システム全体の同時 PID 数とスレッド数の天井を確認する。 ```bash $ cat /proc/sys/kernel/pid_max $ cat /proc/sys/kernel/threads-max ``` ```output 4194304 65536 ``` 一時的に引き上げるには `sysctl -w`。恒久化は `/etc/sysctl.d/` にファイルを置く。 ```bash # 一時変更(再起動で消える) $ sudo sysctl -w kernel.pid_max=4194304 $ sudo sysctl -w kernel.threads-max=131072 # 恒久化 $ echo 'kernel.pid_max = 4194304' | sudo tee /etc/sysctl.d/99-pids.conf $ sudo sysctl --system ``` `pid_max` は 64bit 環境で最大 `4194304`(約 419 万)まで設定できる。ただしスレッドはそれぞれカーネルメモリを消費するため、闇雲に上げるとメモリ側が先に枯渇する。`threads-max` の既定はメモリ量から自動計算されるので、引き上げ時は `free -h` で余力を確認する。 ::: warning `pid_max` / `threads-max` を上げてもメモリが足りなければ、今度は `fork` が `ENOMEM`(Cannot allocate memory)で失敗する。EAGAIN が ENOMEM に変わったら、上限ではなくメモリが律速。[OOM killer 発動時の対処](/articles/troubleshooting/oom-killer-handling) を参照。 ::: ## 緊急復旧:シェルすら fork できないときは? {#recovery} > **結論**: 新規コマンドが起動できない状態でも、**シェルの組み込み(builtin)コマンド**は `fork` せずに動く。`kill`・`exec`・ジョブ制御を使い、外部コマンドを起動せずに暴走プロセスを止めて枠を空ける。 `fork` が枯渇していると `ps` や `kill /usr/bin/kill` のような外部コマンドすら起動できない。このときは**新しいプロセスを作らない** bash 組み込みだけで戦う。 ```bash # 組み込みの kill(外部 /bin/kill ではない)でジョブや PID にシグナル送信 $ kill %1 $ kill 2314 ``` PID の特定も、外部コマンドを呼ばずに `/proc` を組み込みの `echo`・グロブで覗ける。 ```bash # 自分のプロセスの cmdline をグロブで列挙(外部コマンド不要) $ for p in /proc/[0-9]*; do echo "$p"; done ``` それでも収拾がつかなければ、`exec` で暴走の元になっている自分のセッションを置き換えるか、別の既存ログインセッション(まだ生きている SSH 等)から対処する。最終手段はコンソール / クラウドの管理画面からの再起動だが、原因(リーク元)を特定しないと再発する。 ::: tip SSH が新規に繋がらなくなる前に、**復旧用のセッションを 1 つ繋ぎっぱなし**にしておくと安全。上限系のトラブルは「気づいたときには新規ログインも弾かれる」ため、作業用とは別の保険セッションが効く。 ::: ## それでも直らないときのチェックリスト {#checklist} > **結論**: fork の EAGAIN は「タスク数の枠の枯渇」が確定した状態。ユーザー別 `ulimit -u` → cgroup `TasksMax` → システム `pid_max`/`threads-max` の順に、近い枠から外へ確認すれば原因はこの層のどこかに収束する。リークが無いことの確認を忘れない。 - [ ] エラーは `EAGAIN`(Resource temporarily unavailable)か `ENOMEM`(Cannot allocate memory)か区別したか - [ ] `ulimit -u` の値と、`ps -L` での現在スレッド数を突き合わせたか - [ ] `ps -eo pid,nlwp,... --sort=-nlwp` でスレッドを量産している犯人プロセスを特定したか - [ ] それは正常な数か、それともスレッド / プロセスのリークか - [ ] 対象はログインセッションか、systemd デーモンか(`limits.conf` か `TasksMax` か)を切り分けたか - [ ] `systemctl show -p TasksMax ` の値は十分か - [ ] 全体合計で足りないなら `kernel.pid_max` / `kernel.threads-max` を確認したか - [ ] 上限を上げた後、メモリ(`free -h`)に余力があるか ## 次に読む {#next} - [「Too many open files」の対処 - ファイルディスクリプタ枯渇](/articles/troubleshooting/too-many-open-files) - [OOM killer 発動時の対処 - メモリ不足でプロセスが落ちた](/articles/troubleshooting/oom-killer-handling) - [ゾンビプロセス(zombie process)の正体と対処](/articles/troubleshooting/zombie-process) # リポジトリのGPG鍵エラー - NO_PUBKEYと期限切れ署名 Source: https://penguin-gym-linux.com/articles/troubleshooting/gpg-key-expired-repo ## この記事で解決できること {#intro} - `apt update` で出る **GPG error** を `NO_PUBKEY` と `EXPKEYSIG` で正しく見分けられる - 不足している公開鍵を **keyring に取得** し、`signed-by=` で安全に紐付けできる - 非推奨の `apt-key` を避け、**現行の鍵管理**へ移行できる ::: tip **結論(最短復旧)** - **`NO_PUBKEY `** → 公開鍵が手元に無い。鍵を取得して keyring に置く - **`EXPKEYSIG` / `KEYEXPIRED`** → 署名鍵が期限切れ。新しい鍵を取り直す - 配布元の **HTTPS 公開鍵 URL から取得 → `gpg --dearmor` → `/etc/apt/keyrings/` 配置 → `signed-by=`** が安全な型 ::: ::: warning **前提(対象環境)** - OS: Ubuntu 20.04 以降 / Debian 11 以降 - `sudo` 実行権限あり - 対象は **サードパーティ APT リポジトリ**(公式リポジトリの鍵は[専用セクション](#ubuntu-keyring)参照) ::: ## GPG鍵エラーとは何か?なぜ起きる? {#what} > **結論**: APT はパッケージの署名を公開鍵で検証する。鍵が手元に無い・期限切れ・別鍵に交代した場合に `apt update` が GPG error を出す。 APT は「リポジトリの `Release` ファイルが正規の鍵で署名されているか」を毎回検証する(secure apt)。検証に失敗すると更新を拒否し、警告を表示する。原因は大きく 3 系統に分かれる。 | エラー文言 | 意味 | 主因 | | ----------------------- | ---------------------------------- | ------------------------------------------- | | `NO_PUBKEY ` | 対応する公開鍵が手元に無い | リポジトリ追加時に鍵を入れ忘れ / 鍵が消えた | | `EXPKEYSIG ` | 署名は鍵由来だが、その鍵が期限切れ | 配布元の署名鍵の有効期限切れ | | `KEYEXPIRED ` | 鍵の有効期限が過ぎている | 同上(gpg 側の表現) | 典型的な出力は次のようになる。 ```output W: GPG error: https://repo.example.com stable InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 871920D1991BC93C E: The repository 'https://repo.example.com stable InRelease' is not signed. ``` ::: warning 警告を消すために署名検証を無効化する(`trusted=yes` や `--allow-unauthenticated`)のは**改ざんパッケージを掴むリスク**を負う。原則として鍵を正しく入れて直す。 ::: ## NO_PUBKEY エラーはどう直す? {#no-pubkey} > **結論**: エラー文言の `KEYID` を控え、配布元の公開鍵を取得して keyring に置く。配布元が HTTPS で鍵を公開していれば、その URL から取るのが最も安全。 ### 1. 不足している鍵 ID を特定する エラー末尾の `NO_PUBKEY` に続く 16 桁(または 8 桁)の 16 進数が鍵 ID。上の例では `871920D1991BC93C`。 ### 2. 配布元の公開鍵 URL から取得する(推奨) 多くの配布元は `.gpg` または `.asc` の公開鍵を HTTPS で配布している。これを `gpg --dearmor` でバイナリ keyring に変換して置く。 ```bash $ curl -fsSL https://repo.example.com/gpg.key \ | sudo gpg --dearmor -o /etc/apt/keyrings/example.gpg ``` ::: tip `--dearmor` は ASCII 形式(`-----BEGIN PGP PUBLIC KEY-----`)をバイナリ keyring に変換する。すでにバイナリの `.gpg` なら `curl -fsSL ... -o /etc/apt/keyrings/example.gpg` で直接保存してよい。 ::: ### 3. sources エントリに signed-by を付ける 鍵を「どのリポジトリの検証に使うか」を `signed-by=` で明示する([詳細](#signed-by))。 ```bash $ echo "deb [signed-by=/etc/apt/keyrings/example.gpg] https://repo.example.com stable main" \ | sudo tee /etc/apt/sources.list.d/example.list $ sudo apt update ``` ### 鍵 ID しか分からない場合:keyserver から取得 配布元 URL が分からず鍵 ID だけ判明しているときは、keyserver から直接 keyring に受信する。 ```bash $ sudo gpg --no-default-keyring \ --keyring /etc/apt/keyrings/example.gpg \ --keyserver keyserver.ubuntu.com \ --recv-keys 871920D1991BC93C ``` ::: warning keyserver 経由は「その鍵 ID が本当に配布元の正規鍵か」を自分で保証できない。可能な限り配布元の HTTPS URL から取得する方を選ぶ。 ::: ## 期限切れ署名(EXPKEYSIG / KEYEXPIRED)はどう直す? {#expired} > **結論**: 期限切れは「鍵が無い」のではなく「同じ鍵の有効期限が過ぎた」状態。配布元が更新した最新の公開鍵を取り直して上書きすれば直る。 出力は次のようになる。 ```output W: GPG error: https://repo.example.com stable InRelease: The following signatures were invalid: EXPKEYSIG 871920D1991BC93C Example Repo Signing Key ``` 対処は `NO_PUBKEY` とほぼ同じで、**最新鍵で keyring を上書き**する。配布元は期限を延長した同じ鍵、または後継鍵を再配布していることが多い。 ```bash # 最新の公開鍵を取り直して上書き $ curl -fsSL https://repo.example.com/gpg.key \ | sudo gpg --dearmor -o /etc/apt/keyrings/example.gpg $ sudo apt update ``` 取得した鍵の有効期限は次で確認できる。`[expired]` や `expires:` 行を見る。 ```bash $ gpg --show-keys /etc/apt/keyrings/example.gpg ``` ::: tip 配布元がまだ鍵を更新していない場合、こちら側では直せない。配布元のアナウンス・リポジトリの案内ページを確認する。 ::: ## apt-key は使ってはいけないのか? {#apt-key} > **結論**: `apt-key` は Ubuntu 20.04 / Debian 11 以降で非推奨。新しい手順では使わず、keyring ファイル + `signed-by=` に移行する。 `apt-key adv --recv-keys ...` という古い記事を見かけるが、`apt-key` は廃止予定で、新しい鍵を**全リポジトリで信頼される単一の鍵束**に入れてしまう設計上のリスクがある(ある配布元の鍵で別の配布元のパッケージも検証が通る)。 現行の正解は、鍵を**個別ファイル**に分け、`signed-by=` で対象リポジトリだけに効かせること。 ```output Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)). ``` この警告が出たら、次セクションの方式へ移行する。 ## signed-by で鍵を正しく配置するには? {#signed-by} > **結論**: 鍵は `/etc/apt/keyrings/` に置き、sources エントリの `[signed-by=...]` でフルパス指定する。ファイルは 644 以上の読み取り権限が必要。 ### 配置場所と権限 | 項目 | 推奨 | | -------------- | ---------------------------------------------------------------------------- | | 鍵の配置先 | `/etc/apt/keyrings/`(無ければ `sudo install -d -m 0755 /etc/apt/keyrings`) | | 鍵ファイル形式 | `gpg --dearmor` 済みバイナリ | | ファイル権限 | **644 以上**(root 以外も読める) | ::: warning keyring ファイルの権限が 600 などで root しか読めないと、`_apt` ユーザーが鍵を読めず `NO_PUBKEY` が**直らない**。`sudo chmod 644 /etc/apt/keyrings/example.gpg` で確認する。 ::: ### sources エントリ(1 行形式) ```bash deb [signed-by=/etc/apt/keyrings/example.gpg] https://repo.example.com stable main ``` ### sources エントリ(deb822 形式 / `.sources`) 新しい Ubuntu / Debian では deb822 形式も使える。`Signed-By:` に同じパスを書く。 ```bash $ sudo tee /etc/apt/sources.list.d/example.sources > /dev/null <<'EOF' Types: deb URIs: https://repo.example.com Suites: stable Components: main Signed-By: /etc/apt/keyrings/example.gpg EOF ``` ## Ubuntu 公式リポジトリの鍵が期限切れの場合は? {#ubuntu-keyring} > **結論**: 公式アーカイブの鍵は `ubuntu-keyring`(Debian は `debian-archive-keyring`)パッケージで管理される。更新すれば最新の鍵が入る。 サードパーティではなく Ubuntu/Debian 公式リポジトリ自体で鍵エラーが出る場合は、鍵束パッケージの更新を試す。 ```bash # Ubuntu $ sudo apt install --reinstall ubuntu-keyring # Debian $ sudo apt install --reinstall debian-archive-keyring ``` ::: warning `apt update` 自体が鍵エラーで失敗していると上記もダウンロードできないことがある。その場合は配布元の鍵更新アナウンスを確認し、信頼できる経路で新しい `*-keyring` パッケージ(`.deb`)を入手して `sudo dpkg -i` で導入する。 ::: ## トラブルシュート早見表 {#cheatsheet} > **結論**: エラー文言で原因の系統が決まる。NO_PUBKEY は取得、EXPKEYSIG は取り直し、権限・パスの取りこぼしを最後に確認する。 | 症状 | 確認 | 対処 | | -------------------------- | ---------------------------- | ------------------------------------------------------------ | | `NO_PUBKEY ` | 鍵 ID を控える | 配布元 URL or keyserver から取得して keyring へ | | `EXPKEYSIG` / `KEYEXPIRED` | `gpg --show-keys` で期限確認 | 最新鍵で keyring を上書き | | 鍵を入れたのに直らない | `ls -l /etc/apt/keyrings/` | 権限を 644 以上に(`chmod 644`) | | 鍵を入れたのに直らない | sources の `signed-by=` パス | keyring のフルパスと一致させる | | `apt-key is deprecated` | — | keyring + `signed-by=` へ移行 | | 公式リポジトリで発生 | — | `ubuntu-keyring` / `debian-archive-keyring` を再インストール | ::: danger **やってはいけないこと** - `[trusted=yes]` や `--allow-unauthenticated` で検証を無効化したまま運用する - 出所不明の鍵 ID を keyserver から取り込んで全体に信頼させる - `apt-key add` で新規に鍵を追加する(非推奨・全体信頼のリスク) ::: ## 次に読む {#next} - [壊れた依存関係の修復(held / unmet dependencies)](/articles/troubleshooting/package-held-broken-dependencies) - [「Could not get lock」の解決](/articles/troubleshooting/dpkg-lock-held) - [「certificate verify failed」の解決](/articles/troubleshooting/ssl-certificate-verify-failed) # load averageが高い時の診断 - CPU待ち・I/O待ちの見極め Source: https://penguin-gym-linux.com/articles/troubleshooting/high-load-average-diagnosis ## この記事で解決できること {#intro} - `load average` が高いとき、**CPU 待ちか I/O 待ちか** を切り分けられる - Linux 特有の load average の定義(**I/O 待ちも数える**)を理解できる - `uptime` / `top` / `vmstat` / `iostat` の **どこを見れば判定できるか** が分かる ::: tip **結論(最短の切り分け)** 1. `uptime` で load average、`nproc` で CPU コア数を確認し、**load > コア数** なら過負荷 2. `vmstat 1` の **r 列(実行待ち)が多ければ CPU 飽和**、**b 列(停止)と `wa` が多ければ I/O 待ち** 3. 方向が決まったら CPU 側は `top`/`ps`、I/O 側は `iostat -x` で犯人を特定 ::: ::: warning **前提(対象環境)** - OS:Ubuntu / 一般的な Linux - シェル:bash - ツール:`vmstat` は `procps` パッケージ(通常プリインストール)、`iostat` / `mpstat` は `sysstat` パッケージに含まれる(未導入なら `sudo apt install sysstat`) ::: ## load average とは何か?数字の意味は? {#what} > **結論**: load average は「実行中+実行待ち+I/O 待ちのプロセス数」の指数移動平均で、`uptime` が表示する 3 つの値は左から 1 分・5 分・15 分平均。 `uptime` または `cat /proc/loadavg` で確認する。 ```bash $ uptime 14:23:05 up 10 days, 3:42, 2 users, load average: 4.85, 3.12, 1.90 ``` ```bash $ cat /proc/loadavg 4.85 3.12 1.90 5/812 23145 ``` 3 つの数字は **左から 1 分・5 分・15 分** の平均。並びを比較すると傾向が読める。 - **1 分 > 15 分**(例:4.85 / 1.90)→ 負荷は **上昇中** - **1 分 < 15 分** → 負荷は **収束中** - 3 つが近い → 負荷が **定常的に高い** `/proc/loadavg` の 4 列目 `5/812` は「実行中プロセス数 / 全プロセス数」、5 列目は最後に生成された PID。 ## load average はいくつから「高い」のか? {#threshold} > **結論**: 絶対値で判断せず CPU コア数と比較する。`load average ÷ コア数` が 1.0 を超えると、処理を捌ききれずプロセスが待たされている状態。 load average は「待っているプロセスの数」なので、**CPU コア数が多いほど許容値も上がる**。 ```bash $ nproc 4 ``` - コア数 4 で load average 4.0 → ほぼ **使い切り**(待ち行列ゼロに近い) - コア数 4 で load average 8.0 → **2 倍の過負荷**(半分のプロセスが順番待ち) 目安として `load ÷ コア数` を使う。 | load ÷ コア数 | 状態 | | ------------- | ------------------------ | | ~0.7 | 余裕あり | | 0.7~1.0 | 使い切りに近い(要注意) | | 1.0 超 | 過負荷(待ちが発生) | ::: warning これは **CPU 飽和の場合の目安**。Linux では I/O 待ちも load に乗るため、コア数を超えていても「CPU はヒマ」というケースがある。次節がその切り分け。 ::: ## なぜ Linux の load average は CPU 使用率と一致しないのか? {#why-iowait} > **結論**: Linux の load average は実行可能プロセス(TASK_RUNNING)に加え、割り込み不可の I/O 待ち(TASK_UNINTERRUPTIBLE / D 状態)も数える。だから CPU 使用率が低くても load average は跳ね上がる。 多くの UNIX が「CPU の実行キュー長」だけを load にするのに対し、**Linux は割り込み不可スリープ(uninterruptible sleep、`ps` の `D` 状態)のプロセスも含める**。D 状態は主に **ディスク I/O やネットワークストレージの応答待ち** で発生する。 このため、次のような一見矛盾した状態が起こりうる。 - **load average 8.0 なのに CPU 使用率(us+sy)は 10%** → 残りは I/O 待ちで積み上がっている - ストレージが遅い・NFS がハングした、といった状況で load だけが急騰する ::: tip 「load average が高い = CPU が忙しい」とは限らない。**CPU 飽和と I/O 待ちは原因も対処も別物**なので、まず切り分ける。 ::: ## CPU 待ちか I/O 待ちかをどう見極めるのか? {#diagnose} > **結論**: `vmstat 1` の r 列(実行待ち)と b 列(I/O 等で停止)、top の `%wa`(iowait)を見る。r が大きければ CPU 飽和、b と wa が大きければ I/O 待ち。 ### 手順 1:vmstat で全体傾向を見る ```bash $ vmstat 1 5 ``` ```output procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 6 0 0 124560 20480 890120 0 0 8 12 210 430 78 12 10 0 0 5 0 0 124100 20480 890120 0 0 0 0 198 410 80 11 9 0 0 ``` 注目する列は 2 つ。 - **`r`(runnable)**:CPU を実行中/実行待ちのプロセス数。**コア数より継続的に大きい → CPU 飽和** - **`b`(blocked)**:I/O 等で割り込み不可スリープ中のプロセス数。**大きい → I/O 待ち** CPU 欄の `wa`(iowait、I/O 完了待ちの割合)も併せて見る。上の例は `r=6` でコア数 4 を超え、`wa=0` なので **CPU 飽和型**。 ### 手順 2:top で内訳を確認する ```bash $ top ``` ヘッダ行の CPU 内訳を見る。 ```output %Cpu(s): 78.0 us, 12.0 sy, 0.0 ni, 10.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st ``` - **`us`(user)+ `sy`(system)が高い** → CPU 飽和。`P` キーで CPU 使用率順に並べ替えて犯人プロセスを特定 - **`wa`(iowait)が高い** → I/O 待ち。CPU はディスク等の完了を待っている ### 手順 3:I/O 待ちの D 状態プロセスを名指しする `wa` が高いときは、どのプロセスが I/O で詰まっているかを `ps` の状態列(STAT)で探す。 ```bash $ ps -eo pid,stat,comm,wchan | awk '$2 ~ /D/' ``` ```output 1842 D mysqld wait_on_page_bits 1990 D+ dd balance_dirty_pages ``` `STAT` が `D`(uninterruptible sleep)のプロセスが I/O 待ちの当事者。`wchan`(カーネル内の待ち場所)も原因のヒントになる。 ### 手順 4:iostat でディスク側を裏取りする ```bash $ iostat -x 1 3 ``` ```output Device r/s w/s rkB/s wkB/s await aqu-sz %util sda 8.00 420.00 64.00 51200.0 85.20 9.80 99.6 ``` - **`%util` が 100% 近い** → そのデバイスが飽和(これ以上 I/O を捌けない) - **`await`(1 I/O あたりの平均応答ミリ秒)が大きい** → ディスクが遅い - **`aqu-sz`(平均キュー長)が大きい** → I/O が滞留 `%util` ほぼ 100% は I/O 待ち型の決め手。 ::: tip **判定チートシート** - `vmstat` の `r` 高・`wa` 低 → **CPU 飽和** - `vmstat` の `b` 高・top の `wa` 高・`iostat` の `%util` 高 → **I/O 待ち** - どちらも高い → CPU と I/O が連鎖(DB の全表スキャン等)。両面から見る ::: ## CPU 飽和が原因のときの対処は? {#cpu-bound} > **結論**: top/ps で CPU を食うプロセスを特定し、暴走なら nice での優先度調整や停止、慢性的ならコア増設・処理の分散・コード最適化を検討する。 ```bash $ ps -eo pid,pcpu,comm --sort=-pcpu | head ``` - 単発の暴走 → 原因プロセスを停止、または `renice` で優先度を下げる - 慢性的にコア数を超える → スケールアップ(コア増設)・処理の並列分散・アルゴリズム改善 - 特定時間帯だけ → cron / バッチの集中を疑い、実行時刻を分散 詳細な犯人特定の手順は [CPU100%の調べ方](/articles/troubleshooting/cpu-high-load) を参照。 ## I/O 待ちが原因のときの対処は? {#io-bound} > **結論**: iostat で飽和デバイスを特定し、I/O を出しているプロセスを iotop で見つける。ログ肥大・スワップ多発・遅いストレージが典型要因。 ```bash $ sudo iotop -o ``` - **特定プロセスの書き込みが多い** → ログ出力過多・全表スキャン等を疑う - **`si`/`so`(vmstat のスワップ列)が動いている** → メモリ不足でスワップ多発。[メモリ不足の調べ方](/articles/troubleshooting/memory-troubleshooting) へ - **ストレージ自体が遅い** → デバイス交換・I/O スケジューラ見直し・読み書きの削減 ディスク I/O の深掘りは [ディスクI/Oが遅いときの調べ方](/articles/troubleshooting/disk-io-troubleshooting) を参照。 ::: warning **やってはいけないこと** - load average の **絶対値だけ** で判断する(コア数を必ず併記) - `wa` を無視して CPU 増設に走る(I/O 待ちはコアを足しても解決しない) - D 状態プロセスを `kill -9` で無理に止めようとする(I/O 完了まで止まらないことが多い) ::: ## まとめ {#next} - [CPU100%の調べ方:top/ps で原因プロセスを特定する](/articles/troubleshooting/cpu-high-load) - [ディスクI/Oが遅いときの調べ方:iostat / vmstat](/articles/troubleshooting/disk-io-troubleshooting) - [メモリ不足の調べ方:free/top/ps と OOM Killer](/articles/troubleshooting/memory-troubleshooting) # 「Host key verification failed」の解決 - known_hostsの扱い Source: https://penguin-gym-linux.com/articles/troubleshooting/host-key-verification-failed ## 「Host key verification failed」とは何を意味するのか? {#intro} > **結論**: サーバの提示したホスト鍵が、クライアントが記憶している鍵と食い違ったため接続を打ち切った状態。鍵やパスワードの問題ではなく「サーバの身元確認」で止まっている。 SSH はパスワードや公開鍵で **自分を認証する** 前に、まず **接続先サーバが本物か** を確認する。この確認に使うのがサーバの「ホスト鍵」で、一度接続したサーバの鍵は `~/.ssh/known_hosts` に記録される。次回以降、提示された鍵が記録と一致しなければ SSH は接続を中断する。これが `Host key verification failed` だ。 ```output Host key verification failed. ``` 重要なのは、このエラーには **2つの異なる原因** があることだ。 - **記録済みの鍵と違う鍵が提示された**(サーバ再構築・IP 再利用、まれに中間者攻撃) - **初めて接続するサーバで、厳格チェックにより未知の鍵を拒否した**(`StrictHostKeyChecking yes` 等) この2つは対処がまったく違う。まずどちらかを見分ける。 ::: warning **前提(対象環境)** - クライアント: Ubuntu / macOS(考え方は共通) - サーバ: OpenSSH server - 確認対象は基本的に **クライアント側** の `~/.ssh/known_hosts` ::: ## まず原因を切り分けるには? {#triage} > **結論**: エラーの直前に出るメッセージを読む。`REMOTE HOST IDENTIFICATION HAS CHANGED` なら鍵の不一致、`No ... host key is known` なら初回接続の拒否。 `Host key verification failed.` の **直前の行** に原因が書いてある。スクロールして必ずそこを読む。 **パターンA: 鍵が変わった(記録と不一致)** ```output @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! ... Offending ECDSA key in /home/user/.ssh/known_hosts:12 ... Host key verification failed. ``` **パターンB: 初回接続で拒否された** ```output No ED25519 host key is known for server.example.com and you have requested strict checking. Host key verification failed. ``` パターンA は [鍵が変わった場合の直し方](#fix-changed)、パターンB は [初回接続で弾かれる場合](#first-connect)へ進む。 ## なぜ鍵が変わるのか?原因の全体像 {#causes} > **結論**: 大半はサーバ側の正当な変更(再構築・IP 再利用)。ごく一部に中間者攻撃の可能性があるため、消す前に変更が正当か必ず裏取りする。 | 原因 | 起きやすい状況 | 危険度 | | ------------------------------- | ---------------------------------------------- | ------ | | サーバ再構築・OS 再インストール | ホスト鍵が再生成された | 低 | | IP / ホスト名の再割り当て | クラウド・DHCP で別サーバが同じ宛先になった | 低 | | ホスト鍵のローテーション | 運用方針で鍵を更新した | 低 | | 接続先の取り違え | 踏み台越し・ポートフォワードで別ホストに繋いだ | 中 | | 中間者攻撃(MITM) | 経路上で第三者が偽サーバに誘導している | 高 | ほとんどは低危険度だが、「心当たりがない」のに鍵が変わったときは [削除する前の確認](#verify)を飛ばさない。 ## known_hosts はどこにあり、何を記録しているか? {#known-hosts} > **結論**: ユーザーごとの記録は `~/.ssh/known_hosts`、システム全体は `/etc/ssh/ssh_known_hosts`。各行が「ホスト名/IP + 鍵種別 + 公開鍵」で、こことの不一致が今回のエラーを生む。 ```bash $ cat ~/.ssh/known_hosts ``` 1行が1ホスト1鍵に対応する。 ```output server.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [server.example.com]:2222 ecdsa-sha2-nistp256 AAAAE2VjZHNh... ``` 標準ポート以外は `[ホスト名]:ポート` の形式で記録される点に注意する。該当エントリを探すには `ssh-keygen -F` を使う。 ```bash $ ssh-keygen -F server.example.com ``` ::: tip `HashKnownHosts yes`(Ubuntu のデフォルト)ではホスト名がハッシュ化され、`cat` してもどの行か目視できない。だが後述の `ssh-keygen -R` / `-F` はハッシュ化されたエントリも正しく扱えるので、手で編集する必要はない。 ::: ## 鍵が変わった場合の直し方(ssh-keygen -R) {#fix-changed} > **結論**: 該当エントリを `ssh-keygen -R ホスト名` で削除し、再接続して新しい鍵を登録し直す。`known_hosts` を手で開いて消す必要はない。 エラー文に `Offending ... key in /home/user/.ssh/known_hosts:12` と **ファイルと行番号** が出ている。ここを消すのが正解だが、行番号で手編集するより `ssh-keygen -R` が安全だ。 ```bash $ ssh-keygen -R server.example.com ``` ```output # Host server.example.com found: line 12 /home/user/.ssh/known_hosts updated. Original contents retained as /home/user/.ssh/known_hosts.old ``` 標準ポート以外を使っている場合は、記録形式に合わせて指定する。 ```bash $ ssh-keygen -R '[server.example.com]:2222' ``` IP でも接続している場合は、ホスト名と IP の両方のエントリを消す必要がある。 ```bash $ ssh-keygen -R 192.0.2.10 ``` 削除後に再接続すると、新しい鍵について確認を求められる。フィンガープリントを確認して `yes` で登録する。 ```bash $ ssh user@server.example.com ``` ```output The authenticity of host 'server.example.com (192.0.2.10)' can't be established. ED25519 key fingerprint is SHA256:abcd1234... Are you sure you want to continue connecting (yes/no/[fingerprint])? ``` ::: warning 古い OpenSSH では行番号で手編集する記事も多いが、`HashKnownHosts` 環境や複数エントリで事故りやすい。`ssh-keygen -R` を優先する。 ::: ## 削除する前に:本当に正規の変更か確認する {#verify} > **結論**: 鍵が変わる心当たりがないなら、削除前にサーバ側の正規フィンガープリントと突き合わせる。一致しなければ中間者攻撃を疑い、その経路では接続しない。 `Host key verification failed` の本来の役目は **なりすましの検知** だ。警告を機械的に消す癖は、その防御を自分で外すことになる。 サーバに別経路(コンソール等)で入れるなら、サーバ自身が持つホスト鍵のフィンガープリントを表示する。 ```bash $ ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub ``` ```output 256 SHA256:abcd1234... root@server (ED25519) ``` この `SHA256:...` が、再接続時に SSH が表示するフィンガープリントと **一致** すれば正規の変更だ。安心して登録してよい。一致しなければ、その接続経路を疑う。 ::: danger 心当たりのない鍵変更を「とりあえず消して再接続」するのは危険。経路上の盗聴・改ざんを見逃す。サーバ再構築・移設などの **変更イベントを自分で把握している** 場合に限り、確認を省いてよい。 ::: ## 初回接続で弾かれる場合(StrictHostKeyChecking) {#first-connect} > **結論**: `No ... host key is known ... strict checking` は、未知ホストを `StrictHostKeyChecking yes` が拒否した状態。鍵を安全に登録すれば通る。むやみに `no` へ緩めない。 初めて接続するサーバで、かつ厳格チェックが有効だと、対話確認なしに即拒否される。 ```output No ED25519 host key is known for server.example.com and you have requested strict checking. Host key verification failed. ``` 対処は「正しい鍵を `known_hosts` に登録する」こと。最も安全なのは、サーバ側の正規フィンガープリントを [事前に確認](#verify)したうえで登録する方法だ。一時的に通すだけなら `accept-new` が使える。 ```bash $ ssh -o StrictHostKeyChecking=accept-new user@server.example.com ``` `accept-new`(OpenSSH 7.6 以降)は **未知のホストは自動登録するが、既知ホストの鍵変更は従来どおり拒否する**。`no` と違い、なりすまし検知を残したまま初回登録だけ自動化できる。 ::: warning `StrictHostKeyChecking no` は鍵変更すら無視するため、本番運用では使わない。自動化で未知ホストを許容したい場合でも `accept-new` を選ぶ。 ::: ## known_hosts を事前に登録する(ssh-keyscan) {#keyscan} > **結論**: 自動化や複数台への配布では `ssh-keyscan` で鍵を取得して `known_hosts` に投入できる。ただし取得経路自体が信頼できる前提で使う。 対話確認を挟めない CI / Ansible などでは、接続前に鍵を登録しておく。 ```bash $ ssh-keyscan -t ed25519 server.example.com >> ~/.ssh/known_hosts ``` 複数ホストをまとめて取得することもできる。 ```bash $ ssh-keyscan server1 server2 server3 >> ~/.ssh/known_hosts ``` ::: warning `ssh-keyscan` は「いまネットワーク越しに見える鍵」をそのまま取り込む。取得時点で経路が汚染されていれば偽の鍵を登録してしまう。可能なら取得した鍵のフィンガープリントを正規値と突き合わせる。 ::: ## それでも直らないときのチェックリスト {#checklist} > **結論**: エラー直前の行で A/B を見分け、A は `ssh-keygen -R`、B は安全な登録で対処する。消す前の確認と、緩めすぎない設定を徹底する。 - [ ] `Host key verification failed.` の **直前の行** を読んだか(A: 変更 / B: 初回) - [ ] パターン A なら `Offending ... known_hosts:NN` のファイル・行を確認したか - [ ] 鍵変更に **心当たりがあるか**(なければフィンガープリント突き合わせ) - [ ] `ssh-keygen -R ホスト名` で削除したか(ポート付き・IP も忘れず) - [ ] 再接続時のフィンガープリントは正規値と一致したか - [ ] パターン B で安易に `StrictHostKeyChecking no` にしていないか(`accept-new` を検討) - [ ] システム全体の `/etc/ssh/ssh_known_hosts` に古いエントリが残っていないか ## 次に読む {#next} - [SSH 接続全般のトラブルシュート](/articles/troubleshooting/ssh-troubleshooting) - [「Permission denied (publickey)」の解決](/articles/troubleshooting/permission-denied-publickey) - [ufw でSSHが繋がらなくなった場合](/articles/troubleshooting/ufw-ssh-troubleshooting) # inode 枯渇の対処 - 容量はあるのに書き込めない Source: https://penguin-gym-linux.com/articles/troubleshooting/inode-exhaustion ## この記事で解決できること {#intro} - `df -h` には空きがあるのに `No space left on device` で書き込めない症状の正体が分かる - `df -i` で inode 使用率を確認し、犯人ディレクトリを特定できる - 原因別(セッション・メール・Docker・ログ)の安全な削除と再発防止策が分かる ::: tip **結論(最短 3 ステップ)** 1. `df -i` で **inode 使用率 100% のマウントポイント** を特定 2. 該当マウントポイントで `find ... -xdev | cut ... | sort | uniq -c | sort -rn` により **ファイルが集中しているディレクトリ** を絞り込む 3. 原因に応じて削除(セッション・古いログ・mail キュー・Docker 不要レイヤ)し、cron / logrotate で **再発を止める** ::: ::: warning **前提(対象環境)** - OS:Ubuntu / Debian / RHEL 系(ext4 想定) - 権限:`sudo` が使える前提 - XFS / Btrfs / ZFS は通常 inode が動的のため、本記事の症状は基本発生しない ::: ## inode 枯渇とは?なぜ容量が余っていても書けないのか {#what} inode 枯渇とは、ファイルシステムの **メタデータ管理領域(inode テーブル)が満杯になり、データブロックに空きがあっても新しいファイルが作れない** 状態。 ファイルシステムは 2 種類の上限を持つ。 - **データブロック**: ファイルの中身を格納する領域 → `df -h` で見える - **inode**: 1 ファイル = 1 inode のメタデータ(権限・所有者・サイズ・ブロック位置等) → `df -i` で見える ext4 のような **静的 inode 割り当て** のファイルシステムでは、`mkfs` 時に inode 総数が固定される(デフォルトは約 16KiB ごとに 1 inode)。**小さいファイルが大量に作られると、データ容量より先に inode が枯渇** する。 ::: highlight **典型症状** - `touch newfile` → `No space left on device` - `df -h` → 使用率 60% で「空きあり」表示 - `df -i` → 使用率 100%(IUse% = 100%) ::: ## 1. df -i で inode 使用率を確認する {#df-i} 最初にやることは `df -i`。マウントポイントごとに inode 使用率が出る。 ```bash $ df -ih ``` ```output Filesystem Inodes IUsed IFree IUse% Mounted on /dev/sda1 3.8M 3.8M 12K 100% / /dev/sdb1 6.4M 12K 6.4M 1% /data tmpfs 1.0M 5 1.0M 1% /run ``` `IUse%` が 100%(または 99%)の行が **犯人のマウントポイント**。上記例では `/` が満杯。 ::: tip `df` の `-i` オプションはほぼ全ディストリ標準(GNU coreutils)。POSIX 必須ではないが Linux では事実上常用可能。 ::: ## 2. inode を消費しているディレクトリを特定する {#find-culprit} 該当マウントポイントの直下からファイル数の多いディレクトリを掘り下げる。 ### 2-1. 配下のファイル数を素早く数える ```bash $ sudo find /var -xdev -printf . | wc -c ``` `-xdev` を必ず付ける(**他のマウントポイントを掘らない** ため)。`-printf .` は 1 ファイル 1 ドット出力で `wc -c` が高速。 ### 2-2. どのサブディレクトリにファイルが多いかを集計 ```bash $ sudo find / -xdev -type f -printf '%h\n' 2>/dev/null \ | cut -d/ -f1-4 \ | sort \ | uniq -c \ | sort -rn \ | head -20 ``` ```output 432105 /var/lib/php 187220 /var/spool/postfix 54221 /var/log/journal 8021 /var/lib/docker ... ``` `cut -d/ -f1-4` で **深さ 3 まで集約** して全体傾向を把握。怪しいパスが見えたら `-f1-6` などに変えて再度掘り下げる。 ### 2-3. 単一ディレクトリ内のファイル数(直下のみ) ```bash $ ls -1U /var/lib/php/sessions | wc -l ``` `ls -1U` は **ソートしない** ため大量ファイルでも高速。`wc -l` で行数(≒ ファイル数)。 ::: warning `ls` でソートすると数百万ファイルでメモリ不足になる場合がある。**`ls -U` か `find` を使う**。 ::: ## 3. 原因別の対処 {#fix} ### 3-1. PHP セッション(`/var/lib/php/sessions`) PHP の `session.gc_probability` が低い・cron clean が止まっている場合に堆積する。 ```bash # まず件数と古さを確認 $ sudo find /var/lib/php/sessions -xdev -type f | head $ sudo find /var/lib/php/sessions -xdev -type f -mtime +7 | wc -l # 7 日以上前のセッションを安全削除(dry-run 後に -delete) $ sudo find /var/lib/php/sessions -xdev -type f -mtime +7 -print $ sudo find /var/lib/php/sessions -xdev -type f -mtime +7 -delete ``` Debian/Ubuntu では `/etc/cron.d/php` が `sessionclean` を回しているか確認する。 ### 3-2. Postfix メールキュー(`/var/spool/postfix`) 外向きメール失敗 / バウンス停滞でキューが膨らむ。 ```bash $ sudo mailq | tail -1 # キュー件数 $ sudo postqueue -p | tail -1 # 同上 $ sudo postsuper -d ALL # 全削除(影響大、確認後に実行) $ sudo postsuper -d ALL deferred # 配送遅延分のみ削除 ``` ::: danger `postsuper -d ALL` は **未配送メールを全消去** する。業務メールが含まれていないか必ず確認する。 ::: ### 3-3. systemd journal の細切れログ ```bash $ journalctl --disk-usage $ sudo journalctl --vacuum-time=7d # 7 日より古いを削除 $ sudo journalctl --vacuum-size=200M # 200MB 以下に圧縮 ``` `/etc/systemd/journald.conf` に `SystemMaxFiles=` を設定し再発防止。 ### 3-4. Docker の overlay2 レイヤ ```bash $ docker system df $ docker system prune -a --volumes # 未使用イメージ・ボリュームを削除(影響確認後) ``` 詳細は [Docker ディスク使用量の特定](/articles/troubleshooting/docker-disk-usage) を参照。 ### 3-5. ログローテーション失敗 ```bash $ sudo ls -lh /var/log | head $ sudo logrotate -d /etc/logrotate.conf # dry-run $ sudo logrotate -f /etc/logrotate.conf # 強制実行 ``` ## 4. 削除前の安全策 {#safety} ### 4-1. 必ず dry-run ```bash # まず -print で対象を確認 $ sudo find /tmp -xdev -type f -mtime +30 -print # 内容を確認してから -delete $ sudo find /tmp -xdev -type f -mtime +30 -delete ``` ### 4-2. オープン中のファイルを掴んでいないか 削除しても `unlinked but still open` だと領域が解放されないことがある。 ```bash $ sudo lsof +L1 | head # link count 0 のオープンファイル ``` 該当プロセスを restart すると inode と容量が同時に解放される。 ### 4-3. xargs の引数オーバーフロー回避 ```bash # NG: 引数が長すぎてエラーまたは部分実行 $ rm $(find /var/lib/php/sessions -type f) # OK: -print0 / xargs -0 で安全 $ sudo find /var/lib/php/sessions -xdev -type f -mtime +7 -print0 \ | sudo xargs -0 -r rm -- ``` 詳細は [find で安全にファイル削除する方法](/articles/troubleshooting/find-safe-delete) を参照。 ## 5. 再発防止:inode を恒久的に増やしたい場合 {#prevent} ### 5-1. 現在の inode 比を確認 ```bash $ sudo tune2fs -l /dev/sda1 | grep -E 'Inode count|Block count|Inode size' ``` ### 5-2. ext4 は再フォーマットでのみ inode 数を増やせる ```bash # データ退避必須。-i は バイト/inode 比、小さくすると inode が増える $ sudo mkfs.ext4 -i 8192 /dev/sdb1 # 約 8KB ごとに 1 inode $ sudo mkfs.ext4 -N 10000000 /dev/sdb1 # 総数指定 ``` ::: warning **`mkfs` はディスク内容を全消去する**。実行前にスナップショット / バックアップ必須。本番系では事前検証環境で必ず手順確認。 ::: ### 5-3. 動的 inode の XFS / Btrfs に移行 新規ボリュームを切れるなら、小ファイルが大量発生するワークロードでは **XFS や Btrfs を選ぶ** と inode 上限の心配がほぼ消える。 ### 5-4. 監視を入れる `df -i` の `IUse%` を Prometheus node_exporter (`node_filesystem_files`, `node_filesystem_files_free`) などで取得し、80% 超過でアラートを上げる。 ## 6. やってはいけないこと {#dont} ::: danger - `rm -rf /var/spool/postfix` のようにディレクトリごと削除(Postfix が起動できなくなる) - `find / -delete` のような **無条件大量削除** - `tune2fs` で inode 数を変えようとする(**できない**) - 確認なしの `postsuper -d ALL`(業務メール消失リスク) - 大量ファイル削除中の `Ctrl+C` 連打(中途半端な状態で残る) ::: ## 次に読む {#next} - [No space left on device の対処法](/articles/troubleshooting/no-space-left-on-device) - [find で安全にファイル削除する方法](/articles/troubleshooting/find-safe-delete) - [Docker ディスク容量の調べ方](/articles/troubleshooting/docker-disk-usage) # 「Input/output error」の対処 - ディスク・デバイス障害の兆候 Source: https://penguin-gym-linux.com/articles/troubleshooting/input-output-error ## 「Input/output error」とは何のサインか? {#what} > **結論**: `Input/output error` はカーネルが返す `EIO`(errno 5)。論理的なミスではなく、ストレージやデバイスの **物理層で I/O が失敗した** サイン。ディスク不良・ケーブル/コントローラ異常・デバイス切断・ネットワークストレージ障害のいずれかを疑う。 ファイル操作中に次のように出る典型。 ```output $ cat /var/log/app.log cat: /var/log/app.log: Input/output error $ cp bigfile /mnt/data/ cp: error reading 'bigfile': Input/output error ``` `Permission denied`(権限)や `No space left`(容量)と違い、`Input/output error` は **正しく操作したのに低レイヤーが応えられなかった** ことを意味する。原因はアプリやコマンドの外、つまりデバイス側にある。 主な発生源は次の系統。切り分けの優先順位もこの順。 - **A. ディスク自体の不良**(最頻)— 不良セクタ・寿命・SMART 異常。特定ファイルだけ EIO になることが多い - **B. 接続・コントローラの問題** — SATA/USB ケーブル緩み・電源不足・HBA 異常で一時的に I/O が落ちる - **C. デバイスの切断** — USB/外付けドライブが抜けた、`/dev/sdX` が消えた - **D. ネットワークストレージの障害** — NFS/iSCSI のサーバ断・タイムアウト - **E. ファイルシステムの深刻な破損** — メタデータ損傷で読み書きが弾かれる ::: warning `Input/output error` は **症状であって原因ではない**。同じメッセージでも、退避すべき瀕死のディスクと、ケーブルを挿し直せば直る一過性障害がある。次節の dmesg を見ずに `fsck` や再フォーマットに走ると、生きているデータを失う。まず原因を読むこと。 ::: ## まず何を確認すべきか? {#first} > **結論**: 一次情報は **カーネルログ**。`dmesg -T` または `journalctl -k` で、EIO が出た瞬間のデバイス名(`sda` 等)と具体的なエラー(I/O error, sector, link reset 等)を読む。これで A〜E のどれかがほぼ決まる。 ### dmesg / journalctl でカーネルの声を聞く EIO はカーネルがデバイスドライバから受け取った失敗を上位へ返したもの。だから根拠はカーネルログに必ず残る。 ```bash # 直近のエラーを時刻付きで dmesg -T | grep -iE 'error|i/o|fail|reset' | tail -30 # 永続ログから(再起動をまたいで追える) journalctl -k -b -p err --no-pager ``` ログの読み分けの目安。 ```output # A: ディスク不良(不良セクタ) blk_update_request: I/O error, dev sda, sector 1234567 op 0x0:(READ) critical medium error, dev sda, sector 1234567 # B: 接続/リンクの問題 ata1: SATA link down (SStatus 0 SControl 300) ata1.00: failed command: READ FPDMA QUEUED # C: デバイス切断(USB 抜け等) sd 6:0:0:0: [sdb] Synchronize Cache(10) failed usb 1-1: USB disconnect, device number 5 # D: NFS の障害 nfs: server 10.0.0.5 not responding, still trying # E: ファイルシステム破損 EXT4-fs error (device sda1): ext4_find_entry: reading directory lblock ``` `sector` 付きの medium error が出ていれば A(ディスク不良)がほぼ確定。`link down` / `reset` が並ぶなら B(接続)。`disconnect` なら C。これで以降の手順が決まる。 ::: tip EIO を返したファイルやデバイスを **再度叩く前に** ログを取る。瀕死のディスクは読み直すたびに状態が悪化することがある。「とりあえずもう一度 cat」は最悪手。 ::: ## ディスク障害を疑うには?(SMART) {#smart} > **結論**: dmesg に medium error / sector が出たら `smartctl` でディスクの自己診断値を見る。`Reallocated_Sector_Ct` や `Current_Pending_Sector` が増えていれば物理劣化。バックアップと交換を急ぐ。 `smartmontools` の `smartctl` で SMART 情報を読む(未導入なら `apt install smartmontools` / `dnf install smartmontools`)。 ```bash # 健康サマリ sudo smartctl -H /dev/sda # 全属性 sudo smartctl -a /dev/sda ``` 注目する属性。 ```output ID# ATTRIBUTE_NAME RAW_VALUE 5 Reallocated_Sector_Ct 48 ← 代替処理済み不良セクタ。増加=劣化 197 Current_Pending_Sector 16 ← 代替待ちの不審セクタ。EIO の直接原因 198 Offline_Uncorrectable 16 ← 回復不能セクタ 199 UDMA_CRC_Error_Count 120 ← ケーブル/接続由来(ディスク本体は無事のことも) ``` - `Current_Pending_Sector` / `Reallocated_Sector_Ct` が **0 以外で増えている** → ディスク本体の劣化。寿命と判断しデータ退避+交換へ。 - `UDMA_CRC_Error_Count` だけ高い → **ケーブル/接続**(B)の可能性。挿し直し・交換で直ることがある。 short テストで裏取りもできる。 ```bash sudo smartctl -t short /dev/sda # 数分後に -a で結果確認 ``` ::: danger SMART が劣化を示したディスクは **いつ完全に死んでもおかしくない**。`fsck` や `badblocks -w`(書込テスト)を先にかけると、残った読めるデータごと失う。順序は必ず **①退避(ddrescue)→ ②検査・修復**。健全なディスクへコピーを取るのが最優先。 ::: ## ファイルシステム破損か切り分けるには? {#fsck} > **結論**: dmesg が `EXT4-fs error` 等の FS 破損を示し、SMART は健全なら、`fsck` でメタデータを修復する。ただし `fsck` は **必ずアンマウント状態** で。マウント中の実行は破損を悪化させる。 まず読取専用で問題を確認(`-n` は一切書き込まない)。 ```bash # 対象を特定(マウント状態の確認) lsblk -f findmnt /mnt/data # アンマウントしてから検査(-n = 読取専用ドライラン) sudo umount /dev/sda1 sudo fsck -n /dev/sda1 ``` ルートファイルシステム(`/`)が対象でアンマウントできない場合は、`fsck` をブート時に実行させるか、ライブ USB / レスキューモードから行う。 ```bash # 次回ブート時に強制 fsck(root FS 向け) sudo touch /forcefsck # systemd 環境では fsck.mode=force カーネル引数が確実 ``` 問題が確認できたら実際に修復する。重要データは退避済みであることが前提。 ```bash sudo fsck -y /dev/sda1 # -y = 修復を自動承認 ``` ::: warning `fsck` をマウント中の FS に実行してはいけない。カーネルとツールが同じメタデータを別々に書き換え、軽微な破損が致命傷になる。`umount` できない(`device is busy`)場合は [「device is busy」でアンマウントできない時の対処](/articles/troubleshooting/device-or-resource-busy) を参照。 ::: ## ディスク以外の原因のときは? {#other} > **結論**: dmesg に `link down` / `disconnect` / `nfs ... not responding` が出ているなら、ディスク本体ではなく **接続・切断・ネットワーク** が原因。物理確認や再マウントで直ることが多く、`fsck` は不要。 ### 接続・ケーブル(B) `UDMA_CRC_Error` 高、`SATA link down` / `ata reset` が並ぶケース。 - SATA/USB ケーブルを挿し直す・別ポート/別ケーブルに替える - 外付けは **電源不足** が定番。セルフパワーの USB ハブや AC アダプタを使う - 改善するか dmesg を監視(`dmesg -w` でリアルタイム) ### デバイス切断(C) `USB disconnect` の場合、`/dev/sdX` が消えて以降の全操作が EIO になる。 ```bash lsblk # デバイスが見えるか sudo dmesg -w # 挿し直した瞬間を観測 ``` 抜けたデバイス上のマウントは無効。アンマウントして挿し直し、再マウントする。 ### ネットワークストレージ(D) NFS サーバ断やネットワーク障害でも EIO になる。サーバ側・経路を確認する。 ```bash mount | grep nfs ping showmount -e # エクスポートが見えるか ``` ハングした NFS は [「Stale file handle」の対処](/articles/troubleshooting/stale-file-handle-nfs) も併読。`Stale file handle (ESTALE)` は EIO と紛らわしいが対処が異なる。 ## 緊急時のデータ退避はどうする? {#rescue} > **結論**: 死にかけのディスクからは、通常の `cp` ではなく `ddrescue` で退避する。読めるブロックを先に確保し、不良ブロックは後回し・スキップするので、最大限のデータを救える。 `cp` は EIO に当たると止まり、再試行でディスクをさらに痛める。`gddrescue`(`apt install gddrescue` / コマンド名は `ddrescue`)は不良を飛ばしながら救出し、ログで再開もできる。 ```bash # /dev/sdb(障害ディスク)→ /dev/sdc(健全な退避先) # 第3引数のマップファイルで中断・再開が可能 sudo ddrescue -d -r3 /dev/sdb /dev/sdc rescue.map ``` - `-d` … ダイレクト I/O(OS キャッシュを介さず実セクタを読む) - `-r3` … 不良ブロックを最大 3 回まで再試行 - `rescue.map` … 進捗マップ。中断後 **同じコマンドで続きから** 再開 ファイル単位ではなくデバイス/パーティション丸ごとを退避先に取り、退避先に対して `fsck` やファイル復旧をかけるのが定石。元の瀕死ディスクへの操作回数を最小化するのが目的。 ::: tip **最短フロー**: ① `dmesg -T \| grep -i error` で原因系統(A〜E)を判定 → ② medium error なら `smartctl -a` でディスク劣化を確認 → ③ 劣化なら即 `ddrescue` で退避 → ④ FS 破損は退避後に `fsck`(要 umount)→ ⑤ link/disconnect/NFS は物理・経路を確認。dmesg を飛ばさないのが全ての起点。 ::: ## まとめ / 次に読む {#next} - [「Read-only file system」の対処 - 再マウントとfsck](/articles/troubleshooting/read-only-file-system) - [「Stale file handle」の対処 - NFSマウントの再接続](/articles/troubleshooting/stale-file-handle-nfs) - [「device is busy」でアンマウントできない時の対処](/articles/troubleshooting/device-or-resource-busy) - [ディスク I/O 高負荷の調査](/articles/troubleshooting/disk-io-troubleshooting) # 起動できない時の初動 - kernel panic と緊急モード Source: https://penguin-gym-linux.com/articles/troubleshooting/kernel-panic-boot-failure ## この記事で解決できること {#intro} - 画面が `kernel panic` で固まる・`(initramfs)` で止まる・緊急モードに落ちる、それぞれの **意味と違い** が分かる - 起動失敗が **どの段階で止まっているか** を切り分けられる - GRUB からの旧カーネル起動・`fstab` 修復・`fsck` による **最短の復旧手順** が身につく ::: tip **結論(切り分けの型)** 起動失敗は「**どの段階で止まったか**」で対処が全く変わる。上から順に到達点を確認する。 1. **`grub rescue>` / `grub>`** → ブートローダ段階で停止(GRUB 設定・モジュール喪失) 2. **`(initramfs)` プロンプト** → root ファイルシステムをマウントできない(初期ユーザ空間) 3. **`Kernel panic - not syncing: ...`** → カーネルが回復不能で停止 4. **緊急モード(emergency / rescue)のシェル** → ユーザ空間には到達、起動が途中で失敗(多くは `fstab`) ::: ::: warning **前提(対象環境)** - ディストリ:Ubuntu / Debian 系(systemd・GRUB 2 を想定) - 物理コンソール または クラウドのシリアルコンソール / VNC で画面が見える状態 - 復旧操作には root 権限が必要(緊急モードのシェルは root) ::: ## 起動失敗はどの段階で止まっているか? {#stages} > **結論**: 画面に出る文字列で段階を判定する。`grub` ならブートローダ、`(initramfs)` なら root マウント失敗、`Kernel panic` ならカーネル停止、`emergency mode` ならユーザ空間到達後の失敗。 Linux の起動は大きく次の順で進む。**止まった場所**を特定することが復旧の第一歩。 | 段階 | 画面に出るサイン | 主な原因 | | ----------------- | ------------------------------------------- | --------------------------------------------------- | | 1. ブートローダ | `grub rescue>` / `grub>` | GRUB 設定・`/boot` の喪失、パーティション変更 | | 2. 初期ユーザ空間 | `(initramfs)` プロンプト | root デバイスが見つからない、FS 破損 | | 3. カーネル | `Kernel panic - not syncing:` | root マウント不能、必須ドライバ欠如、initramfs 破損 | | 4. systemd | `You are in emergency mode` / `rescue mode` | `fstab` のミス、必須マウント失敗、サービス起動失敗 | ::: tip **まず試す価値があるのは旧カーネル起動** 直前のカーネル更新後に起動しなくなった場合、GRUB の「Advanced options」から **1 つ前のカーネル**で起動するだけで復旧することが多い(後述)。 ::: ## kernel panic とは何か? {#panic} > **結論**: kernel panic は、カーネルが回復不能なエラーを検知して安全のため停止した状態。多くは root ファイルシステムをマウントできない、または必須ドライバ・initramfs の欠落が原因。 カーネルが内部で致命的な矛盾を検知すると、これ以上続行するとデータ破壊につながると判断して**意図的に停止**する。これが kernel panic。アプリのクラッシュとは違い、システム全体が止まる。 典型的なメッセージ: ```output Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ``` これは「**root ファイルシステムをマウントできなかった**」サイン。よくある原因: - カーネル更新後に **initramfs が壊れている / 古い**(必要なストレージドライバが含まれていない) - GRUB の `root=` パラメータが指す **UUID / デバイスが変わった** - ストレージ構成変更(LVM / RAID / ディスク交換)で root が見つからない 別パターンの `Kernel panic - not syncing: Attempted to kill init!` は、`init`(systemd)が起動直後に死んだことを示す。initramfs や root FS の破損を疑う。 ::: warning panic 画面はスクロールして消えることがある。**最初の数行(最上部)**に本当の原因が出る。クラウドならシリアルコンソールのログ、物理機なら写真を撮って最上部を確認する。 ::: ## GRUB メニューから旧カーネルで起動するには? {#grub-older} > **結論**: 起動時に GRUB メニューを出し、「Advanced options」から 1 つ前のカーネルを選ぶ。直近のカーネル更新が原因なら、これだけで起動できる。 カーネル更新直後の起動不能は、旧カーネルで起動して切り分けるのが最短。 1. 電源投入後、GRUB メニューを表示する(出ない場合は起動中に `Shift` 長押し、UEFI なら `Esc` 連打) 2. **Advanced options for Ubuntu** を選ぶ 3. 一覧から **1 つ前のバージョン**のカーネルを選んで起動 旧カーネルで起動できたら、現行カーネルの initramfs を作り直す: ```bash $ sudo update-initramfs -u -k all $ sudo update-grub ``` ### GRUB の起動エントリをその場で編集する {#grub-edit} メニューでカーネルを選んだ状態で `e` を押すと、その場で起動パラメータを編集できる。 1. 対象エントリで `e` を押す 2. `linux` で始まる行の末尾(`ro quiet splash` など)にカーソルを移動 3. 必要なパラメータを追記する(例: `systemd.unit=rescue.target`) 4. `Ctrl + X` または `F10` で起動 この編集は**一時的**で、再起動すると元に戻る。恒久化は `/etc/default/grub` 編集 + `update-grub`。 ## initramfs プロンプトで止まったら? {#initramfs} > **結論**: `(initramfs)` プロンプトは root ファイルシステムをマウントできなかった状態。多くは FS 破損で、`fsck` をかけてから `exit` で起動を続行できる。 `(initramfs)` という BusyBox のプロンプトが出るのは、カーネルは起動したが **root をマウントできなかった**ため。直前に次のような行が出ることが多い。 ```output ALERT! /dev/sda1 does not exist. Dropping to a shell! ``` または FS の不整合検知。対処: ```bash # 認識されているブロックデバイスを確認 (initramfs) cat /proc/partitions (initramfs) ls /dev/sd* /dev/nvme* # 対象パーティションを検査・修復(必ずアンマウント状態で) (initramfs) fsck /dev/sda1 # 修復後、起動を続行 (initramfs) exit ``` `fsck` が「修復するか?」を聞いてきたら、原則 `y`。大量に出る場合は `fsck -y /dev/sda1` で一括。終了後 `exit` で通常の起動シーケンスに戻る。 ::: danger `fsck` は**マウント中のファイルシステムにかけない**こと。データ破損の危険がある。`(initramfs)` 段階や緊急モードの read-only マウント状態、あるいはライブ USB から実行する。 ::: ## 緊急モード(emergency / rescue)の入り方と違いは? {#emergency} > **結論**: `rescue.target` は基本サービスとローカル FS マウントありの単一ユーザ相当、`emergency.target` は root を read-only マウントするだけの最小環境。fstab 破損などでは emergency に自動的に落ちる。 systemd には復旧用の 2 つのターゲットがある。**できることの範囲**が違う。 | ターゲット | マウント | サービス | 用途 | | ------------------ | ---------------------- | -------------- | ---------------------------------- | | `rescue.target` | ローカル FS をマウント | 最小限を起動 | 単一ユーザ相当。通常の復旧作業向け | | `emergency.target` | root のみ read-only | ほぼ起動しない | 最も最小。`fstab` 破損時の最後の砦 | ### 意図的に rescue / emergency で起動する {#enter} GRUB の編集(`e`)で `linux` 行末尾に追記する。 ```bash # rescue モード(推奨。多くの復旧はこれで足りる) systemd.unit=rescue.target # emergency モード(最小構成) systemd.unit=emergency.target ``` 短縮形(`rescue` / `emergency` / 古い `single` `1`)も使えるが、`systemd.unit=` 形式が確実。`Ctrl + X` で起動するとパスワード入力後に root シェルに入る。 ### 緊急モードで root を書き込み可にする {#remount} emergency に落ちると root が **read-only** のことが多く、ファイルを編集できない。次で書き込み可に再マウントする。 ```bash $ mount -o remount,rw / ``` ## fstab のミスで緊急モードに落ちたら? {#fstab} > **結論**: `/etc/fstab` の記述ミスや存在しないデバイス指定は起動を止め、emergency モードに落とす。該当行を修正し、`mount -a` で全行を検証してから再起動する。 緊急モードへの転落で最も多いのが `/etc/fstab` の問題。追加したマウントの UUID 間違い、デバイス未接続、タイプミスなどで、systemd がマウント待ちのまま起動を諦める。 復旧手順: ```bash # 1. root を書き込み可で再マウント $ mount -o remount,rw / # 2. 直近のブートログから失敗したマウントを特定 $ journalctl -xb | grep -i -E 'mount|fstab|dependency' # 3. fstab を修正(問題行を一時的にコメントアウトしてもよい) $ nano /etc/fstab # 4. 設定を再読込し、全行のマウントを検証 $ systemctl daemon-reload $ mount -a ``` `mount -a` が**エラーなく完了**すれば fstab は健全。エラーが出るなら、その行がまだ問題を抱えている。 ::: tip **事前の保険**: 必須でないマウントには `nofail` オプションを付けておくと、そのデバイスが見つからなくても起動が止まらない。外付けディスクやネットワークマウントに有効。 ```bash UUID=xxxx /mnt/data ext4 defaults,nofail 0 2 ``` ::: ## ブートログをどう読むか? {#logs} > **結論**: 復旧後は `journalctl -xb` で今回の、`journalctl -b -1` で前回の失敗ブートを読む。赤字(失敗ユニット)と `Dependency failed` の行が起点。 起動できたら、再発防止のため何が起きたかを確認する。 ```bash # 今回のブート全体(-x で説明文付き) $ journalctl -xb # 直前(失敗した)のブート $ journalctl -b -1 # 失敗したユニットだけを一覧 $ systemctl --failed ``` `Dependency failed for ...` や `Failed to mount ...` の行が、起動が止まった直接の原因を指す。ディスク I/O エラーが混じる場合はハードウェア障害も疑う。 ::: warning 前回ブートのログ(`-b -1`)を読むには永続ジャーナルが必要。`/var/log/journal/` が無い環境では再起動でログが消える。`sudo mkdir -p /var/log/journal && sudo systemctl restart systemd-journald` で永続化しておく。 ::: ## やってはいけないこと {#pitfalls} > **結論**: マウント中の FS への fsck、原因未確認のままの再インストール、panic 画面の見落としは事態を悪化させる。最上部のログと段階の特定を最優先する。 - **マウント中のファイルシステムに `fsck` をかける** → データ破損。必ず read-only / アンマウント状態で - **panic 画面の最後の行だけ見て判断** → 本当の原因は最上部。スクロールアウトに注意 - **原因を切り分けずに OS 再インストール** → fstab 1 行や旧カーネル起動で直るケースを潰してしまう - **`fstab` を直さず問題行を放置** → 再起動のたびに emergency に落ちる - **永続ジャーナル未設定のまま再起動** → 失敗時のログが消えて原因不明になる ## 次に読む {#next} - [「Read-only file system」の対処](/articles/troubleshooting/read-only-file-system) - [systemd サービスが起動しない](/articles/troubleshooting/systemd-service-wont-start) - [「Input/output error」の対処](/articles/troubleshooting/input-output-error) # 「cannot set LC_ALL」ロケール警告の解決 Source: https://penguin-gym-linux.com/articles/troubleshooting/locale-cannot-set ## この記事で解決できること {#intro} - `cannot set LC_ALL` / `setlocale` 警告が **なぜ出るのか** が分かる - どの環境変数が原因かを `locale` / `locale -a` で **即切り分け** できる - 警告を **応急処置で止める方法** と、ロケールを生成して **恒久解決する手順** が身につく ::: tip **結論(切り分けの型)** - 警告の正体は「**要求したロケールがシステムに存在しない**」の 1 点 - まず `locale` で警告を、`locale -a` で **手元にあるロケール** を確認する - 無ければ `locale-gen` で生成、急ぐなら `export LC_ALL=C` で黙らせる ::: ::: warning **前提(対象環境)** - OS: Ubuntu / Debian / RHEL 系(glibc + systemd) - ローカル端末・SSH 接続先サーバ・コンテナのいずれでも発生しうる ::: ## 「cannot set LC_ALL」警告はなぜ出るのか? {#why} > **結論**: `LANG` / `LC_*` 環境変数が指すロケール(例 `en_US.UTF-8`)が、そのシステムに生成・インストールされていないため。存在しないロケールを要求され、glibc が C ロケールへフォールバックする。 ロケールは「言語・文字コード・日付や数値の書式」をまとめた設定で、`en_US.UTF-8` や `ja_JP.UTF-8` のような名前を持つ。プログラムは起動時に `setlocale()` を呼び、`LC_ALL` → `LC_*` → `LANG` の優先順で要求するロケールを決める。 このとき**要求した名前がシステムに存在しない**と、glibc は警告を出して安全な `C`(POSIX)ロケールにフォールバックする。典型的なメッセージは次の通り。 ```output # locale コマンド自身 locale: Cannot set LC_ALL to default locale: No such file or directory # bash / 各種コマンド bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8): No such file or directory # perl(apt や Git のフックで頻出) perl: warning: Setting locale failed. perl: warning: Please check that your locale settings: LC_ALL = (unset), LC_CTYPE = "UTF-8", LANG = "en_US.UTF-8" are supported and installed on your system. perl: warning: Falling back to the standard locale ("C"). ``` ::: highlight **「存在しない」の主なパターン** - `locales` パッケージ自体が入っていない(最小構成のコンテナ・クラウドイメージ) - パッケージは入っているが、その**ロケールが生成されていない**(`locale-gen` 未実行) - SSH で**手元の端末のロケールが転送**され、接続先サーバに同じロケールが無い - タイプミス(`en_US` のように文字コード `.UTF-8` を書き忘れる、区切りを `en-US.UTF-8` のように間違える等)で存在しない名前を指している ::: ## どの環境変数がずれているかをどう確認するか {#diagnose} > **結論**: `locale` で現在値と警告対象の変数を、`locale -a` で利用可能なロケール一覧を確認する。要求している名前が一覧に無ければ、それが原因。 まず `locale` を実行すると、現在の設定と「どの変数が問題か」が同時に分かる。 ```bash $ locale ``` ```output locale: Cannot set LC_ALL to default locale: No such file or directory LANG=en_US.UTF-8 LANGUAGE= LC_CTYPE="en_US.UTF-8" LC_NUMERIC="en_US.UTF-8" ... LC_ALL= ``` 次に、そのシステムで**実際に使えるロケール**を一覧する。 ```bash $ locale -a ``` ```output C C.UTF-8 POSIX ``` この例では `en_US.UTF-8` を要求しているのに、一覧には `C` 系しか無い。**要求名が `locale -a` に出てこない**ことが、警告の決定的な証拠になる。 ::: warning 名前の表記ゆれに注意。`locale -a` は `en_US.utf8`(小文字・ハイフン無し)形式で表示することがあるが、`LANG=en_US.UTF-8` 形式の指定でも同一として扱われる。一方で `en_US`(文字コード無し)と `en_US.UTF-8` は**別物**なので、サフィックスまで一致しているか確認する。 ::: ## SSH 接続で警告が出るのはなぜか {#ssh} > **結論**: SSH クライアントが `SendEnv LANG LC_*` で手元のロケールをサーバへ転送し、サーバ側が `AcceptEnv` で受け入れるため。サーバにそのロケールが無いと、ログインのたびに警告が出る。 「ローカルでは出ないのにサーバに SSH すると毎回出る」場合、原因はほぼこれ。多くのディストリの SSH クライアント設定には次の行がある。 ```output # /etc/ssh/ssh_config もしくは ~/.ssh/config SendEnv LANG LC_* ``` これにより手元の `LANG=ja_JP.UTF-8` などがサーバへ送られる。サーバ側 `/etc/ssh/sshd_config` の `AcceptEnv LANG LC_*` が受け取り、セッションの環境変数に設定する。サーバにそのロケールが無ければ、シェル起動時に `setlocale` が失敗する。 対処は 2 択。**サーバ側にロケールを入れる**(後述の生成手順)か、**転送をやめる**。転送を止めるならクライアント側の設定を書き換える。 ```bash # ~/.ssh/config で特定ホストだけ転送を無効化 Host myserver SendEnv -LANG -LC_* ``` ::: tip 特定の接続だけ一時的にロケール起因の警告を避けたいなら、サーバが必ず持つ `C` ロケールを渡して上書きする。`LC_ALL` は他のすべての `LC_*` / `LANG` より優先されるため、転送された値があっても `C` で上書きされる。 ```bash $ LC_ALL=C ssh user@myserver ``` ::: ## ロケールを生成・インストールするには?(Debian / Ubuntu) {#fix-debian} > **結論**: `locales` パッケージを入れ、`locale-gen` で目的のロケールを生成し、`update-locale` で既定値を設定する。対話式の `dpkg-reconfigure locales` でも同じことができる。 Debian / Ubuntu ではロケールは「パッケージ導入」と「生成」の 2 段階。 ```bash # 1. locales パッケージ(未導入の最小環境向け) $ sudo apt update && sudo apt install -y locales # 2. 目的のロケールを生成 $ sudo locale-gen en_US.UTF-8 ja_JP.UTF-8 # 3. システム既定のロケールを設定 $ sudo update-locale LANG=en_US.UTF-8 ``` 生成済みかは `locale -a` で再確認する。設定の永続先は `/etc/default/locale`。 ```bash $ cat /etc/default/locale ``` ```output LANG=en_US.UTF-8 ``` 対話メニューで選びたい場合は次のコマンドを使う。表示された一覧からロケールをスペースキーで選択して生成する。 ```bash $ sudo dpkg-reconfigure locales ``` ::: warning `update-locale` の変更や `/etc/default/locale` は **新しいログインセッションから反映**される。現在のシェルにすぐ効かせたいなら、一度ログアウト・再ログインするか、`export LANG=en_US.UTF-8` で当座の値を入れる。 ::: ## RHEL / Fedora 系での直し方 {#fix-rhel} > **結論**: RHEL 8 以降は `glibc-langpack-` パッケージでロケールを導入し、`localectl set-locale` で既定値を設定する。 RHEL / CentOS Stream / Fedora 系では、言語ごとの langpack を入れる方式。 ```bash # 英語ロケール一式 $ sudo dnf install -y glibc-langpack-en # 日本語が必要なら $ sudo dnf install -y glibc-langpack-ja # システム既定を設定 $ sudo localectl set-locale LANG=en_US.UTF-8 ``` `localectl` は systemd 系で共通のロケール管理コマンドで、設定は `/etc/locale.conf` に書き込まれる。現在値は `localectl status` で確認できる。 ```bash $ localectl status ``` ```output System Locale: LANG=en_US.UTF-8 VC Keymap: us X11 Layout: us ``` ::: tip Debian 系でも `localectl`(systemd)が使える環境なら `localectl set-locale` で既定値を設定できる。ただしロケールの**生成**自体は `locale-gen` が担うため、生成 → `localectl` の順は変わらない。 ::: ## 今すぐ黙らせたいときの応急処置 {#workaround} > **結論**: `export LC_ALL=C` または `export LANG=C.UTF-8` で、存在が保証されたロケールに切り替える。根本解決ではないが、警告を即座に止められる。 スクリプト実行やワンショットの作業で「今だけ警告を消したい」場合、確実に存在する `C` 系ロケールを指定する。 ```bash # 警告を完全に止める(英語メッセージ・ASCII 照合になる) $ export LC_ALL=C # UTF-8 を維持したい場合(多くの環境で利用可能) $ export LANG=C.UTF-8 $ unset LC_ALL ``` `C`(= `POSIX`)と `C.UTF-8` は glibc に組み込まれており、`locale-gen` 不要でほぼ確実に使える。`C.UTF-8` を選べば、英語メッセージながら UTF-8 のバイト列は壊れずに扱える。 ::: warning `LC_ALL` は**すべての `LC_*` を上書きする最優先変数**。これを `C` に固定すると、日本語の文字種判定やソート順も C ロケールになる。恒久設定(`.bashrc` 等)に `LC_ALL=C` を書くと別の不具合を招くため、応急処置に留め、恒久対処はロケール生成で行う。 ::: ::: tip スクリプト単体で警告を抑えたいだけなら、先頭で次のように限定するのが安全。 ```bash #!/usr/bin/env bash export LC_ALL=C.UTF-8 ``` ::: ## 再発させないためのチェックリスト {#prevent} > **結論**: サーバに必要なロケールを生成済みにし、不要な SSH 転送を止め、`LC_ALL` の恒久固定を避ければ、ロケール警告はほぼ防げる。 | 対策 | コマンド / 設定 | 効果 | | ----------------------- | ---------------------------------------- | -------------------------------- | | 必要なロケールを生成 | `sudo locale-gen en_US.UTF-8` | 要求名の不在を解消 | | システム既定を明示 | `update-locale` / `localectl set-locale` | ログインごとの既定値を固定 | | 不要な SSH 転送を止める | `SendEnv -LANG -LC_*` | 存在しないロケールの持ち込み防止 | | 応急処置は局所に留める | スクリプト内 `export LC_ALL=C.UTF-8` | 恒久固定による副作用を回避 | ::: tip **コピペ用:ロケール診断ワンライナー** ```bash # 要求中のロケールと利用可能な一覧を並べて確認 echo "--- requested ---"; locale 2>&1 | grep -E '^(LANG|LC_ALL|LC_CTYPE)='; \ echo "--- available ---"; locale -a ``` ::: ## 次に読む {#next} - [SSH で接続できないときのチェックリスト](/articles/troubleshooting/ssh-troubleshooting) - [「sudo: unable to resolve host」の解決](/articles/troubleshooting/sudo-unable-to-resolve-host) - [自作スクリプトが「command not found」になる](/articles/troubleshooting/bash-command-not-found-path) # メモリ不足の調べ方:free/top/ps と OOM Killer の見分け Source: https://penguin-gym-linux.com/articles/troubleshooting/memory-troubleshooting ## この記事で解決できること {#intro} - サーバが重い/落ちる原因が「メモリ不足」かどうか判断できます - メモリを食っているプロセスを特定できます - OOM Killer(メモリ不足でOSがプロセスを殺す仕組み)が動いた痕跡を確認できます - "とりあえず再起動" で誤魔化さず、再発防止に繋げられます ::: tip **結論(最短ルート)** メモリが怪しいときは、次の順で見れば迷いません。 1. **全体把握**:`free -h`(メモリ/Swapの状況) 2. **犯人候補**:`top`(RESが大きい、CPUも高い) 3. **上位一覧**:`ps aux --sort=-%mem | head` 4. **OOM痕跡**:`journalctl -k | grep -i oom` 5. **対処**:原因プロセス/サービスへ(ログ、設定、メモリ上限、スケール、Swap) ::: ::: warning **前提(対象環境)** - OS:Ubuntu - 対象:サーバ触り始めた新人 - `sudo` 可能 - 目的:切り分けと復旧(+最低限の再発防止) ::: ## 1. そもそもメモリ不足っぽい症状 {#symptoms} > **結論**: サーバ遅延・プロセス突然死・502/503増加・再起動で一時回復するパターンがメモリ不足の典型症状。 「メモリ不足」は、見た目が色々です。 - サーバが極端に遅い(SSHが重い、コマンドが返らない) - ある瞬間にアプリが落ちる / 502や503が増える - プロセスが突然死する(ログにエラーが残る) - 何度再起動しても時間が経つと同じ現象が起きる ::: warning **重要**:CPUが100%でも、原因がメモリ(スワップ地獄)なことがあります。CPUだけ見て原因を誤認しないようにします。 ::: ## 2. free -h で全体を把握する(最初の一手) {#free} > **結論**: `free -h`のavailableが数十MBや Swap が満タンに近ければメモリ枯渇の危険信号と判断できる。 ```bash $ free -h ``` 例(イメージ): ```output total used free shared buff/cache available Mem: 2.0Gi 1.9Gi 30Mi 120Mi 70Mi 80Mi Swap: 1.0Gi 1.0Gi 0Mi ``` **見るポイント:** - **available**:実質的に使えるメモリの目安(これが小さいと厳しい) - **Swap**:Swapがほぼ満タンなら危険信号(スワップスラッシングで激重になりやすい) **目安(ざっくり):** - available が **数十MB** → かなり危険(OOMや極端な遅さが起きやすい) - Swap が **常に増え続ける** → 根本原因が放置されている可能性が高い ## 3. top で"今重い犯人"を探す(RESを見る) {#top} > **結論**: `top`でMキーを押してRES順に並べ替え、実メモリ使用量が大きいプロセスを犯人候補として絞り込む。 ```bash $ top ``` 見方(新人が見るべきはここ): - `%MEM`:メモリ使用率 - `RES`:実メモリ使用量(重要) - `%CPU`:CPU使用率 **topの操作(よく使う):** - `M`:メモリ順に並べ替え - `P`:CPU順に並べ替え - `q`:終了 ## 4. ps で"メモリ上位一覧"を確定する(証拠を作る) {#ps} > **結論**: `ps aux --sort=-%mem`でメモリ上位を一覧化し、プロセス巨大化・同種増殖・Docker増殖などのパターンを特定する。 ```bash $ ps aux --sort=-%mem | head -n 20 ``` CPU上位も併せて見ると、原因の方向性が分かります。 ```bash $ ps aux --sort=-%cpu | head -n 20 ``` **よくあるパターン:** - 1つのプロセスが巨大(例:java、node、php-fpm、python、db) - 同じ種類が大量(ワーカー暴走、プロセスが増殖) - Dockerコンテナが増殖 ## 5. OOM Killer が動いたか確認する(ここが決定打) {#oom} > **結論**: `journalctl -k | grep -i oom`でカーネルログにOOM Killerの記録があれば強制終了されたプロセス名が特定できる。 OOM Killer は「メモリが足りないから、OSがプロセスを強制終了した」状態です。アプリが突然死する原因として非常に多いです。 ### 5-1. カーネルログから探す(推奨) ```bash $ sudo journalctl -k | grep -i oom | tail -n 50 ``` ### 5-2. killed process を探す ```bash $ sudo journalctl -k | grep -i "killed process" | tail -n 50 ``` ### 5-3. dmesgでも確認可能 ```bash $ dmesg | grep -i oom | tail -n 50 ``` 典型ログ(イメージ): ```output Out of memory: Killed process 1234 (node) total-vm:... anon-rss:... ``` ここに **殺されたプロセス名** が出ます。それが原因の中心です。 ## 6. "メモリ不足" の原因をタイプ分けする(対処が変わる) {#types} > **結論**: スパイク型・リーク型・プロセス増殖型の3タイプで原因が異なり、タイプに応じた対処(上限設定・再起動設計・スケール)を選ぶ。 ### タイプA:一時的なスパイク(瞬間的に増える) - バッチ処理、重い集計、画像処理など - 対処:処理の分離、メモリ上限/ワーカー数調整、スワップ追加、インスタンスサイズアップ ### タイプB:リーク/増え続ける(時間経過で悪化) - Node/Python/Javaなどで長時間動かすと増える - 対処:アプリ側のメモリリーク疑い、ワーカー/プロセスの再起動設計、上限設定 ### タイプC:プロセス増殖(fork爆発・ワーカー過多) - php-fpmの子プロセスが増えすぎ、キュー/アクセス増でワーカーが増殖 - 対処:子プロセス数/ワーカー数の上限設定、負荷試験・レート制限 ## 7. "今すぐ復旧" の現実的手順(安全優先) {#recovery} > **結論**: まずサービスのログを確認してから再起動する順番を守り、再起動で一時回復する場合はメモリ蓄積系と判断して原因を潰す。 ### 7-1. どのサービスが重いか分かっている場合:優先的にログを見る ```bash $ sudo journalctl -u nginx -n 200 $ sudo journalctl -u app -n 200 ``` ### 7-2. サービス再起動(最終手段に近いが現場では必要) ```bash $ sudo systemctl restart $ sudo systemctl status ``` ::: warning 注意:再起動で治るなら、原因は "メモリが溜まる" 系が濃厚です。そのまま放置すると再発します。 ::: ## 8. Swap が絡むと"激重"になる(スワップ地獄の見分け) {#swap} > **結論**: SwapがほぼフルになるとメモリとディスクI/Oが連鎖して激重になるため`free -h`でSwap使用量を確認し根本原因を対処する。 Swapが増えてくると、メモリ不足→ディスクI/Oでさらに遅くなる地獄になります。 ```bash $ free -h ``` まずは "全体がswap使ってる" と分かれば十分です。 ## 9. やってはいけないこと {#mistakes} > **結論**: ログ確認なしの再起動繰り返し・Swapを万能薬扱い・ワーカー上限未設定放置が再発を招く典型的な失敗パターン。 ### やってはいけない1:原因を見ずに再起動を繰り返す ログとOOM痕跡を見ない再起動は、毎回同じ事故を繰り返します。 ### やってはいけない2:Swapを"万能薬"だと思う Swapは緊急回避としては有効ですが、根本対策ではありません。パフォーマンス悪化も招きます。 ### やってはいけない3:上限なしのワーカー/プロセス設定を放置する php-fpmやアプリワーカーは、上限なしだとメモリを食い尽くします。"上限"を必ず設けてください。 ::: tip **コピペ用:メモリ不足調査テンプレ** ```bash # 1) 全体 free -h # 2) 今の犯人(メモリ順に) top # 3) メモリ上位一覧(証拠) ps aux --sort=-%mem | head -n 20 # 4) OOM痕跡(決定打) sudo journalctl -k | grep -i oom | tail -n 50 sudo journalctl -k | grep -i "killed process" | tail -n 50 ``` ::: ::: tip **まとめ** - `free -h` の **available** と **Swap** をまず見る - `ps aux --sort=-%mem` で上位を確定 - `journalctl -k | grep -i oom` で **OOMの有無** を確定 - 再起動で一瞬治るなら、ほぼ確実に再発する。原因をタイプ分けして潰す ::: ## 次に読む {#next} - [システムログでOOMの痕跡を確認する](/articles/tutorials/journalctl-basics) - [CPU高負荷との切り分け](/articles/troubleshooting/cpu-high-load) - [Dockerがメモリ・ディスクを食っている場合](/articles/troubleshooting/docker-disk-usage) # Nginx/Apacheのログ場所と見方:access/errorで何が分かる? Source: https://penguin-gym-linux.com/articles/troubleshooting/nginx-apache-log ## この記事で解決できること {#intro} - Ubuntuで Nginx / Apache のログファイルの場所が分かります - 「500/502/503/504」「403」「404」などの原因を **ログから切り分け** できるようになります - ログ調査の"型"(見る順番・絞り込み・再現・確認)が身につきます - ログ肥大(ディスク逼迫)にも繋がるので、再発防止の観点も押さえられます ::: tip **結論(最短)** Webが壊れたら、まずこの順番で見れば迷いません。 1. **systemctlで生死確認**:`systemctl status nginx|apache2` 2. **error log を見る**:`tail -n 200 .../error.log` 3. **access log で発生箇所/ステータスを特定**:`grep " 500 " .../access.log` 4. **時間を絞って再現→直後のログを見る**(これが最強) 5. **502/503系なら upstream(アプリ側)へ波及** ::: ::: warning **前提(対象環境)** - OS:Ubuntu - Webサーバ:Nginx または Apache - `sudo` 可能 - "新人が迷わない" ために、手順を固定化して書きます ::: ## 1. まずどっちを使っているか確認(Nginx? Apache?) {#which} > **結論**: `systemctl status nginx/apache2`でどちらがactiveか確認し、両方activeならリバプロ構成を疑う。 既に分かっているなら飛ばしてOKです。 ### 1-1. サービス状態で確認 ```bash $ sudo systemctl status nginx $ sudo systemctl status apache2 ``` - `nginx` が active → Nginx を見ればOK - `apache2` が active → Apache を見ればOK - 両方 active → リバプロ構成の可能性(まず外側のログから) ### 1-2. ポート待受で確認(補助) ```bash $ sudo ss -lntp | grep ':80 ' $ sudo ss -lntp | grep ':443 ' ``` ## 2. ログの場所(Ubuntuの典型パス) {#location} > **結論**: Nginxは`/var/log/nginx/`、Apacheは`/var/log/apache2/`にerror.logとaccess.logが置かれている。 Ubuntuではだいたいここです。 ### 2-1. Nginx - error log:`/var/log/nginx/error.log` - access log:`/var/log/nginx/access.log` ```bash $ ls -la /var/log/nginx ``` ### 2-2. Apache(apache2) - error log:`/var/log/apache2/error.log` - access log:`/var/log/apache2/access.log` ```bash $ ls -la /var/log/apache2 ``` ## 3. まず "error log" を見る(ここが最短) {#error-log} > **結論**: error.logに原因(スタックトレース/設定エラー/上流死)が出るため`tail -n 200`で直近を確認し再現しながら`tail -f`で追うのが最強。 アクセスログ(access.log)は「何が起きたか」を見つけるのに良いですが、**原因(スタックトレース/設定エラー/上流死)** が出るのは error.log です。 ### 3-1. 直近200行を見る(最初の一手) ```bash # Nginx $ sudo tail -n 200 /var/log/nginx/error.log # Apache $ sudo tail -n 200 /var/log/apache2/error.log ``` ### 3-2. 再現しながらリアルタイム追跡(最強) ```bash # Nginx $ sudo tail -f /var/log/nginx/error.log # Apache $ sudo tail -f /var/log/apache2/error.log ``` ::: tip これを開いたまま、別タブでブラウザ/curl で再現すると、原因に直行できます。「ログ見たけど分からない」は、再現とセットにしていないことが多いです。 ::: ## 4. access log で「どのリクエストが死んだか」特定する {#access-log} > **結論**: `grep " 500 " access.log`のようにステータスコードやパスで絞り込み、どのリクエストが障害を起こしているか特定する。 ### 4-1. 500だけ拾う ```bash $ sudo grep " 500 " /var/log/nginx/access.log | tail -n 50 ``` ### 4-2. 404だけ拾う ```bash $ sudo grep " 404 " /var/log/nginx/access.log | tail -n 50 ``` ### 4-3. 特定パスだけ拾う(例:/api/) ```bash $ sudo grep " /api/" /var/log/nginx/access.log | tail -n 50 ``` ## 5. ステータスコード別:だいたいこう切り分ける {#status-codes} > **結論**: 500はアプリ/設定エラー、502/503/504はupstream障害、403は権限/アクセス制御、404はルーティング不備として次の調査先が変わる。 ### 5-1. 500(Internal Server Error) - アプリ内部エラー(PHP/Node/Ruby等) - 設定ミス(FastCGI設定など) - 権限/パス(Permission denied)の可能性も 次に見る:error.log(最優先)、アプリログ、`journalctl -u` ### 5-2. 502 / 503 / 504(Bad Gateway / Service Unavailable / Gateway Timeout) - Nginx/Apache が **上流(upstream)** に繋げない/応答が遅い/死んでる 次にやる: ```bash $ nc -vz 127.0.0.1 3000 $ curl -I http://127.0.0.1:3000 ``` ### 5-3. 403(Forbidden) - Basic認証、IP制限、WAF、ディレクトリ権限、ファイル権限 次に見る:error.log に "permission denied" や "client denied" が出る ### 5-4. 404(Not Found) - ルーティング/ファイルが存在しない - SPAのリライト設定不足 ## 6. "ログ場所が違う"時の見つけ方 {#find-location} > **結論**: `nginx -T`でaccess_log/error_logのパスを全設定から抽出でき、Apacheは`apache2ctl -S`で確認する。 ### 6-1. Nginxの実設定を全部出す ```bash $ sudo nginx -T 2>&1 | grep -E "access_log|error_log" | head -n 50 ``` ### 6-2. Apacheの構成確認 ```bash $ sudo apache2ctl -S ``` ## 7. 調査を速くする「絞り込みのコツ」 {#tips} > **結論**: 再現しながら`tail -f`で追うのが最強で、キーワードgrepを組み合わせることで調査時間を大幅に短縮できる。 ### 7-1. 今この瞬間に起きているものだけ見たい 最強は「再現→tail -f」です。 ### 7-2. キーワードで絞る(例:upstream) ```bash $ sudo grep -i "upstream" /var/log/nginx/error.log | tail -n 50 ``` ## 8. 失敗例と読み解き(error.logでよく出るやつ) {#errors} > **結論**: Connection refused/timeout/permission deniedなどパターンごとに原因が異なりerror.logから次の調査先が決まる。 ### 8-1. connect() failed (111: Connection refused) while connecting to upstream 意味:upstream先が待ち受けていない/落ちている ```bash $ nc -vz 127.0.0.1 $ sudo systemctl status ``` ### 8-2. upstream timed out (110: Connection timed out) 意味:upstreamが遅い/詰まっている(負荷・DB・I/Oなど) ### 8-3. permission denied 意味:ファイル/ディレクトリ権限、SELinux/APPArmor絡みもあり得る ### 8-4. client intended to send too large body 意味:アップロードサイズ制限(nginxのclient_max_body_size等) ## 9. やってはいけないこと {#mistakes} > **結論**: ログ確認なしのrestart連打・access.logだけで完結・ログ肥大放置の3つがNginx/Apacheトラブル調査の典型的な失敗パターン。 ### やってはいけない1:ログ見ずに restart を連打する ログを見ない再起動は、原因を隠すだけで時間を溶かします。まず error.log / journalctl を見てから。 ### やってはいけない2:access.logだけ見て満足する 原因が出るのはだいたい error.log です。accessは「どのリクエストか」特定用。 ### やってはいけない3:ログ肥大を放置する アクセスが増えるとログは増えます。ディスク逼迫(No space left)に繋がるので、容量監視やローテーション(logrotate)は必須。 ::: tip **コピペ用:ログ調査テンプレ(Nginx例)** ```bash # サービス生死 sudo systemctl status nginx # エラー(まずここ) sudo tail -n 200 /var/log/nginx/error.log # 再現しながら追う(最強) sudo tail -f /var/log/nginx/error.log # どのリクエストが死んだか sudo grep " 500 " /var/log/nginx/access.log | tail -n 50 sudo grep " 502 " /var/log/nginx/access.log | tail -n 50 sudo grep " 403 " /var/log/nginx/access.log | tail -n 50 sudo grep " 404 " /var/log/nginx/access.log | tail -n 50 ``` ::: ::: tip **まとめ** - まず **error.log**、次に **access.log** - 再現しながら `tail -f` が最短 - 502/503/504 は upstream(アプリ)へ。ポート疎通の型に戻る - ログはディスクを食う。No space left の原因にもなる ::: ## 次に読む {#next} - [systemdログの確認方法](/articles/tutorials/journalctl-basics) - [サービスの起動・停止・状態確認](/articles/tutorials/systemctl-basics) - [権限エラーの解決方法](/articles/troubleshooting/permission-denied-fix) # 「No route to host」の切り分け - ルーティングとファイアウォール Source: https://penguin-gym-linux.com/articles/troubleshooting/no-route-to-host ## 「No route to host」とは何を示すのか? {#intro} > **結論**: 相手ホストまでパケットを届ける**経路が確立できない**状態。カーネルまたは経路上の機器が「この宛先には到達できない」と判断した結果で、`EHOSTUNREACH` というエラーが返る。refused と同様に**即座に**失敗するのが特徴。 `No route to host` は、宛先までのパスのどこかで「届けられない」と判定されたときに出る。システムコールレベルでは `connect()` が `EHOSTUNREACH` を返し、アプリはこれを「No route to host」と表示する。 ```output $ ssh user@192.0.2.10 ssh: connect to host 192.0.2.10 port 22: No route to host ``` ```output $ curl http://192.0.2.10/ curl: (7) Failed to connect to 192.0.2.10 port 80 after 2 ms: No route to host ``` 判定が出る場所は 3 通りある。①自ホストのカーネルが「そもそも経路が無い」と判断する、②同一セグメントで相手が **ARP に応答しない**、③経路途中のルータやファイアウォールが **ICMP host-unreachable / host-prohibited** を返す。いずれも応答は**数ミリ秒で返る**ため、無応答の timeout とは挙動が大きく異なる。 ::: warning **前提(対象環境)** - OS: Ubuntu / 一般的な Linux(`ip` コマンド = iproute2) - 接続先: IP で到達したい任意のホスト - ルーティングテーブル・ARP テーブルを `sudo` 無しで参照できる前提 ::: ## refused / timeout とどう違うのか? {#vs-others} > **結論**: refused は「届いたがポートで拒否」、timeout は「届いたか不明・無応答」、No route to host は「そもそも届ける経路が無い / 相手に到達できない」。同じ接続失敗でも切り分けの起点がまったく異なる。 接続エラーは大きく 3 種類あり、原因の層が違う。最初にどれなのかを確定させる。 | エラー | 返ってくるもの | 主な原因の層 | | -------------------- | --------------------------- | ---------------------------------- | | Connection refused | TCP RST(即時) | サービス停止 / ポート(L4 アプリ) | | Connection timed out | 無応答(数十秒待つ) | DROP / 経路の沈黙(L3〜L4) | | No route to host | ICMP unreachable 等(即時) | 経路・ARP・REJECT(L2〜L3) | ```bash $ time curl -sS http://192.0.2.10/ ``` ```output curl: (7) Failed to connect to 192.0.2.10 port 80 after 2 ms: No route to host real 0m0.012s ``` `0.0xx` 秒で `No route to host` が返れば、相手ホストの**手前の経路**で止まっている。サービス停止やポート閉塞なら [Connection refused](/articles/troubleshooting/connection-refused) を、無応答で待たされるなら [ポート疎通の確認](/articles/troubleshooting/port-connectivity) を参照。本記事は **No route to host** を扱う。 ::: tip refused が「ホストは生きているがポートで断られた」なのに対し、No route to host は「ホストまでの道が無い・相手に届かない」。L4(ポート)の問題ではなく、L2〜L3(経路・到達性)の問題である点が決定的に違う。 ::: ## なぜ No route to host が出るのか? {#causes} > **結論**: 原因は「①ローカルの経路欠落 ②同一セグメントの ARP 解決失敗 ③経路途中のルータが ICMP 拒否 ④ファイアウォールの icmp-host-prohibited REJECT」の 4 つに整理できる。近い層(自ホスト)から順に潰すのが速い。 EHOSTUNREACH が返る経路を原因別に並べると次のとおり。 | 原因 | 起きやすい状況 | 確認の起点 | | ------------------------- | --------------------------------------------------- | ------------------ | | ローカル経路が無い | デフォルトゲートウェイ未設定 / route 削除 / IF down | `ip route get` | | ARP 解決の失敗 | 同一セグメントの相手が停止 / IP 重複 / L2 不通 | `ip neigh` | | 経路途中のルータが拒否 | ルータが ICMP host/network-unreachable を返す | `traceroute` | | ファイアウォールの REJECT | `reject-with icmp-host-prohibited` 等が入っている | `nft list ruleset` | 切り分けは **自ホストに近い層から外へ** 進める。まず「自分のルーティングテーブルにそもそも経路があるか」を見て、次に「同一セグメントなら相手と L2 で通信できるか」、最後に「経路の途中で拒否されていないか」の順だ。 ::: warning よく似たエラーに `Network is unreachable`(`ENETUNREACH`)がある。これは宛先**ネットワーク**への経路が無いケース(デフォルトルートすら無い等)で出やすい。No route to host(`EHOSTUNREACH`)は「ネットワークへの経路はあるが、その先のホストに届かない」寄りのニュアンス。どちらもローカル経路の確認から始める点は同じ。 ::: ## ローカルの経路を確認するには? {#routing} > **結論**: `ip route get <宛先IP>` で「カーネルがどの経路・どの出口 IF・どのゲートウェイを使うか」を一発で確認する。ここでエラーが出るなら原因は自ホストのルーティングテーブルに確定する。 まず、カーネルがその宛先へどう送ろうとするかを問い合わせる。 ```bash $ ip route get 192.0.2.10 ``` 経路が正常なら、出口インターフェースとゲートウェイ(または直結)が表示される。 ```output 192.0.2.10 via 10.0.0.1 dev eth0 src 10.0.0.42 uid 1000 cache ``` 経路が無い場合は、その場でエラーになる。 ```output $ ip route get 192.0.2.10 RTNETLINK answers: Network is unreachable ``` このときはルーティングテーブルとデフォルトゲートウェイを確認する。 ```bash $ ip route $ ip route show default ``` ```output default via 10.0.0.1 dev eth0 proto static 10.0.0.0/24 dev eth0 proto kernel scope link src 10.0.0.42 ``` `default via ...` の行が無ければデフォルトゲートウェイが未設定で、ローカルネットワーク外の宛先はすべて到達不能になる。インターフェース自体が down していないかも併せて確認する。 ```bash $ ip -br link $ ip -br addr ``` ```output eth0 UP 52:54:00:11:22:33 ``` IF が `DOWN` なら `sudo ip link set eth0 up`、IP やゲートウェイの恒久設定は netplan / NetworkManager 等の設定ファイルで行う。 ::: tip `ip route get` は実際にパケットを送らずに**カーネルの経路選択だけ**を再現する。宛先がどの IF・どの GW に向かうかを副作用なく確認できるため、切り分けの最初の一手として最適。 ::: ## 同一セグメントの ARP 解決を確認するには? {#arp} > **結論**: 宛先が同一サブネット内なら、通信には ARP による MAC 解決が必須。相手が応答しないと近傍テーブルが `FAILED` になり No route to host が返る。`ip neigh` で状態を確認する。 `ip route get` の出力で `via ` が無く `dev eth0 src ...`(直結)と出た場合、宛先は同一セグメントにある。このときは ARP(IPv6 では NDP)で相手の MAC を解決できるかが鍵になる。 ```bash $ ping -c1 -W1 192.0.2.10 $ ip neigh show 192.0.2.10 ``` 相手が応答していれば `REACHABLE` や `STALE` で MAC が入る。応答が無いと `FAILED` になる。 ```output 192.0.2.10 dev eth0 FAILED ``` `FAILED` は「同一セグメントに居るはずだが ARP に誰も答えない」状態。相手ホストの停止、IP の打ち間違い、IP アドレスの重複、スイッチ/VLAN/ケーブルといった L2 の問題を疑う。同セグメントの他ホストとの ARP が通るかを比較すると、自分側か相手側かを切り分けやすい。 ```bash $ ip neigh ``` ```output 10.0.0.1 dev eth0 lladdr 52:54:00:aa:bb:cc REACHABLE 192.0.2.10 dev eth0 FAILED ``` ゲートウェイ(`10.0.0.1`)が `REACHABLE` なのに目的の相手だけ `FAILED` なら、自ホストの L2 は正常で、相手ホストまたはその経路に問題がある。 ::: warning ARP テーブルは時間で状態が変わる。`FAILED` を見たら数秒おいて再度 `ip neigh` を確認するか、`ping` で能動的に解決を試みてから状態を読むと誤判定を避けられる。 ::: ## 経路途中のルータが原因の場合は? {#path} > **結論**: 別ネットワーク宛で No route to host が出るなら、経路途中のルータが ICMP host/network-unreachable を返している可能性が高い。`ping` の応答元と `traceroute` でどのホップで止まるかを特定する。 別サブネット宛でローカル経路もゲートウェイも正常なのに失敗する場合、判定は経路の途中で起きている。`ping` を打つと、ルータが代理で unreachable を返すことがある。 ```bash $ ping -c2 192.0.2.10 ``` ```output From 10.0.0.1 icmp_seq=1 Destination Host Unreachable From 10.0.0.1 icmp_seq=2 Destination Host Unreachable ``` 応答元(`From 10.0.0.1`)が**宛先ではなくルータ**になっている点が重要だ。これは「ゲートウェイまでは届いたが、その先で相手に到達できない」ことを示す。どのホップで途切れるかは `traceroute` / `mtr` で追う。 ```bash $ traceroute -n 192.0.2.10 ``` ```output 1 10.0.0.1 0.4 ms 0.3 ms 0.3 ms 2 10.0.0.1 !H * !H ``` `!H`(host unreachable)が出たホップが拒否の発生源。`!N` ならネットワーク到達不能、`!X` は管理的に通信禁止(後述のファイアウォール拒否)を表す。ここまで来たら、原因は自ホストではなく経路上の機器・対向側の設定にある。 ::: tip `traceroute` の末尾記号は切り分けの近道。`!H`=ホスト到達不能 / `!N`=ネットワーク到達不能 / `!X`=管理的に禁止(フィルタ)。`!X` 系が出たら次のファイアウォールセクションへ進む。 ::: ## ファイアウォールが原因の場合(icmp-host-prohibited) {#firewall} > **結論**: ファイアウォールが `reject-with icmp-host-prohibited`(または `icmp-host-unreachable`)で拒否すると、クライアントには **No route to host** が出る。`tcp-reset` なら refused になるため、REJECT の種別が症状を決める。 REJECT は「何を返して拒否するか」を選べる。返す ICMP の種類で、クライアント側の表示が変わる。 | REJECT の種別 | クライアントに出る症状 | | ------------------------------- | ---------------------- | | `icmp-host-prohibited` | No route to host | | `icmp-host-unreachable` | No route to host | | `icmp-net-unreachable` | Network is unreachable | | `icmp-port-unreachable`(既定) | Connection refused | | `tcp-reset` | Connection refused | つまり No route to host が出ていて、かつローカル経路・ARP・traceroute に問題が無いなら、`icmp-host-prohibited` 系の REJECT ルールを疑う。多くのディストリの初期 iptables ルールにはこの種別が含まれることがある。 ```bash # nftables のルール確認 $ sudo nft list ruleset | grep -i -E 'reject|prohibit' # iptables を使う環境 $ sudo iptables -L -n -v --line-numbers ``` ```output Chain INPUT (policy ACCEPT) num target prot ... destination 8 REJECT all ... reject-with icmp-host-prohibited ``` 上記のように `reject-with icmp-host-prohibited` が該当トラフィックにマッチしていれば、それが発生源だ。サーバ側・経路上のいずれにあるかは traceroute の `!X` 位置と合わせて判断する。ufw 環境での許可ルール確認と復旧は [ufw でSSHが繋がらない時](/articles/troubleshooting/ufw-ssh-troubleshooting) を参照。 ::: warning ファイアウォールのルール変更は、適用順序を誤ると自分自身の接続(SSH 等)を切る危険がある。リモートでルールを編集する際は、一定時間後に自動で元に戻すスケジュール(`at` での復旧予約など)を併用し、締め出しを防ぐ。 ::: ## それでも直らないときのチェックリスト {#checklist} > **結論**: No route to host は「経路・到達性の問題」が確定した状態。ローカル経路 → ARP → traceroute → REJECT ルールの順に、自ホストから外へ向かって潰せば原因はこの 4 層のどこかに収束する。 - [ ] エラーは `No route to host`(即時)か `timed out`(待たされる)か区別したか - [ ] `ip route get <宛先>` で経路・出口 IF・ゲートウェイを確認したか - [ ] `ip route show default` にデフォルトゲートウェイの行があるか - [ ] 出口インターフェースは `UP` か(`ip -br link`) - [ ] 同一セグメント宛なら `ip neigh` が `FAILED` になっていないか(ARP 解決) - [ ] `ping` の unreachable 応答元は宛先かルータか(`From ` なら経路途中) - [ ] `traceroute` でどのホップに `!H` / `!N` / `!X` が出るか - [ ] `nft list ruleset` / `iptables -L` に `icmp-host-prohibited` 系の REJECT が無いか ## 次に読む {#next} - [「Connection refused」の切り分け - サービス停止かポート閉塞か](/articles/troubleshooting/connection-refused) - [ポート疎通の確認:ss / lsof / nc / curl で原因切り分け](/articles/troubleshooting/port-connectivity) - [ufw でSSHが繋がらなくなった場合](/articles/troubleshooting/ufw-ssh-troubleshooting) # ディスクがいっぱい(No space left on device)になったときの調べ方(df/du + ログ肥大の切り分け) Source: https://penguin-gym-linux.com/articles/troubleshooting/no-space-left-on-device ## この記事で解決できること {#intro} - Ubuntuサーバで「ディスクがいっぱい」と言われたとき、どこが増えているかを素早く特定できます - `df` と `du` の使い分けが分かります - ログ肥大(Nginx/Apache、WordPress周辺、Docker、systemd journal)を疑うときの確認手順が分かります ::: tip **結論(最短)** 1. `df -h` で **どの領域(マウントポイント)が埋まっているか** を特定 2. 埋まっている領域で `du` を使い、**大きいディレクトリ → 大きいファイル** の順に絞り込み 3. `/var/log` と `journalctl` の使用量を見て、**ログ肥大かどうか** を切り分け ::: ::: warning **前提(対象環境)** - OS:Ubuntu(サーバ) - シェル:bash - 権限:`sudo` が使える想定(使えない場合は、読めない場所はスキップしてください) ::: ## 1. まずは「どこが埋まっているか」を確認する(df) {#df} > **結論**: df -hでUse%が高いマウントポイントを特定し次の調査対象ディレクトリを決めるのが最初の一手。 ```bash $ df -h ``` ### 注目ポイント - `Use%` が高い行(例:90%超) - `Mounted on`(どこにマウントされているか) ### よくあるパターン - `/`(ルート)が満杯 - `/var` が別パーティションで満杯(ログやサービスデータが溜まりやすい) - Docker利用で `/var/lib/docker` 配下が肥大 ### inode枯渇も確認(ファイル数が多すぎるケース) 容量が残っているのに「空きがない」系の症状は、inode枯渇の可能性があります。 ```bash $ df -ih ``` `IUse%` が高い(例:100%近い)場合は、**小さいファイルが大量**にある可能性が高いです。 ## 2. 「何が増えているか」を上から探す(du) {#du} > **結論**: du -x -h -d 1でディレクトリを粗く見て大きい箇所から掘り下げるのが原因特定の定石。 `df` で埋まっているマウントポイントが分かったら、その配下で `du` を使って原因を絞ります。 ### ルート配下の大きいディレクトリをざっくり見る (ルート `/` が満杯の場合) ```bash $ sudo du -x -h -d 1 / | sort -h ``` - `-x`:別マウントをまたがない(原因の切り分けがしやすい) - `-d 1`:深さ1階層まで(まずは粗く) - `sort -h`:サイズ順 ここで大きいディレクトリ(例:`/var`、`/home`)が見えたら、次はその中を掘ります。 ### /var が大きいとき(ログ・Docker・DBが溜まりやすい) ```bash $ sudo du -x -h -d 1 /var | sort -h ``` **よく肥大する候補** - `/var/log`(ログ) - `/var/lib`(DockerやDBなどのデータ) - `/var/cache`(キャッシュ) ## 3. ログ肥大の切り分け(/var/log と systemd journal) {#log} > **結論**: /var/logのサイズとjournalctl --disk-usageで出力するログ肥大かどうかを切り分けてから削除方針を決める。 ### /var/log のサイズを見る ```bash $ sudo du -h -d 1 /var/log | sort -h ``` さらに「ファイル単位」で大きいものを探します: ```bash $ sudo find /var/log -type f -printf "%s %p\n" | sort -n | tail -n 20 ``` **ここで候補に上がりやすい例** - Nginx:`/var/log/nginx/access.log` `error.log` - Apache:`/var/log/apache2/access.log` `error.log` - システム:`/var/log/syslog` `auth.log` ::: danger まずは「何が増えているか」を見つけるのが目的です。**いきなり削除はしないでください。** ::: ### systemd journal のサイズを見る(journalctl) Ubuntuでは journal(バイナリログ)が肥大していることがあります。 ```bash $ sudo journalctl --disk-usage ``` ## 4. Docker を使っている場合の確認(ログ/イメージ肥大の入口) {#docker} > **結論**: docker system dfでImages/Containers/Local Volumesのどれが大きいかの方向性を把握してから詳細調査に進む。 Docker環境だと、ログやイメージ、ボリュームで容量を使いがちです。 ### Docker全体の使用量を確認 ```bash $ docker system df ``` - `Images` / `Containers` / `Local Volumes` のどれが大きいかの当たりを付けます ::: tip 詳細な削除は事故りやすいので、ここでは「原因の方向性を掴む」までに留めるのがおすすめです。 ::: ## 5. 応急処置(安全寄り):まず空きを作る {#emergency} > **結論**: journalctl --vacuum-sizeで古いjournalを削除するのが副作用が少なく比較的安全な応急処置の一手。 根本原因(ログが出続ける、エラーが止まらない等)を止めないと再発します。 ただし緊急時は、まず空きを作ってサービスを復旧させる必要があります。 ### 古いjournalを削除して容量を制限(比較的安全) 最大1GBまでに抑える例: ```bash $ sudo journalctl --vacuum-size=1G ``` 日数ベース(例:7日より古いログを削除): ```bash $ sudo journalctl --vacuum-time=7d ``` ::: tip 迷う場合は `--vacuum-size` のほうが扱いやすいです。 ::: ### ログが異常に増え続ける場合(根本原因の入口) Nginx/Apache/WordPressでエラーが出続けると、ログが止まらず再発します。 - Nginx/Apacheのエラーログ(`error.log`) - WordPress/PHP-FPMのエラーログ - アプリの例外ログ まずは「何のエラーが連続しているか」を確認し、原因のサービスを止める/直す方向に進めます。 ## 6. 確認(直ったかチェック) {#check} > **結論**: 作業後はdf -hとdf -ihで容量とinode両方が改善されているかを確認して完了とする。 作業後は必ず `df` で改善を確認します。 ```bash $ df -h $ df -ih ``` ::: warning **事故らないための注意点** - **原因特定の前に削除しない**(消すほど悪化することがあります) - `/var/lib` はDocker/DBなどのデータがあるため、闇雲に触らない - ログ削除は「出続けている原因」が残っていると再発する(根本原因の修正が必要) ::: ## 次に読む {#next} - [安全にファイルを削除する方法](/articles/troubleshooting/find-safe-delete) - [Dockerが原因の場合の特定方法](/articles/troubleshooting/docker-disk-usage) - [ディスクI/Oが遅い場合の調査](/articles/troubleshooting/disk-io-troubleshooting) # サーバ時刻ズレの対処 - chrony/timedatectl で NTP を整える Source: https://penguin-gym-linux.com/articles/troubleshooting/ntp-time-skew ## 時刻ズレが引き起こす問題 {#impact} サーバの時刻がずれると、SSL/TLS 証明書検証エラー・Kerberos 認証失敗・ログのタイムスタンプ不整合・cron の誤動作など多岐にわたる障害につながる。数秒のズレが原因特定を困難にするため、時刻同期は運用の基本インフラとして維持する。 ::: tip **まず実行するコマンド** ```bash timedatectl status ``` `System clock synchronized: yes` なら同期済み。`no` なら以降の手順で修正する。 ::: ## 現在の時刻状態をどう確認するか {#check-status} `timedatectl status` が最初の診断コマンド。NTP 同期の有無・タイムゾーン・RTC 状態を一度に確認できる。 ### timedatectl status ```bash timedatectl status ``` ```output Local time: Mon 2026-06-01 10:00:00 JST Universal time: Mon 2026-06-01 01:00:00 UTC RTC time: Mon 2026-06-01 01:00:00 Time zone: Asia/Tokyo (JST, +0900) System clock synchronized: yes NTP service: active RTC in local TZ: no ``` | フィールド | 正常 | 異常 | | --------------------------- | -------- | ------------------ | | `System clock synchronized` | `yes` | `no` | | `NTP service` | `active` | `inactive` / `n/a` | ### date コマンドで現在時刻を確認 ```bash date date -u ``` ローカル時刻と UTC の差がタイムゾーンのオフセットと一致するか確認する。 ## どの NTP クライアントが動いているか {#ntp-clients} Ubuntu / RHEL 系では以下のどちらかが稼働している。両方を同時に有効にしないこと。 | デーモン | 主な採用先 | 設定ファイル | | ------------------- | -------------------------------- | --------------------------------------------------- | | `systemd-timesyncd` | Ubuntu(軽量) | `/etc/systemd/timesyncd.conf` | | `chronyd` | Ubuntu / RHEL / CentOS(高精度) | `/etc/chrony.conf` または `/etc/chrony/chrony.conf` | 稼働中のサービスを確認する: ```bash systemctl status systemd-timesyncd systemctl status chronyd ``` ::: warning `chronyd` と `systemd-timesyncd` を同時に有効にすると競合し、どちらも正しく同期しなくなる。片方のみ有効にすること。 ::: ## chrony を使った同期確認と修正 {#chrony} ### chronyc tracking で同期状態を確認 ```bash chronyc tracking ``` ```output Reference ID : 133.243.238.163 (ntp.nict.jp) Stratum : 2 Ref time (UTC) : Mon Jun 01 01:00:00 2026 System time : 0.000123456 seconds fast of NTP time Last offset : +0.000123456 seconds RMS offset : 0.000012345 seconds Frequency : -1.234 ppm fast Residual freq : +0.001 ppm Skew : 0.123 ppm Root delay : 0.012345678 seconds Root dispersion : 0.000123456 seconds Update interval : 64.2 seconds Leap status : Normal ``` `System time` が数秒以上ズレている場合は即時修正が必要。 ### chronyc sources で NTP サーバ一覧を確認 ```bash chronyc sources -v ``` ```output MS Name/IP address Stratum Poll Reach LastRx Last sample ^* ntp.nict.jp 1 6 37 43 -0.123ms[+0.456ms] +/- 12.3ms ``` `^*` が現在同期中のサーバ。`?` や `x` が多い場合は NTP サーバ側に問題がある。 ### 時刻を強制同期する(makestep) 大きなズレ(数秒〜数分)がある場合、通常の緩やかな修正を待たず即時修正する: ```bash chronyc makestep ``` ```output 200 OK ``` ::: warning `makestep` は時刻を急激にスキップさせる。ログタイムスタンプの飛びが発生するため、本番環境では実行前に影響を確認すること。 ::: ### chronyd が起動していない場合 ```bash # Ubuntu sudo apt install chrony sudo systemctl enable --now chronyd # RHEL / CentOS / Rocky Linux sudo dnf install chrony sudo systemctl enable --now chronyd ``` ## timedatectl で NTP を有効化する {#timedatectl} NTP 同期が無効な状態(`NTP service: inactive`)では以下で有効化する。 ```bash sudo timedatectl set-ntp true ``` 確認: ```bash timedatectl status | grep NTP ``` ```output NTP service: active ``` ### タイムゾーンの設定ミスを確認 時刻ズレの原因がタイムゾーン設定ミスの場合もある: ```bash # 現在のタイムゾーン確認 timedatectl status | grep "Time zone" # 利用可能なゾーン一覧(Asia/* を絞り込み) timedatectl list-timezones | grep Asia # タイムゾーンを設定 sudo timedatectl set-timezone Asia/Tokyo ``` ## systemd-timesyncd の確認と設定 {#timesyncd} `chrony` を使わず `systemd-timesyncd` を使っている環境では以下で確認する: ```bash timedatectl show-timesync --all ``` NTP サーバを変更する場合は `/etc/systemd/timesyncd.conf` を編集する: ``` [Time] NTP=ntp.nict.jp 0.pool.ntp.org 1.pool.ntp.org FallbackNTP=2.pool.ntp.org 3.pool.ntp.org ``` 設定を反映する: ```bash sudo systemctl restart systemd-timesyncd ``` ## VM・クラウド環境特有の時刻ズレ {#vm-cloud} VM をサスペンド/リジュームすると時刻が大きくずれることがある。`chronyc makestep` で即時修正するか、chrony 設定に `makestep` ディレクティブを追加しておく: ``` # /etc/chrony.conf に追加 makestep 1.0 3 ``` `makestep 1.0 3` は「最初の 3 回の同期で 1.0 秒以上のズレがあれば即時スキップ」を意味する。 ### クラウドプロバイダーの推奨 NTP | プロバイダー | 推奨 NTP | | ------------ | -------------------------- | | AWS | `169.254.169.123` | | Azure | `time.windows.com` | | GCP | `metadata.google.internal` | ## ファイアウォールで NTP が遮断されている場合 {#firewall} NTP は UDP 123 番ポートを使用する。ファイアウォールが遮断していると同期できない: ```bash # ufw の場合 sudo ufw allow out 123/udp # firewalld の場合 sudo firewall-cmd --add-service=ntp --permanent sudo firewall-cmd --reload ``` NTP サーバへの到達確認: ```bash chronyc ntpdata ``` ## まとめ {#summary} - 時刻ズレの確認は `timedatectl status` から始める - NTP 同期が無効なら `sudo timedatectl set-ntp true` で有効化 - `chronyc tracking` で詳細な同期状態とオフセット量を確認 - 即時修正が必要なら `chronyc makestep` - VM 環境では `/etc/chrony.conf` に `makestep 1.0 3` を設定しておくと安全 ## 次に読む {#next} - [SSH で接続できないときのチェックリスト](/articles/troubleshooting/ssh-troubleshooting) - [DNS 名前解決トラブルシューティング](/articles/troubleshooting/dns-troubleshooting) - [journalctl の基本と実践](/articles/tutorials/journalctl-basics) # OOM killer 発動時の対処 - メモリ不足でプロセスが落ちた Source: https://penguin-gym-linux.com/articles/troubleshooting/oom-killer-handling ## OOM killer とは何か? {#what-is} OOM killer(Out-Of-Memory killer)はLinuxカーネルに組み込まれたメモリ管理機構で、物理メモリとスワップが枯渇した際にプロセスを強制終了してシステム全体のクラッシュを防ぐ。「突然プロセスが落ちた」「ログに何も残っていない」ときはまず OOM killer の発動を疑う。 ::: tip **症状のチェックポイント** - アプリが理由不明でクラッシュした - `systemctl status` で `exit-code=SIGKILL` が表示される - `dmesg` に `Out of memory: Killed process` の行がある ::: ## ログで OOM killer 発動を確認する方法 {#check-log} カーネルが OOM killer を発動すると、その記録は必ずカーネルリングバッファとシステムログに残る。最初にここを確認する。 ### dmesg で確認する ```bash sudo dmesg | grep -i "out of memory" sudo dmesg | grep -i "oom" ``` ```output [1234567.890] Out of memory: Killed process 12345 (nginx) total-vm:512000kB, anon-rss:256000kB, file-rss:8000kB, shmem-rss:0kB, UID:0 pgtables:512kB oom_score_adj:0 ``` ### journalctl でカーネルログを検索する ```bash sudo journalctl -k --since "2 hours ago" | grep -i oom sudo journalctl -k -g "Out of memory" ``` `-k` はカーネルメッセージのみ表示するオプション。時刻が絞れる場合は `--since` で範囲を狭める。 ### syslog / messages から確認する ```bash sudo grep -i "out of memory" /var/log/syslog sudo grep -i "oom_killer" /var/log/kern.log ``` ::: tip `dmesg` のタイムスタンプはブート後秒数のため、`dmesg -T` を使うと人間が読める日時で表示される。 ::: ## どのプロセスが殺されたか調べる {#which-process} ログには殺されたプロセスの情報が詳細に記録される。 ```bash sudo dmesg | grep "Killed process" ``` ```output [1234567.890] Killed process 12345 (nginx) total-vm:512000kB, anon-rss:256000kB ``` 出力の読み方: | フィールド | 説明 | | --------------- | ------------------------------------- | | `12345` | プロセス PID | | `(nginx)` | プロセス名 | | `total-vm` | 仮想メモリ確保量 | | `anon-rss` | 実際に使用していた匿名ページ | | `file-rss` | ファイルバックドページ | | `oom_score_adj` | kill 優先度の調整値(0 がデフォルト) | OOM killer 発動前後のメモリ状態も記録される場合がある: ```bash sudo dmesg | grep -A 30 "Out of memory" | head -50 ``` ## OOM score の仕組みとスコア確認 {#oom-score} カーネルは各プロセスに `oom_score`(0〜1000)を付けており、スコアが高いプロセスから順に kill 対象として選ぶ。スコアはメモリ使用量ベースで計算される。 ### 現在のスコア確認 ```bash cat /proc//oom_score ``` 全プロセスのスコアをスコア降順で一覧する: ```bash awk '{print $1}' /proc/*/status 2>/dev/null | \ xargs -I{} sh -c 'echo "$(cat /proc/{}/oom_score 2>/dev/null) {} $(cat /proc/{}/comm 2>/dev/null)"' | \ sort -rn | head -20 ``` ### oom_score_adj でスコアを補正する `/proc//oom_score_adj` に -1000〜1000 の値を書き込むことでスコアを補正できる。 | 値 | 効果 | | ------- | ------------------------- | | `-1000` | OOM kill 対象から完全除外 | | `-500` | kill されにくくなる | | `0` | デフォルト | | `500` | kill されやすくなる | | `1000` | 最優先で kill される | ```bash # nginx プロセスを OOM kill 対象から外す(一時的) echo -500 | sudo tee /proc/$(pgrep nginx)/oom_score_adj ``` ::: warning `-1000` に設定したプロセスは OOM killer に殺されなくなるため、そのプロセス自体がメモリリークを起こすとシステム全体がクラッシュするリスクがある。重要なシステムサービスに限定して使用すること。 ::: ## 即時対処:プロセス再起動とメモリ回収 {#immediate} OOM killer がプロセスを殺した直後は、システムのメモリが回復しているはずだが、根本原因が残っている場合は再発する。 ### 殺されたサービスを再起動する ```bash sudo systemctl restart ``` ### ページキャッシュを手動解放する ```bash sync echo 3 | sudo tee /proc/sys/vm/drop_caches ``` ::: danger `drop_caches` はファイルシステムのページキャッシュ・dentryキャッシュ・inodeキャッシュを解放する。本番環境での使用はI/Oスパイクを引き起こす可能性があるため慎重に。 ::: ### 大量メモリを使用しているプロセスを確認する ```bash ps aux --sort=-%mem | head -20 free -h ``` ## 再発防止 — oom_score_adj を永続化する {#persistent-adj} プロセスを再起動するたびに `oom_score_adj` はリセットされる。永続化するには systemd のユニットファイルに設定を追加する。 ```bash sudo systemctl edit ``` エディタが開いたら以下を追記する: ```ini [Service] OOMScoreAdjust=-500 ``` 変更を反映する: ```bash sudo systemctl daemon-reload sudo systemctl restart ``` 確認: ```bash cat /proc/$(pgrep )/oom_score_adj ``` ## スワップを追加してメモリ圧迫を緩和する {#swap} スワップが不足または存在しない環境では、OOM killer が発動しやすくなる。スワップファイルを追加することで急激なメモリスパイク時のバッファを確保できる。 ### スワップファイルの作成手順 ```bash # 2GB のスワップファイルを作成する sudo fallocate -l 2G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile ``` 有効化を確認する: ```bash free -h swapon --show ``` ```output total used free shared buff/cache available Mem: 3.8G 3.1G 100M 50M 600M 600M Swap: 2.0G 0B 2.0G ``` ### 再起動後も有効にする(/etc/fstab に追加) ```bash echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab ``` ::: tip **スワップは根本解決ではない** スワップはメモリ枯渇の緩衝材に過ぎない。アプリのメモリ使用量が持続的に増加し続けるならメモリリークの調査が必要。`swappiness` の値(デフォルト 60)も運用環境に応じて調整する(サーバーは 10〜20 が一般的)。 ::: ## 根本対策:メモリ使用量を見直す {#root-cause} OOM killer が繰り返し発動する場合、スコア調整やスワップ追加は対症療法に過ぎない。根本原因の調査が必要。 ### よくある根本原因 1. **メモリリーク** — プロセスのメモリ使用量が時間とともに増加し続ける 2. **設定値の過大なメモリ要求** — JVM の `-Xmx`、MySQL の `innodb_buffer_pool_size` 等が物理メモリを超えている 3. **並列プロセス数の多すぎ** — Apache/PHP-FPM の `MaxRequestWorkers` 等 ### メモリ使用量の経時変化を監視する ```bash # 5秒間隔で特定プロセスのメモリ使用量を記録する watch -n 5 "ps -p -o pid,rss,vsz,comm" ``` ### cgroup でメモリ上限を設定する(再発防止) systemd 経由でサービスのメモリ上限を明示的に設定できる。 ```bash sudo systemctl edit ``` ```ini [Service] MemoryMax=512M MemorySwapMax=0 ``` 上限を超えた場合、サービスのみが終了し他のプロセスへの影響を防げる。 ## 次に読む {#next} - [メモリ不足の調べ方(free/top/ps)](/articles/troubleshooting/memory-troubleshooting) - [CPU 使用率 100% の原因調査](/articles/troubleshooting/cpu-high-load) - [No space left on device の対処法](/articles/troubleshooting/no-space-left-on-device) # 壊れた依存関係の修復 - held packages と unmet dependencies Source: https://penguin-gym-linux.com/articles/troubleshooting/package-held-broken-dependencies ## 「held packages」と「broken dependencies」は何が違うのか? {#intro} > **結論**: 両者は別問題。**held(保留)** はパッケージが意図的に更新対象から外れている状態で、エラーではない。**broken dependencies(壊れた依存関係)** は必要な依存が満たせず、インストール・更新が完了できない異常状態。前者は `apt-mark`、後者は `apt --fix-broken install` が対処の起点になる。 apt を使っていると、次のような違う症状を「依存関係の問題」とまとめて捉えがちだが、原因も対処も異なる。 ```output # (A) held: 更新が保留されただけ。エラーではない $ sudo apt upgrade The following packages have been kept back: linux-generic nvidia-driver-535 ``` ```output # (B) broken: 依存が満たせず処理が止まる。これは異常 $ sudo apt install some-package The following packages have unmet dependencies: some-package : Depends: libfoo (>= 2.0) but 1.8 is to be installed E: Unable to correct problems, you have held broken packages. ``` (A) は「apt がわざと更新を見送った」状態で、システムは正常。(B) は「必要なライブラリのバージョンが噛み合わない」状態で、放置すると他のインストールも巻き込まれる。**まずどちらの症状かを切り分ける**のが最初の一歩だ。 ::: warning **前提(対象環境)** - OS: Ubuntu / Debian 系(apt / dpkg を使うディストリビューション) - `sudo` が使える前提で進める - サードパーティ PPA や手動 `.deb` を入れた直後に (B) が起きやすい ::: ## なぜパッケージが「kept back(保留)」されるのか? {#kept-back} > **結論**: `apt upgrade` は**既存パッケージの削除を伴う更新を実行しない**保守的な設計(依存解決に必要な新規パッケージの追加は行う)。そのため削除を要するメタパッケージやカーネル系は「kept back」になる。Ubuntu の **phased updates(段階的配信)** でも一時的に保留される。 `apt upgrade` で「kept back」が出る理由は主に 2 つある。 | 理由 | 起きやすいパッケージ | 解消方法 | | -------------------------------- | -------------------------------------------- | ------------------ | | 更新に既存パッケージの削除が必要 | メタパッケージ / 移行パッケージ / カーネル系 | `apt full-upgrade` | | phased updates で配信途中 | 任意(Ubuntu のみ) | 待つ / 後述の確認 | `apt upgrade` は安全側に倒すため、依存解決に必要な**新しいパッケージは追加する**が、「今あるパッケージを消す」ことはしない。更新が既存パッケージの削除を要する場合、その更新は実行されず kept back になる。カーネル系メタパッケージの更新がこの制約に引っかかることがある。 削除も許可してシステム全体をまとめて更新するには `full-upgrade`(`apt-get` では `dist-upgrade`)を使う。 ```bash $ sudo apt full-upgrade ``` ::: tip Ubuntu の **phased updates** は、更新を全ユーザーに一斉配信せず数日かけて段階展開する仕組み。自分の端末がまだ対象%に入っていないと、その更新だけ kept back になる。この場合は数日待てば自然に降ってくる。今すぐ確認したいなら次で実体を見る。 ```bash $ apt-cache policy <パッケージ名> ``` ::: ## 明示的に hold されたパッケージをどう確認・解除するのか? {#hold} > **結論**: 誰か(または自分)が `apt-mark hold` で**意図的に更新を止めている**ケースがある。`apt-mark showhold` で一覧を確認し、不要なら `apt-mark unhold ` で解除する。`dpkg --get-selections` でも確認できる。 `full-upgrade` でも更新されないパッケージがあるなら、明示的に **hold(固定)** されている可能性が高い。hold は「このパッケージは更新するな」という指定で、特定バージョンに固定したいとき(カーネル・ドライバ等)に使われる。 まず hold 中のパッケージを一覧する。 ```bash $ apt-mark showhold ``` ```output nvidia-driver-535 linux-image-generic ``` ここに出た名前が、更新対象から外されているパッケージだ。同じことは `dpkg` でも確認できる。 ```bash $ dpkg --get-selections | grep hold ``` ```output nvidia-driver-535 hold ``` 固定の必要がなくなったら hold を解除する。解除すれば次の `upgrade` から通常どおり更新対象に戻る。 ```bash # hold を解除 $ sudo apt-mark unhold nvidia-driver-535 # 逆に固定したいとき $ sudo apt-mark hold nvidia-driver-535 ``` ::: warning 意図的に hold しているパッケージ(動作確認済みのドライバ等)を不用意に unhold すると、次の更新で挙動が変わることがある。**なぜ hold されているか**が分からないうちは解除しないこと。心当たりがなければ、まず誰がいつ設定したか(チームの運用ルール・プロビジョニングスクリプト)を確認する。 ::: ## unmet dependencies はなぜ起きるのか? {#unmet-causes} > **結論**: `unmet dependencies` は「必要なバージョンの依存が入手・両立できない」状態。原因はリポジトリの混在(PPA・手動 `.deb`)、`apt update` 不足、インストールの中断、ピン留め設定の 4 つに集約される。エラー本文の `Depends:` 行が直接の手がかり。 `unmet dependencies` が出たら、まずエラー本文を読む。どの依存が、どのバージョン制約で満たせないかが書いてある。 ```output The following packages have unmet dependencies: packageA : Depends: libbar (>= 3.0) but it is not going to be installed Depends: libbaz (= 1.2) but 1.4 is to be installed ``` `Depends: libbar (>= 3.0) but ...` は「libbar の 3.0 以上が要るのに、それが入れられない / 別バージョンが入ろうとしている」という意味だ。原因は次の 4 つに整理できる。 | 原因 | 起きやすい状況 | 対処の方向 | | ------------------------- | -------------------------------------------- | -------------------------------- | | リポジトリの混在 | PPA / 手動 `.deb` / 異なる Ubuntu 版を混ぜた | 該当 PPA を外す / `--fix-broken` | | `apt update` が古い | 依存の新バージョンをまだ知らない | `apt update` 後に再実行 | | 前回のインストール中断 | 電源断 / `kill` で `apt` が途中終了 | `dpkg --configure -a` | | apt pinning(優先度設定) | `/etc/apt/preferences` でバージョン固定 | ピン設定を見直す | 特に多いのが 1 つ目で、外部 PPA や野良 `.deb` が公式リポジトリと食い違うバージョンの依存を要求するケース。`apt-cache policy` で、その依存がどのリポジトリから来ているかを確認できる。 ```bash $ apt-cache policy libbar ``` ```output libbar: Installed: 2.8-1 Candidate: 2.8-1 Version table: 3.0-1 500 500 https://ppa.example/ubuntu jammy/main amd64 Packages *** 2.8-1 500 500 http://archive.ubuntu.com/ubuntu jammy/main amd64 Packages ``` 候補(Candidate)と要求バージョンがどのリポジトリ由来かを見れば、混在が原因かどうかが判断できる。 ## 壊れた依存関係をどう修復するのか? {#fix-broken} > **結論**: 定番は `sudo apt --fix-broken install`(旧 `apt-get -f install`)。半端な依存を解決しようと試みる。中断が原因なら先に `dpkg --configure -a`。リポジトリ情報が古いだけなら `apt update` で直ることも多い。 修復は影響の小さい順に試す。いきなりパッケージを強制削除するのではなく、apt 自身に解決させるのが基本だ。 まず、リスト情報を最新化してから壊れた依存の自動修復を試みる。 ```bash $ sudo apt update $ sudo apt --fix-broken install ``` `--fix-broken`(`-f`)は、依存関係が壊れたパッケージを検出し、足りない依存の追加や不要なものの削除を提案・実行する。前回の `apt` が処理途中で止まっていた場合は、先にこちらで「展開済みだが未設定」のパッケージを設定し直す。 ```bash $ sudo dpkg --configure -a $ sudo apt --fix-broken install ``` `apt` が「このパッケージを削除する」と提案してきたときは、**何が消えるのかを必ず読む**。意図しない重要パッケージの削除を伴う提案なら、`yes` と答える前に立ち止まる。 ::: warning 解決策としてよく出回る `sudo dpkg -i --force-depends *.deb` や `--force-all` は、**依存チェックを無視して無理やり入れる**ため、その場は通っても後でさらに壊れる。force 系オプションは最終手段であり、原因(混在リポジトリ・pinning)を直す方が先。 ::: ::: tip **復旧の定番セット**は次の順で実行する。 ```bash sudo apt update sudo dpkg --configure -a sudo apt --fix-broken install sudo apt full-upgrade ``` ::: ## aptitude で解決案を出すには? {#aptitude} > **結論**: `apt --fix-broken install` で解決できない複雑な依存衝突は、`aptitude` が**複数の解決案を対話的に提示**してくれる。採用したくない提案は拒否でき、apt より柔軟に落としどころを探れる。 apt が「解決できない」と諦める依存衝突でも、`aptitude` なら段階的な解決案(どれを保留し、どれをダウングレードするか等)を順に提示する。標準では入っていないことが多いので導入する。 ```bash $ sudo apt install aptitude $ sudo aptitude install <パッケージ名> ``` `aptitude` は依存が満たせないとき、`Accept this solution? [Y/n/q/?]` のように複数案を提示する。`n` で次の案、`y` で採用と、納得できる案を選べる。apt の「全部入れるか諦めるか」より粒度の細かい操作ができるのが利点だ。 ::: warning `aptitude` の提案にも「大量のパッケージを削除する案」が混ざることがある。提示された解決策の**削除・ダウングロード対象を必ず確認**してから採用すること。安易に最初の案を呑むと、apt で force するのと変わらない結果になる。 ::: ## それでも直らないときのチェックリスト {#checklist} > **結論**: 症状が held なのか broken なのかを切り分け、broken なら「update → configure → fix-broken」を順に当てる。混在リポジトリの心当たりがあれば、その PPA を外して再評価するのが最短ルート。 - [ ] 症状は「kept back(保留)」か「unmet dependencies(壊れ)」かを切り分けたか - [ ] kept back なら `apt full-upgrade` を試したか(phased updates なら待つ) - [ ] `apt-mark showhold` で意図的な hold が無いか確認したか - [ ] `unmet dependencies` のエラー本文の `Depends:` 行を読んだか - [ ] `sudo apt update` → `dpkg --configure -a` → `apt --fix-broken install` の順に試したか - [ ] `apt-cache policy <依存名>` で混在リポジトリ(PPA / 手動 .deb)を確認したか - [ ] `--force-*` で無理やり入れる前に、原因(pinning / 混在)を先に潰したか - [ ] 複雑な衝突は `aptitude` で解決案を確認したか ## 次に読む {#next} - [「Could not get lock /var/lib/dpkg/lock」の解決 - ロックの安全な解放と dpkg 復旧](/articles/troubleshooting/dpkg-lock-held) - [Permission denied の直し方 - Linux での原因切り分け(chmod / chown / sudo)](/articles/troubleshooting/permission-denied-fix) - [ディスクがいっぱい(No space left on device)になったときの調べ方](/articles/troubleshooting/no-space-left-on-device) # Permission denied の直し方 - Linux での原因切り分け(chmod / chown / sudo) Source: https://penguin-gym-linux.com/articles/troubleshooting/permission-denied-fix ## この記事で解決できること {#intro} - Ubuntuで `Permission denied` が出たとき、原因を素早く切り分けできます - `chmod` / `chown` / `sudo` をどう使い分けるか分かります - ありがちな事故(権限を開きすぎる等)を避けながら直せます ::: tip **結論(最短)** `Permission denied` はだいたい次のどれかです: 1. **ファイルの実行権限がない** → `chmod +x` 2. **所有者/グループが違う** → `chown` / `chgrp` 3. **そもそも管理者権限が必要** → `sudo` で実行 4. **ディレクトリに入れない(x権限がない)** → ディレクトリ権限を確認 まず `ls -la`(必要なら `namei -l`)で「誰が」「何の権限で」止められているかを見るのが最短です。 ::: ::: warning **前提(対象環境)** - OS:Ubuntu - シェル:bash - 権限:`sudo` が使える想定(使えない場合は管理者に依頼が必要です) ::: ## 1. まず症状を確認:どこで Permission denied になっているか {#check} > **結論**: どのコマンドでどのパスがPermission deniedになったかを確認してから原因のケース分類に進む。 例:ファイルが読めない ```bash $ cat /path/to/file cat: ...: Permission denied ``` 例:スクリプトが実行できない ```bash $ ./script.sh bash: ./script.sh: Permission denied ``` 例:ディレクトリに入れない ```bash $ cd /path/to/dir bash: cd: ...: Permission denied ``` ## 2. 一番大事:権限と所有者を確認(ls -la) {#ls} > **結論**: ls -laで所有者・グループ・rwxの3組を確認し自分がowner/group/otherのどれかを把握するのが切り分けの核心。 ```bash $ ls -la /path/to/target ``` ### 権限の読み方(最低限) - `r`:read(読む) - `w`:write(書く) - `x`:execute(実行/入る) 3つのまとまり: - 1つ目:所有者(user) - 2つ目:グループ(group) - 3つ目:その他(others) 例: - `-rw-r--r--`:所有者だけ書ける、他は読める - `drwxr-x---`:所有者は全部OK、グループは読む/入るOK、その他は何もできない ## ケースA:スクリプトが実行できない(xがない) {#caseA} > **結論**: ls -laでxが無いことを確認しchmod +xで実行権限を付与するだけで解決する。 症状: ```bash $ ./script.sh Permission denied ``` 確認: ```bash $ ls -la script.sh ``` `-rw-r--r--` のように `x` が無いなら、実行権限を付けます。 対処: ```bash $ chmod +x script.sh ``` ::: tip これは「実行できるようにする」だけです。読み書き権限まで広げる必要はありません。 ::: ## ケースB:書き込みできない(wがない / 所有者が違う) {#caseB} > **結論**: 書き込みはディレクトリの権限が関係するため対象ディレクトリのls -laで所有者とw権限を確認する。 症状: - ファイルの編集・保存ができない - `mkdir` や `touch` が `Permission denied` になる 確認: ```bash $ ls -la /path/to/dir ``` **書き込みは基本的にディレクトリの権限**が関係します。 対処の方向性: - 自分が所有者であるべき場所なら → 所有者を直す(ケースC) - システム領域(/etc, /var, /usr 等)なら → `sudo` で操作(ケースD) ## ケースC:所有者/グループが違う(chown/chgrp) {#caseC} > **結論**: chownで所有者を自分に変更するが-Rは対象パスを必ず確認してから実行しないと広範囲に事故が起きる。 症状: - 自分が管理しているはずのディレクトリなのに編集できない - デプロイ後に所有者が `root` になっている など 所有者を自分にする(例:ユーザーが `ubuntu` の場合): ```bash $ sudo chown ubuntu:ubuntu /path/to/dir ``` 配下もまとめて直す(注意して使う): ```bash $ sudo chown -R ubuntu:ubuntu /path/to/dir ``` ::: warning **注意**:`-R` は広範囲に効きます。パスを間違えると事故るので、必ず対象を確認してから実行してください。 ::: グループだけ変える: ```bash $ sudo chgrp www-data /path/to/dir ``` ## ケースD:そもそも管理者権限が必要(sudo) {#caseD} > **結論**: /etc/や/var/log/などシステム領域はsudoで操作するが一般ユーザーの作業ファイルにsudoは使わない。 システム設定やサービス関連は、一般ユーザーが触れないのが普通です。 例: - `/etc/nginx/nginx.conf` を編集 - `/var/log` の一部ログを読む - サービスを再起動する 対処: ```bash $ sudo vim /etc/nginx/nginx.conf ``` ```bash $ sudo systemctl restart nginx ``` ## ケースE:ディレクトリに入れない(xがない) {#caseE} > **結論**: cdのPermission deniedはディレクトリのx権限が原因でls -ldで確認しchmod 777は避けて所有者やグループを見直す。 `cd` の Permission denied は「ディレクトリの x 権限(入る権限)」が原因のことが多いです。 確認: ```bash $ ls -ld /path/to/dir ``` 対処の方向性: - 本来入れるべきなら、所有者やグループを見直す(ケースC) - むやみに `chmod 777` しない ## 3. どこで止まってるか分からない時:経路を分解して確認(namei) {#namei} > **結論**: namei -lでパス経路上のすべてのディレクトリ権限を一覧表示しどこで権限が切れているかを確認できる。 パスの途中のディレクトリ権限で止まることがあります。この場合、対象ファイルだけ見ても原因が見えません。 ```bash $ namei -l /path/to/target ``` 途中のどのディレクトリで権限が足りないかを確認できます。 ## 4. やってはいけないこと(ありがちな事故) {#danger} > **結論**: chmod 777やchown -R /は取り返しのつかない事故を引き起こすため必ず対象を確認してから最小限の修正を行う。 ::: danger **なんでも chmod 777 で直す** 一時的に動くことはありますが、セキュリティ事故の原因になります。特にWebサーバ配下や設定ファイルでやるのは危険です。 ::: ::: danger **ルート直下で雑に chown -R する** ```bash $ sudo chown -R ubuntu:ubuntu / # 絶対ダメ! ``` これは最悪の事故です(システムが壊れます)。`-R` は対象パスの確認が必須です。 ::: ## 次に読む {#next} - [SSH接続で権限エラーが出る場合](/articles/troubleshooting/ssh-troubleshooting) - [ファイル転送時の権限問題](/articles/tutorials/scp-rsync-basics) - [Webサーバのログで権限エラーを確認](/articles/troubleshooting/nginx-apache-log) # 「Permission denied (publickey)」の解決 - SSH公開鍵認証の確認 Source: https://penguin-gym-linux.com/articles/troubleshooting/permission-denied-publickey ## 「Permission denied (publickey)」とは何を意味するのか? {#intro} > **結論**: サーバまで到達しているが、公開鍵認証で拒否された状態。ネットワークやパスワードの問題ではなく「鍵が通っていない」ことだけを示す。 このエラーは、SSH の TCP 接続自体は成立し、サーバの `sshd` まで会話が届いていることを意味する。`Connection timed out` や `Connection refused` とは段階が違う。サーバは「`publickey` 方式での認証を試みたが、有効な鍵を確認できなかった」と言っている。 ```output user@server: Permission denied (publickey). ``` 括弧内の `publickey` は、サーバがその時点で許可している認証方式の一覧だ。`(publickey,password)` なら鍵かパスワードのどちらでも通るが、`(publickey)` だけならパスワード認証は無効化されており、鍵を通すしか道はない。 ::: warning **前提(対象環境)** - クライアント: Ubuntu / macOS(考え方は共通) - サーバ: Ubuntu(OpenSSH server) - サーバ側確認には別経路のログイン(コンソール等)または `sudo` が必要 ::: ## まず最短で何を確認すべきか? {#first} > **結論**: `ssh -v` で「鍵が提示されているか」と「サーバがどの段階で拒否したか」を読む。これだけで原因がクライアント側かサーバ側かに二分できる。 原因は大きく分けて、**クライアントが正しい鍵を提示していない**か、**サーバが提示された鍵を受け付けていない**かのどちらかだ。最初に切り分けるべきはここで、`-v` の出力がその判断材料になる。 ```bash $ ssh -v user@server.example.com ``` 注目する行: ```output debug1: Offering public key: /home/user/.ssh/id_ed25519 ED25519 SHA256:... debug1: Authentications that can continue: publickey debug1: No more authentication methods to try. ``` `Offering public key` が **一度も出ない** → クライアントが鍵を提示していない(クライアント側の問題)。 `Offering public key` は出るのに拒否される → サーバが鍵を受け付けていない(サーバ側の問題)。 ## なぜ鍵が拒否されるのか?原因の全体像 {#causes} > **結論**: 原因はクライアント層・サーバ層・sshd 設定層の3つに分類できる。上から順に潰すと迷子になりにくい。 | 層 | 主な原因 | 確認コマンド | | ------------ | ------------------------------------------------------- | ----------------------- | | クライアント | 鍵を指定していない / agent に未登録 / ユーザー名違い | `ssh -v` / `ssh-add -l` | | サーバ | `authorized_keys` に公開鍵が無い / 権限・所有者違い | `ls -la ~/.ssh` | | sshd 設定 | `PubkeyAuthentication no` / `AuthorizedKeysFile` のパス | `sudo sshd -T` | 以降、この3層を順番に確認していく。 ## ssh -v の出力をどう読むか? {#verbose} > **結論**: `-v` は試行した鍵と、サーバが返した「継続可能な認証方式」を時系列で出す。どこで会話が止まったかを読み取るのが核心。 代表的なパターンを挙げる。 **鍵を1つも提示していない場合:** ```output debug1: Authentications that can continue: publickey debug1: No more authentication methods to try. ``` `Offering public key` 行が無いまま終わっている。クライアントが使う鍵を見つけられていない。 **鍵は提示したが拒否された場合:** ```output debug1: Offering public key: /home/user/.ssh/id_rsa RSA SHA256:... debug1: Authentications that can continue: publickey ``` 鍵を出したのにサーバが受け取らなかった。サーバ側の `authorized_keys` か権限を疑う。 ::: tip `-vv` / `-vvv` でさらに詳細になる。鍵交換やアルゴリズムのネゴシエーションまで見えるが、認証の切り分けは `-v` で足りることが多い。 ::: ## クライアント側: 正しい鍵を提示できているか {#client} > **結論**: 秘密鍵の存在・権限・指定方法を確認する。`-i` で鍵を明示し、それで通るなら設定(config / agent)の問題に絞れる。 ### 確認1: 鍵ファイルが存在するか {#client-key} ```bash $ ls -la ~/.ssh ``` `id_ed25519`(秘密鍵)と `id_ed25519.pub`(公開鍵)のペアがあるか確認する。 ### 確認2: 秘密鍵の権限 {#client-perm} 秘密鍵の権限が緩いと、`ssh` は安全のためその鍵を拒否し、`Permissions 0644 for ... are too open` と警告して使わない。 ```bash $ chmod 700 ~/.ssh $ chmod 600 ~/.ssh/id_ed25519 ``` ### 確認3: 使う鍵を明示する {#client-i} 複数の鍵がある、または `~/.ssh/config` が未設定だと、意図した鍵が提示されないことがある。 ```bash $ ssh -i ~/.ssh/id_ed25519 user@server.example.com ``` これで通るなら、鍵自体は正しい。次は恒久設定として `~/.ssh/config` に書く。 ```output Host server HostName server.example.com User user IdentityFile ~/.ssh/id_ed25519 ``` ### 確認4: ユーザー名が合っているか {#client-user} 公開鍵は「どのユーザーの `authorized_keys` に登録されているか」が重要だ。鍵が正しくてもログインユーザーを間違えると拒否される。 ```bash $ ssh -i ~/.ssh/id_ed25519 deploy@server.example.com ``` ## サーバ側: authorized_keys と権限を確認する {#server} > **結論**: 公開鍵が `authorized_keys` に登録され、かつ `.ssh` と `authorized_keys` の所有者・権限が正しいかを見る。権限が緩いと `StrictModes` が鍵を無効化する。 サーバに別経路(コンソールや既存セッション)で入れる場合、対象ユーザーの `~/.ssh` を確認する。 ### 確認1: 公開鍵が登録されているか {#server-authkeys} ```bash $ cat ~/.ssh/authorized_keys ``` クライアントの `id_ed25519.pub` の内容と一致する行があるか確認する。無ければ登録する。クライアントから一発で登録するなら: ```bash $ ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server.example.com ``` 手動なら、公開鍵の中身を追記する。 ```bash $ cat ~/.ssh/id_ed25519.pub | ssh user@server 'cat >> ~/.ssh/authorized_keys' ``` ### 確認2: 所有者と権限(StrictModes) {#server-strictmodes} `sshd` はデフォルトで `StrictModes yes` のため、ホームや `.ssh` の権限が緩い/所有者が違うと、鍵が正しくても認証を拒否する。これは非常にハマりやすい。 ```bash $ chown -R user:user ~/.ssh $ chmod 700 ~/.ssh $ chmod 600 ~/.ssh/authorized_keys ``` ホームディレクトリ自体が **group/other に書き込み可** だと拒否される点にも注意する。 ```bash $ chmod go-w ~ ``` ::: warning サーバ側ログに理由が出る。`Authentication refused: bad ownership or modes for directory /home/user/.ssh` のような行が出ていれば、権限・所有者が原因だと確定できる。 ::: ## サーバ側ログで理由を確定するには? {#serverlog} > **結論**: `journalctl -u ssh` でサーバの認証ログを見ると、鍵不一致・権限・ユーザー違いなど拒否の具体的理由が出る。推測を止められる。 ```bash $ sudo journalctl -u ssh -n 100 --no-pager ``` リアルタイムで追う場合: ```bash $ sudo journalctl -u ssh -f ``` ディストリによっては `/var/log/auth.log` に出る。 ```bash $ sudo tail -f /var/log/auth.log ``` 典型的な行: - `Authentication refused: bad ownership or modes for directory` → 権限・所有者 - `error: Could not open authorized keys` → パスや存在の問題 - `Invalid user xxx` → ユーザー名違い ## sshd_config の設定を疑うべきケースは? {#sshd} > **結論**: 公開鍵認証自体が無効化されている、または鍵ファイルのパスが標準と異なる場合がある。`sshd -T` で実効値を確認する。 サーバの実効設定は、設定ファイルを直接読むより `sshd -T` で確認するのが確実だ(`Match` ブロックや複数ファイルの合成結果が出る)。 ```bash $ sudo sshd -T | grep -Ei 'pubkeyauth|authorizedkeysfile' ``` 確認ポイント: - `pubkeyauthentication yes` になっているか(`no` なら鍵認証が無効) - `authorizedkeysfile .ssh/authorized_keys` が想定どおりか(独自パスに変更されていないか) 設定を変更した場合は反映する。 ```bash $ sudo systemctl reload ssh ``` ::: danger SSH 設定の変更で自分を締め出さないよう、**既存セッションを1つ残したまま**別ターミナルで再接続テストする。`reload` 後すぐに新規接続が通るか確認してからセッションを閉じる。 ::: ## ssh-agent に鍵が登録されているか? {#agent} > **結論**: agent 経由で認証している場合、鍵が agent にロードされていないと提示されない。`ssh-add -l` で登録済み鍵を確認する。 `~/.ssh/config` で `IdentitiesOnly` を使っていない環境や、踏み台(ProxyJump)経由では、agent にロードした鍵が使われる。 ```bash $ ssh-add -l ``` `The agent has no identities.` なら鍵が未登録だ。追加する。 ```bash $ ssh-add ~/.ssh/id_ed25519 ``` agent 転送(`-A`)を使う場合は、ローカルの agent に鍵があることが前提になる。 ## それでも直らないときのチェックリスト {#checklist} > **結論**: クライアント→サーバ→sshd 設定→ログの順に、提示・登録・権限・実効設定を1つずつ潰す。多くは権限かユーザー名違いに収束する。 - [ ] `ssh -v` で `Offering public key` が出るか - [ ] 秘密鍵の権限は `600`、`~/.ssh` は `700` か - [ ] `-i` で鍵を明示すると通るか - [ ] ログインユーザー名は正しいか - [ ] サーバの `authorized_keys` に公開鍵があるか - [ ] サーバの `~`・`~/.ssh`・`authorized_keys` の所有者と権限は正しいか(StrictModes) - [ ] `sshd -T` で `pubkeyauthentication yes` か - [ ] `journalctl -u ssh` に拒否理由が出ていないか - [ ] agent 利用時、`ssh-add -l` に鍵があるか ## 次に読む {#next} - [SSH 接続全般のトラブルシュート](/articles/troubleshooting/ssh-troubleshooting) - [Permission denied の原因切り分け](/articles/troubleshooting/permission-denied-fix) - [ufw でSSHが繋がらなくなった場合](/articles/troubleshooting/ufw-ssh-troubleshooting) # ポート疎通の確認:ss / lsof / nc / curl で原因切り分け Source: https://penguin-gym-linux.com/articles/troubleshooting/port-connectivity ## この記事で解決できること {#intro} - 「繋がらない」を **ネットワーク / サーバ / アプリ** のどこで止まっているか切り分けできます - ありがちな "ufwのせいだと思い込む" "サービスの起動だけ見て満足する" を避けられます - 最短で原因に当たりを付けるコマンドセット(ss / lsof / nc / curl)が身につきます ::: tip **結論(最短ルート)** 「繋がらない」は、次の順番で見ると一発で整理できます。 1. **DNSが正しいか**(必要なら) 2. **クライアント→サーバの到達確認**:`nc -vz HOST PORT` 3. **サーバ側で待受確認**:`ss -lntp | grep :PORT` 4. **誰が掴んでるか**:`lsof -i :PORT` 5. **HTTPなら応答確認**:`curl -I http(s)://HOST` 6. **サービスとログ**:`systemctl status` / `journalctl -u` ::: ::: warning **前提(対象環境)** - OS:Ubuntu(サーバ側) - 対象:サーバ触り始めた新人 - ここでは「調査・切り分け」に集中(最終修正は別記事へリンク) ::: ## 1. まず「何が繋がらないのか」を言語化する {#clarify} > **結論**: HOST・PORT・プロトコルの3点を先に確定することで調査の迷子を防ぎ、最短で原因に当たりを付けられる。 最低限この3点を先に確定します。 - **HOST**:example.com なのか、IPなのか - **PORT**:22 / 80 / 443 / 3000 / 8080 / 3306 など - **プロトコル**:SSH?HTTP?DB? 例: - SSHが繋がらない → `HOST=example.com` `PORT=22(または2222)` - Webが見れない → `PORT=80/443` - APIが死んでる → `PORT=3000/8080` ::: warning この時点で曖昧だと、永遠に迷子になります。 ::: ## 2. クライアント側:まず到達できるか(nc) {#nc} > **結論**: `nc -vz HOST PORT`のsucceeded/timed out/refusedの結果でネットワーク・待受・サービスのどの層の問題か判別できる。 **一番大事なポイント**:サーバ側をいじる前に、クライアントから到達できるか見たほうが速いです。 ### 2-1. nc で疎通確認(TCP) ```bash $ nc -vz example.com 22 ``` ### 2-2. ポートが違う場合(例:2222) ```bash $ nc -vz example.com 2222 ``` ### 2-3. 結果の読み方(ここが核心) - `succeeded` → **ネットワーク的には届いている**(次はサーバ/アプリの問題) - `timed out` → **届いていない**(FW/SG/経路/DNS/相手死んでる) - `refused` → **届いているが待受がない**(サービス停止/ポート違い/バインド先が違う) ::: tip `timed out` で ufw を疑うのはアリですが、クラウド側FW(SG等)でも同じ症状になります。ufwだけ直しても改善しないことが多いので、次の手順で"サーバ側の待受"とセットで見ます。 ::: ## 3. サーバ側:そのポートで待ち受けているか(ss) {#ss} > **結論**: `ss -lntp | grep ':PORT'`で待受を確認し、127.0.0.1バインドは外部から届かないため0.0.0.0への変更が必要と判断できる。 `nc refused` のとき、ここで一発で決まります。 ### 3-1. 全待受を表示 ```bash $ sudo ss -lntp ``` - `-l`:listen中 - `-n`:数字で表示(DNS解決しない) - `-t`:TCP - `-p`:プロセス表示(sudoが必要) ### 3-2. 特定ポートだけ絞る(例:80) ```bash $ sudo ss -lntp | grep ':80 ' ``` ### 3-3. よくある "罠" の見分け **罠1:127.0.0.1 でしか待ってない(外から繋がらない)** `ss` の結果にこう出るパターン: ```output LISTEN 0 4096 127.0.0.1:3000 ... ``` これは **サーバ内部からしかアクセスできません**。外部公開したいなら `0.0.0.0:3000` 等で待つ必要があります(アプリ設定側)。 ## 4. サーバ側:誰がそのポートを掴んでいるか(lsof) {#lsof} > **結論**: `lsof -i :PORT`で占有プロセスを確認し、想定外のプロセスが掴んでいる場合は別サービスとの競合と判断する。 `ss` でも分かりますが、lsofは「人間に読みやすい」です。 ### 4-1. 80番の占有プロセスを表示 ```bash $ sudo lsof -i :80 ``` ### 4-2. 3000番の例 ```bash $ sudo lsof -i :3000 ``` ここで「想定してないプロセス」が掴んでいたら、別サービスが邪魔している可能性があります。 ## 5. HTTPの場合:curlで"応答の中身"まで見る {#curl} > **結論**: `curl -I`でHTTPステータスを確認し、200/301/403/500/502などのコードからアプリ・権限・上流のどの問題かを切り分ける。 Web/APIは「ポートは開いてるけど500」みたいな状況が普通にあります。 ### 5-1. ヘッダだけ確認 ```bash $ curl -I http://example.com $ curl -I https://example.com ``` ### 5-2. 典型的なステータスの読み方 - `200`:基本OK - `301/302`:リダイレクト(意図通りか確認) - `403`:権限/アクセス制御(WAF/Basic認証/許可IPなど) - `404`:ルーティング/パス間違い - `500`:アプリ内部エラー(ログへ) - `502/503/504`:上流/下流(リバプロやアプリ)問題 ## 6. DNSの切り分け(HOSTがドメインの場合) {#dns} > **結論**: `dig`でIPを確認してIPに直接疎通し、IPはOKでドメインがNGならDNSまたはCDN/WAF側の問題と切り分けられる。 「ドメインだけ繋がらない」はDNSか証明書かリダイレクトが原因になりがちです。 ### 6-1. ドメインがどのIPを引いているか ```bash $ dig example.com +short ``` ### 6-2. 直接IPに向けて疎通してみる(DNS切り離し) ```bash $ nc -vz 203.0.113.10 80 $ curl -I http://203.0.113.10 ``` IPだとOKでドメインだとNG → DNS(またはCDN/WAF)側の問題が濃厚 ## 7. "サーバは届いてるのに動かない"とき(systemctl / journalctl) {#systemctl} > **結論**: ncが成功してもアプリが返さない場合は`systemctl status`と`journalctl`でサービスの起動状態とログを確認する。 `nc succeeded` なのにアプリが返してこない/エラー、みたいな場合はここです。 ### 7-1. サービスが動いてるか ```bash $ sudo systemctl status nginx $ sudo systemctl status apache2 $ sudo systemctl status ssh ``` ### 7-2. ログを見る(最短) ```bash $ sudo journalctl -u nginx -n 200 $ sudo journalctl -u ssh -n 200 ``` ::: warning "起動してる" だけ見て終わるのは危険です。起動直後に落ちている、設定エラーで再起動ループしている、などはログを見ないと分かりません。 ::: ## 8. 失敗例(エラー文から原因階層を当てる) {#errors} > **結論**: timed outはFW/経路、refusedは待受なし、curl (60)はSSL問題、502は上流障害とエラー文から原因の階層が特定できる。 ### 8-1. nc: connect ... timed out 原因候補:ufw、クラウドFW(SG)、社内FW、経路、サーバダウン ### 8-2. nc: connect ... refused 原因候補:待受が無い、ポート違い、プロセス落ちてる ### 8-3. curl: (7) Failed to connect 原因候補:到達できない(timed out系) or 待受なし ### 8-4. curl: (60) SSL certificate problem 原因候補:証明書/ホスト名/中間証明書 ### 8-5. curl -I が 502 Bad Gateway 原因候補:nginx/apache → upstream(アプリ)が死んでる/別ポート ## 9. やってはいけないこと {#mistakes} > **結論**: FW即無効化・ポート番号22固定思い込み・ssで待受確認せずアプリを疑う順序違いが診断を遅らせる典型的な失敗パターン。 ### やってはいけない1:いきなりFWを無効化してごまかす `ufw disable` は最終手段です。まずは `nc` と `ss` で原因階層を決めてください。 ### やってはいけない2:ポート番号を確認せず22固定で進める 本当は2222で待っているのに「22を許可した」とか、時間が溶けます。 ### やってはいけない3:サーバ側の"待受(ss)"を見ずにアプリを疑う 待受が無いならアプリ以前です。切り分けの順序を守るのが最短です。 ::: tip **コピペ用:切り分けテンプレ** ```bash # クライアント側(到達確認) nc -vz example.com PORT # サーバ側(待受確認) sudo ss -lntp | grep ":PORT " # サーバ側(占有プロセス) sudo lsof -i :PORT # HTTPなら応答確認 curl -I http://example.com curl -I https://example.com # サービス/ログ sudo systemctl status sudo journalctl -u -n 200 ``` ::: ::: tip **まとめ** - `nc` で **届く/届かない** をまず確定 - `ss` で **待受がある/ない** を確定 - `curl` で **アプリとして正しく返ってるか** を確認 - 迷ったら「timed out / refused / succeeded」の違いに戻る ::: ## 次に読む {#next} - [ufwでブロックされている場合](/articles/troubleshooting/ufw-ssh-troubleshooting) - [SSH接続トラブルの詳細](/articles/troubleshooting/ssh-troubleshooting) - [名前解決が原因の場合](/articles/troubleshooting/dns-troubleshooting) # 「Read-only file system」の対処 - 再マウントとfsck Source: https://penguin-gym-linux.com/articles/troubleshooting/read-only-file-system ## 「Read-only file system」とは何が起きているのか? {#what} > **結論**: 書き込みが `Read-only file system`(EROFS)で拒否される状態。意図的な `ro` マウントか、カーネルが I/O エラーを検知して保護的に read-only へ落としたかのどちらか。 `touch` や `vi` の保存、ログ書き込みが次のように弾かれる。 ```output touch: cannot touch '/var/log/app.log': Read-only file system ``` 原因は大きく 3 系統に分かれる。切り分けの優先順位もこの順。 - **A. カーネルが障害検知で `ro` 化**(最重要)— ext4 の既定 `errors=remount-ro` により、I/O エラーやメタデータ破損を検知すると保護のため read-only へ自動降格する - **B. 意図的な `ro` マウント** — `/etc/fstab` の `ro` 指定、CD-ROM/SquashFS、コンテナの読み取り専用ボリューム - **C. ファイルシステム破損** — fsck が必要な不整合。多くは A を経由して顕在化する ::: warning **最優先の判断**: A の場合、背後でディスクが壊れかけている可能性がある。安易に `remount,rw` で書き戻す前に、必ず `dmesg` でハード I/O エラーの有無を確認すること。 ::: ## まず現状を確認するには? {#diagnose} > **結論**: `findmnt` で対象が本当に `ro` かを確認し、`dmesg` で ro 化の原因(I/O エラーかメタデータ破損か単なる設定か)を特定する。原因確認なしの書き戻しは禁物。 ### 1. 本当に read-only か確認する ```bash findmnt -o TARGET,SOURCE,FSTYPE,OPTIONS / ``` ```output TARGET SOURCE FSTYPE OPTIONS / /dev/sda1 ext4 ro,relatime,errors=remount-ro ``` `OPTIONS` 先頭が `ro` なら read-only。`/proc/mounts` を直接見てもよい。 ```bash grep ' / ' /proc/mounts ``` ### 2. なぜ ro になったかを `dmesg` で確認する ここが切り分けの核心。出力でハードウェア I/O エラーが見えるかどうかで対応が分岐する。 ```bash dmesg -T | grep -iE 'ext4-fs|i/o error|remount|read-only|critical' ``` ```output [Thu Jun 5 09:12:44 2026] EXT4-fs error (device sda1): ext4_journal_check_start:84: Detected aborted journal [Thu Jun 5 09:12:44 2026] EXT4-fs (sda1): Remounting filesystem read-only [Thu Jun 5 09:12:43 2026] blk_update_request: I/O error, dev sda, sector 12869632 ``` - `I/O error` / `blk_update_request` が出ている → **ディスク障害を疑う**([後述](#disk-failure)) - `EXT4-fs error` のみでハード I/O エラーなし → **破損。fsck が必要**([後述](#fsck)) - 何も出ない → **設定起因の `ro`**。`/etc/fstab` を確認 ::: tip `journalctl -k -b` でも同じカーネルログを参照できる。再起動後に消える `dmesg` と違い過去ブートも追える(`-b -1` 等)。 ::: ## 一時的に read-write へ戻すには?(remount) {#remount} > **結論**: 設定起因や軽微な一過性なら `mount -o remount,rw /` で即復帰できる。ただし I/O エラーや破損が原因のときは書き戻し自体が危険なので、`dmesg` 確認後に限る。 原因が「fstab の `ro` 指定」や「一過性で I/O エラーなし」と確認できた場合のみ実行する。 ```bash sudo mount -o remount,rw / ``` 特定パーティションなら対象を明示する。 ```bash sudo mount -o remount,rw /var ``` 成功すれば `findmnt` の OPTIONS が `rw` に変わる。`remount,rw` が `EROFS` や `cannot remount ... read-write` で失敗する場合、下層で I/O エラーまたは破損が起きているサインなので、無理に繰り返さず fsck/ディスク診断へ進む。 ::: danger **破損が疑われる状態での `remount,rw` は被害を広げる**。マウントしたまま書き込みを続けると、壊れたメタデータの上にさらに書き込み、復旧不能になることがある。I/O エラーが見えたらまず読み取り専用のまま重要データを退避してから修復する。 ::: ## ファイルシステム破損を修復するには?(fsck) {#fsck} > **結論**: `fsck` は**アンマウント状態**で実行するのが鉄則。マウント中の fs に `fsck` をかけると破損を悪化させる。ルートは Live USB かレスキューモードから修復する。 ### なぜマウントしたまま fsck してはいけないのか `fsck` はブロックを直接書き換える。マウント中(特に rw)の fs を裏で書き換えると、カーネルのキャッシュと不整合になりデータを破壊する。`ro` マウント中でも非推奨。必ず unmount するか、ルートなら別環境から起動する。 ### 非ルートパーティションの場合 ```bash sudo umount /dev/sdb1 sudo fsck -y /dev/sdb1 ``` - `-y`: 確認に自動で yes(大量の質問を省く) - `-f`: クリーン状態でも強制チェック - `ext4` なら `fsck.ext4`(= `e2fsck`)が呼ばれる ### ルートファイルシステムの場合 ルートは稼働中アンマウントできない。次のいずれかで修復する。 1. **Live USB / レスキューメディアから起動** → ルートを未マウントのまま `fsck -y /dev/sda1` 2. **次回起動時の自動 fsck を予約**して再起動 ```bash # 次回ブート時に強制 fsck(systemd 環境): カーネルコマンドラインに付与する # fsck.mode=force # 従来法: ルートに fsck 予約フラグを立てる sudo tune2fs -l /dev/sda1 | grep -i 'mount count' sudo touch /forcefsck # 古典的だが systemd では非対応のことがある ``` ::: warning クラウド VM(AWS EC2 等)でルートが ro 化した場合、対象 EBS ボリュームを一旦デタッチして別インスタンスにアタッチし、そこで `fsck` をかける手順が安全。コンソールのシリアルログ(`dmesg` 相当)も合わせて確認する。 ::: 修復後はマウントして書き込みできるか確認する。 ```bash sudo mount /dev/sdb1 /mnt && touch /mnt/.write-test && rm /mnt/.write-test ``` ## ディスク障害を疑うべきとき {#disk-failure} > **結論**: `dmesg` に `I/O error` やセクタ番号が出たら物理障害の可能性が高い。`smartctl` で健康度を確認し、劣化していれば fsck より先にデータ退避を優先する。 `dmesg` でハード I/O エラーを観測したら、修復より先にディスクの状態を確認する。 ```bash sudo smartctl -a /dev/sda | grep -iE 'health|reallocated|pending|uncorrect' ``` ```output SMART overall-health self-assessment test result: FAILED! 5 Reallocated_Sector_Ct 0x0033 100 100 010 - 384 197 Current_Pending_Sector 0x0012 100 100 000 - 16 ``` - `FAILED` / `Reallocated_Sector_Ct` や `Current_Pending_Sector` の増加 → **物理劣化**。fsck は一時しのぎにしかならない - この段階では書き込み(fsck の修復書き込み含む)がトドメになり得る。**読み取り専用のまま `dd`/`rsync` でバックアップ**を最優先にする ::: danger SMART が劣化を示すディスクへの fsck は、不良セクタへの書き込みで全損を招くことがある。データが重要ならディスクを交換し、退避済みデータから復元する判断を優先する。 ::: ## 再発を防ぐには {#prevent} > **結論**: `errors=remount-ro` は障害を握り潰さず可視化する安全設計。これを活かし、SMART 監視と空き容量・inode 監視で「ro 化する前」に異常を検知する体制を作る。 - **`errors=remount-ro` を維持**: ext4 既定。`errors=continue` に弱めると破損を放置して被害が拡大する - **`smartd`(smartmontools)で常時監視**: 不良セクタ増加を ro 化前にメール通知 - **容量・inode の枯渇を監視**: 枯渇起因の書き込み失敗は別問題。[ディスクがいっぱいの調べ方](/articles/troubleshooting/no-space-left-on-device)と[inode 枯渇の対処](/articles/troubleshooting/inode-exhaustion)を参照 - **定期 fsck**: `tune2fs -c ` でマウント回数ベースの自動チェックを設定 ::: tip **最短フロー**: ① `findmnt` で ro 確認 → ② `dmesg` で原因判定 → ③ I/O エラーあり=`smartctl`+退避、なし=`remount,rw` か `fsck`(アンマウント)。原因確認を飛ばさないことが事故を防ぐ唯一の鍵。 ::: ## まとめ / 次に読む {#next} - [ディスクがいっぱい(No space left on device)の調べ方](/articles/troubleshooting/no-space-left-on-device) - [inode 枯渇の対処 - 容量はあるのに書き込めない](/articles/troubleshooting/inode-exhaustion) - [ディスクI/Oが遅いときの調べ方](/articles/troubleshooting/disk-io-troubleshooting) - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) # SSH が勝手に切れる・タイムアウトする - keepalive(ServerAliveInterval)設定 Source: https://penguin-gym-linux.com/articles/troubleshooting/ssh-connection-closed-timeout ## SSH が勝手に切れるのはなぜか? {#intro} > **結論**: 多くは**無通信(idle)が続いた接続を、間にある NAT・ファイアウォールや sshd が黙って切る**のが原因。少し放置すると `Broken pipe` で落ちるなら、定期的に小さなパケットを流す **keepalive** が効いていない。client 側の `ServerAliveInterval` か server 側の `ClientAliveInterval` を設定すれば大半は止まる。 SSH 接続が「操作している間は平気なのに、少し席を外すと切れている」なら、ほぼ idle タイムアウトが犯人。TCP コネクションは無通信でもしばらくは生きているが、経路上の NAT 機器・ステートフルファイアウォール・ロードバランサは、一定時間パケットが流れないセッションを**接続テーブルから消す**。消された後にキー入力すると、行き場を失ったパケットが返り、次のようなメッセージで落ちる。 ```output client_loop: send disconnect: Broken pipe ``` ```output Write failed: Broken pipe packet_write_wait: Connection to 192.0.2.10 port 22: Broken pipe ``` ```output Timeout, server not responding. ``` 逆に「**操作中なのに**突然切れる」「数時間ぴったりで切れる」場合は、サーバ側の明示的なセッション上限や不安定な回線が疑わしい(後述)。まずは「idle で切れるのか、操作中でも切れるのか」を切り分けるのが最短。 ::: warning **前提(対象環境)** - client / server とも Ubuntu / 一般的な Linux(OpenSSH) - 編集対象: client は `~/.ssh/config`、server は `/etc/ssh/sshd_config` - server 側設定の反映には `sudo` と sshd の再読み込みが必要 ::: ## idle で切れるのか操作中でも切れるのかをどう見分けるか? {#which-side} > **結論**: 何もせず放置して切れるなら **idle タイムアウト**(NAT / firewall / `ClientAliveInterval`)。`top` 等を流して画面を更新し続けても切れるなら**回線品質か明示的なセッション上限**。前者は keepalive で直り、後者は別アプローチになる。 原因を二分するために、まず「通信を流し続けたら切れないか」を試す。 ```bash # 何も出力しないが接続は維持される簡易テスト(一定間隔で時刻を表示) $ ssh user@server 'while true; do date; sleep 30; done' ``` - これで**切れなくなる** → 無通信が原因の idle タイムアウト。keepalive で解決する(次節) - これでも**切れる** → 回線断・無線の不安定、またはサーバ側の固定タイムアウト(`sshd_config` の他設定や PAM、ロードバランサのハードリミット)を疑う `ssh -v` で接続を張り、切れた瞬間のログを見るとさらに確実。 ```bash $ ssh -v user@server ``` ```output debug1: client_loop: send disconnect: Broken pipe ``` 切断の直前に keepalive の送受信ログ(`debug1: Sending command` ではなく channel のやり取り)が止まっているか、`server not responding` が出ているかで、どちら側が先に諦めたかが読める。 ::: tip 切れるまでの時間が**毎回ほぼ一定**なら、人為的なタイムアウト設定(NAT の conntrack やサーバの `ClientAliveInterval` × `ClientAliveCountMax`)の可能性が高い。**バラつく**なら回線品質の問題に寄る。 ::: ## クライアント側の対策:ServerAlive をどう設定するか? {#client} > **結論**: client の `~/.ssh/config` に `ServerAliveInterval 60` を入れるのが第一手。無通信でも 60 秒ごとに**暗号化チャネル経由**でサーバへ応答要求を送るため、NAT のセッションが維持され、サーバ無応答も検知できる。サーバを触れない環境(共用サーバ等)でも client 側だけで効く。 `ServerAliveInterval` は「サーバから N 秒データが来なければ、ssh が暗号化チャネル越しに応答要求を送る」秒数。デフォルトは `0`(無効)。`ServerAliveCountMax`(デフォルト `3`)は、応答が無いまま送ってよい回数で、これを超えると ssh が切断する。 ```bash # ~/.ssh/config Host * ServerAliveInterval 60 ServerAliveCountMax 3 ``` この設定なら、60 秒ごとに小さなパケットが流れて NAT・firewall のセッションが生き続け、idle 切断を防げる。同時に、サーバが本当に落ちたときは `60 秒 × 3 = 約 180 秒` で見切りをつけて切断する(ゾンビセッションを残さない)。 一時的に試すだけなら `-o` で指定できる。 ```bash $ ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 user@server ``` ::: tip `Host *` に書けば全接続に効く。特定ホストだけにしたいなら `Host myserver` ブロックに書く。`~/.ssh/config` のパーミッションは `600` が無難(緩いと無視されることがある)。 ::: ::: warning `ServerAliveInterval` を**短くしすぎる**と、瞬断のたびに `ServerAliveCountMax` を消費して**逆に切れやすくなる**。回線が不安定な環境では Interval をむやみに詰めず、CountMax を増やす(例: `Interval 30` / `CountMax 6` で約 180 秒猶予)方向で調整する。 ::: ## サーバー側の対策:ClientAlive をどう設定するか? {#server} > **結論**: server を管理できるなら `/etc/ssh/sshd_config` に `ClientAliveInterval 60` を入れる。sshd が**全クライアントに対して**暗号化チャネル経由で応答要求を送るので、client 側を個別設定しなくても idle 切断を防げる。逆に**idle を積極的に切りたい**ときも同じパラメータで制御する(CountMax との組み合わせ次第で両用途)。 `ClientAliveInterval` は client 側 `ServerAliveInterval` の鏡像で、sshd が「クライアントから N 秒データが来なければ応答要求を送る」秒数。デフォルト `0`(無効)。`ClientAliveCountMax` はデフォルト `3`。 ```bash # /etc/ssh/sshd_config ClientAliveInterval 60 ClientAliveCountMax 3 ``` 設定後は構文チェックしてから反映する。**ここで sshd 設定を壊すと自分も締め出される**ので、必ず別セッションを開いたまま行う。 ```bash # 構文チェック(エラーがあれば反映前に分かる) $ sudo sshd -t # 設定を再読み込み(既存セッションは維持される) $ sudo systemctl reload ssh ``` ```output # sshd -t がエラーを出さなければ何も表示されない(=正常) ``` この設定の意味は用途で逆向きになる点に注意する。 - **接続を維持したい**: `ClientAliveInterval 60` だけで keepalive が流れ、NAT 切断を防げる。`CountMax` は大きめでよい - **idle を切りたい**(セキュリティ要件など): `ClientAliveInterval 300` / `ClientAliveCountMax 1` のように設定すると、無通信のクライアントを約 5 分で切断できる(`ClientAliveCountMax 0` は逆に切断を無効化するので注意) ::: warning sshd を `restart` すると既存接続が落ちる場合がある。設定反映は原則 `reload`。さらに**作業用の SSH セッションをもう 1 本開いたまま** `sshd -t` → `reload` の順で行い、ロックアウトに備える。ufw 等で自分を締め出した場合の復旧は [ufw でSSHが繋がらない時](/articles/troubleshooting/ufw-ssh-troubleshooting) を参照。 ::: ## ServerAlive と TCPKeepAlive はどう違うのか? {#vs-tcpkeepalive} > **結論**: `ServerAliveInterval` / `ClientAliveInterval` は **SSH の暗号化チャネル**を流れるためスプーフ不可で、間隔も秒単位で細かく制御できる。`TCPKeepAlive` は **TCP 層**の keepalive で、OS 既定の長い間隔(通常 2 時間起点)に依存し、なりすまし可能。idle 切断対策には **SSH 層の ServerAlive 系を使う**のが定石。 混同しやすい 3 つを層で整理する。 | 設定 | 層 | 方向 | 備考 | | --------------------- | ------------- | ------------- | ------------------------------------ | | `ServerAliveInterval` | SSH(暗号化) | client→server | client 側に書く。スプーフ不可 | | `ClientAliveInterval` | SSH(暗号化) | server→client | server 側に書く。スプーフ不可 | | `TCPKeepAlive` | TCP | 双方向 | 既定 `yes`。間隔は OS の sysctl 依存 | `TCPKeepAlive yes`(OpenSSH の既定)でも一応生存確認は行われるが、TCP keepalive の開始までの遊休時間は OS 設定(Linux では `net.ipv4.tcp_keepalive_time`、既定 7200 秒=2 時間)に従うため、**数分で切る NAT には間に合わない**。短い idle タイムアウトを越えてセッションを維持したいなら、秒単位で効く `ServerAliveInterval` のほうが適切。 ```bash # 参考: TCP keepalive の開始遊休時間(秒)を確認 $ sysctl net.ipv4.tcp_keepalive_time ``` ```output net.ipv4.tcp_keepalive_time = 7200 ``` ::: tip 公式マニュアル(`man ssh_config` / `man sshd_config`)は、ServerAlive 系メッセージが「暗号化チャネル経由で送られるためスプーフできない」のに対し、`TCPKeepAlive` は「スプーフ可能」だと明記している。セキュリティと制御性の両面で SSH 層 keepalive が優先される。 ::: ## 設定しても切れるときは何を確認するか? {#still} > **結論**: keepalive を入れても切れるなら、①間にある NAT / LB の idle タイムアウトが keepalive 間隔より短い、②回線そのものが瞬断している、③サーバ側の別タイムアウト(PAM・ロードバランサのハードリミット)が効いている、のいずれか。keepalive 間隔をその idle 値より**確実に短く**するのが基本。 keepalive が効かないときの典型を順に潰す。 ```bash # 接続が NAT のどの経路を通っているか・経路途中で詰まっていないかの確認 $ ssh -v user@server 2>&1 | grep -iE 'alive|disconnect|timeout' ``` - **NAT / ファイアウォールの idle が短い**: 家庭用ルータやクラウド LB は idle タイムアウトが 60〜350 秒程度のことがある。`ServerAliveInterval` をその値より明確に小さく(例: LB が 60 秒なら `30`)する - **回線の瞬断**: 無線 / モバイル回線では物理的に切れる。keepalive では救えないので、後述の tmux / mosh で**切断に耐える**構成にする - **サーバ側の別リミット**: ロードバランサや踏み台のセッション上限、PAM の `pam_exec` 等が固定時間で切ることがある。`journalctl -u ssh` でサーバ側の切断理由を確認する ```bash # サーバ側ログで切断理由を確認 $ sudo journalctl -u ssh -n 100 --no-pager ``` クラウド(AWS NLB / GCP 等)のロードバランサ経由では、LB 自体の idle タイムアウト(AWS NLB は既定 350 秒)が支配的になる。keepalive 間隔は必ずこの値より短くする。 ::: warning keepalive 間隔が idle タイムアウトと**同じか長い**と意味がない。「LB 60 秒だから ServerAliveInterval 60」では境界でこぼれる。**半分以下**(この例なら 30 以下)を目安にする。 ::: ## 切断に耐えるセッションには何を使うか? {#resilience} > **結論**: そもそも切れても作業が飛ばないようにするのが本質的な対策。サーバ上で `tmux` / `screen` を使えば、SSH が切れてもセッションは残り、再接続して `attach` で戻れる。回線が不安定なら、再接続を自動でこなす `mosh` が有効。 keepalive は「切らさない」対策、tmux / mosh は「切れても困らない」対策で、併用するのが堅い。 ```bash # サーバ側で tmux セッションを開始 $ tmux new -s work # SSH が切れたら再接続して元のセッションに戻る $ ssh user@server $ tmux attach -t work ``` 長時間バッチや危険なコマンドは tmux / screen 内で実行しておけば、回線断でプロセスが道連れに死ぬのを防げる。`scp` / `rsync` での大容量転送が途中で切れて困る場合は、再開可能な `rsync` を使う([ファイル転送の基本](/articles/tutorials/scp-rsync-basics) 参照)。 ::: tip `mosh`(mobile shell)は UDP ベースで、IP が変わっても・回線が一時的に切れても自動で復帰する。モバイルや不安定な回線での運用に向く。ただしサーバ側に mosh のインストールと UDP ポート開放が必要。 ::: ## まとめとチェックリスト {#checklist} > **結論**: 「idle で切れる」なら client の `ServerAliveInterval` か server の `ClientAliveInterval` で keepalive を流すのが第一手。間にある NAT / LB の idle より短い間隔にし、切れても困らないよう tmux / mosh を併用すれば、SSH の突然切断はほぼ解消する。 - [ ] 放置して切れるのか、操作中でも切れるのかを切り分けたか(idle か回線かの二分) - [ ] client の `~/.ssh/config` に `ServerAliveInterval` / `ServerAliveCountMax` を設定したか - [ ] server を触れるなら `/etc/ssh/sshd_config` に `ClientAliveInterval` を設定し、`sshd -t` → `reload` したか - [ ] keepalive 間隔を NAT / LB の idle タイムアウトより**確実に短く**したか - [ ] 設定変更時に作業用 SSH をもう 1 本開いてロックアウトに備えたか - [ ] 不安定回線では tmux / screen / mosh で切断に耐える構成にしたか - [ ] それでも切れるなら `journalctl -u ssh` でサーバ側の切断理由を確認したか ## 次に読む {#next} - [SSH で接続できないときのチェックリスト](/articles/troubleshooting/ssh-troubleshooting) - [「Connection timed out」の切り分け](/articles/troubleshooting/connection-timed-out) - [ufw でSSHが繋がらない時](/articles/troubleshooting/ufw-ssh-troubleshooting) # SSH で接続できないときのチェックリスト(known_hosts / 鍵 / Permission denied) Source: https://penguin-gym-linux.com/articles/troubleshooting/ssh-troubleshooting ## この記事で解決できること {#intro} - SSH で接続できないときに、原因を順番に切り分けできます - `Permission denied (publickey)` や `Host key verification failed` の対処が分かります - 鍵ファイル・権限・known_hosts・sshd 設定の「新人が詰まりやすい箇所」を避けられます ::: tip **結論(最短)**: まずはこの順番で確認すると迷子になりにくいです。 1. **接続先とポートが合っているか**(DNS/IP、ポート22以外も) 2. **鍵で失敗しているのか**(`Permission denied (publickey)`) 3. **known_hosts で止まっているのか**(`Host key verification failed`) 4. **サーバ側(sshd)が生きているか**(`systemctl status ssh` / ログ) ::: ::: warning **前提(対象環境)** - クライアント: Ubuntu(または macOS 等でも考え方は同じ) - サーバ: Ubuntu(OpenSSH server) - 権限: 必要に応じて `sudo`(サーバ側確認) ::: ## 1. まず接続コマンドを明確にする(基本形) {#basic} > **結論**: ssh接続はuser/host/portの3点を明確にしてから-vで詳細ログを出して原因を絞り込むのが基本形。 ```bash $ ssh user@server.example.com ``` ポートを指定する場合(例: 2222): ```bash $ ssh -p 2222 user@server.example.com ``` 鍵を指定する場合: ```bash $ ssh -i ~/.ssh/id_ed25519 user@server.example.com ``` ## 2. "まずこれ"で原因の手がかりを出す(-v) {#debug} > **結論**: ssh -vで詳細ログを出すと認証・ネットワーク・known_hostsのどの段階で失敗しているかが判明する。 エラーの切り分けは、詳細ログを出すのが最短です。 ```bash $ ssh -v user@server.example.com ``` さらに詳細(必要なときだけ): ```bash $ ssh -vv user@server.example.com ``` ## ケースA: Permission denied (publickey) {#caseA} > **結論**: 鍵ファイルの存在・権限(秘密鍵600・.ssh 700)・authorized_keysへの登録を順番に確認して鍵認証を通す。 **意味**: サーバは到達しているが、鍵認証で拒否されている。 ### 確認1: 鍵ファイルが存在するか {#caseA-check1} ```bash $ ls -la ~/.ssh ``` ### 確認2: 鍵ファイルの権限(これが悪いと鍵が使えない) {#caseA-check2} 一般的な目安: - 秘密鍵: `600` - `.ssh` ディレクトリ: `700` ```bash $ chmod 700 ~/.ssh $ chmod 600 ~/.ssh/id_ed25519 ``` ### 確認3: どの鍵を使っているか明示する {#caseA-check3} ```bash $ ssh -i ~/.ssh/id_ed25519 user@server.example.com ``` ### 確認4: サーバ側に公開鍵が登録されているか {#caseA-check4} サーバ側で確認(ログインできる別経路がある場合や、コンソールから): ```bash $ ls -la /home/user/.ssh $ ls -la /home/user/.ssh/authorized_keys ``` 権限の目安: ```bash $ chmod 700 /home/user/.ssh $ chmod 600 /home/user/.ssh/authorized_keys $ chown -R user:user /home/user/.ssh ``` ::: tip 公開鍵が `authorized_keys` に無いと、正しい鍵でも通りません。 ::: ## ケースB: Host key verification failed {#caseB} > **結論**: サーバ再構築やIP再利用後のknown_hosts不一致はssh-keygen -Rで該当エントリを削除して再接続する。 **意味**: "そのサーバは本当に前に接続したサーバと同じか?"の検証で止まっています。サーバ再構築や IP 再利用でよく起きます。 **対処(該当ホストの known_hosts エントリを削除)**: ```bash $ ssh-keygen -R server.example.com ``` IP で接続している場合: ```bash $ ssh-keygen -R 203.0.113.10 ``` その後、再接続して指紋を確認のうえ受け入れます。 ## ケースC: Connection timed out {#caseC} > **結論**: timed outはネットワーク的に到達できていないためDNS解決とポート疎通をncで確認しFWやSGを疑う。 **意味**: ネットワーク的に到達できていない(FW/SG/ルーティング/ポート違いの可能性)。 ### 確認1: DNS が引けているか {#caseC-check1} ```bash $ dig server.example.com +short ``` ### 確認2: ポートが開いているか(22以外も) {#caseC-check2} `nc` がある場合: ```bash $ nc -vz server.example.com 22 ``` ポートが違う場合: ```bash $ nc -vz server.example.com 2222 ``` ::: warning `timed out` は「そもそも繋がってない」ので、鍵以前の問題です。 ::: ## ケースD: Connection refused {#caseD} > **結論**: Connection refusedはsshdが起動していないかポートが違うためsystemctl status sshとss -lntpで確認する。 **意味**: サーバに到達しているが、SSH がそのポートで待ち受けていない可能性が高い。 ### 確認1: sshd サービスが動いているか {#caseD-check1} Ubuntu では service 名は `ssh` のことが多いです。 ```bash $ sudo systemctl status ssh ``` 起動: ```bash $ sudo systemctl start ssh ``` 自動起動: ```bash $ sudo systemctl enable ssh ``` ### 確認2: 待受ポートを確認 {#caseD-check2} ```bash $ sudo ss -lntp | grep ssh ``` ## 4. サーバ側ログで原因を確定する(journalctl) {#serverlog} > **結論**: journalctl -u ssh -n 200でサーバ側ログを確認すると鍵不一致やユーザー違いなど認証失敗の理由が特定できる。 サーバ側でログを見ると、ほぼ確定できます。 ```bash $ sudo journalctl -u ssh -n 200 ``` 直近だけ追う: ```bash $ sudo journalctl -u ssh -f ``` `Permission denied` の理由(鍵が違う/ユーザー違い等)がログに出ることがあります。 ::: warning **事故らないための注意点** - 知らない相手の Host Key を雑に受け入れない(中間者攻撃のリスク) - 鍵の権限は厳しめが基本(秘密鍵が `600` でないと拒否されることがあります) - サーバ再構築した場合は known_hosts の不一致が起きやすい ::: ## まとめ {#conclusion} - 接続できないときは `-v` で詳細ログを確認するのが最初の一手 - `Permission denied` → 鍵・権限・authorized_keys を確認 - `Host key verification failed` → `ssh-keygen -R` でエントリ削除 - `Connection timed out` → ネットワーク・FW を確認 - `Connection refused` → sshd が動いているか `systemctl status ssh` で確認 関連記事: - [権限エラーの解決方法](/articles/troubleshooting/permission-denied-fix) - [ufw でブロックされている場合](/articles/troubleshooting/ufw-ssh-troubleshooting) # 「certificate verify failed」の解決 - CA証明書とSSL検証 Source: https://penguin-gym-linux.com/articles/troubleshooting/ssl-certificate-verify-failed ## この記事で解決できること {#intro} - 「certificate verify failed」「unable to get local issuer certificate」の **本当の原因** が分かる - `openssl s_client` で **3 系統のどれが原因か** を即切り分けできる - CA 証明書・チェーン不備・時刻ズレを **正しい手順で直せる** ::: tip **結論(切り分けの型)** 原因は次の 3 系統のどれか。 1. **クライアント側**: CA 証明書バンドルが古い / 欠けている → `update-ca-certificates` 2. **サーバ側**: 中間証明書を送っていない(チェーン不完全)→ サーバ設定を fullchain に 3. **環境側**: システム時刻がズレて有効期限を誤判定 → `timedatectl` で同期 切り分けの起点は `openssl s_client` の **`Verify return code`**。 ::: ::: warning **前提(対象環境)** - OS: Ubuntu / Debian 系(RHEL 系は適宜読み替え。後述) - TLS 検証を行うクライアント(curl / wget / Python / git 等)でエラーが出ている ::: ## certificate verify failed とは何か? {#what} > **結論**: クライアントがサーバ証明書を、信頼するルート CA まで辿れなかった状態。証明書が「偽物」とは限らず、検証材料が足りていないことが多い。 TLS 接続では、クライアントはサーバから受け取った証明書を **ルート CA まで連鎖(チェーン)させて検証** する。このチェーンが繋がらない、あるいは有効期限・ホスト名が一致しないと検証は失敗する。 ツールによってメッセージは異なるが、中身は同じ検証失敗である。 ```output curl: (60) SSL certificate problem: unable to get local issuer certificate ``` ```output ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1006) ``` ::: tip `unable to get local issuer certificate` は「発行元(issuer)の証明書が見つからない」という意味。**チェーン不備か CA バンドル不足** を強く示唆する。 ::: ## まず原因を切り分けるには? {#triage} > **結論**: `openssl s_client` で実際のチェーンと `Verify return code` を見る。番号がどの系統の問題かを一意に指す。 ブラウザや curl の前に、まず生の TLS ハンドシェイクを観測する。 ```bash openssl s_client -connect example.com:443 -servername example.com ``` `-servername` は SNI を指定する(バーチャルホスト環境で必須)。出力末尾の `Verify return code` を確認する。 | return code | 意味 | 主な原因 | | --------------------------------------------------- | -------------------- | ---------------------------------------- | | `0 (ok)` | 検証成功 | クライアント側 CA バンドルの問題(後述) | | `20 (unable to get local issuer certificate)` | 発行元が辿れない | CA バンドル不足 | | `21 (unable to verify the first certificate)` | チェーンが繋がらない | サーバが中間証明書を送っていない | | `10 (certificate has expired)` | 期限切れ | 証明書失効 or 時刻ズレ | | `9 (certificate is not yet valid)` | まだ有効でない | ほぼ時刻ズレ | | `19 (self signed certificate in certificate chain)` | 自己署名 | 社内 CA / プロキシ | ::: warning `openssl s_client` が `0 (ok)` を返すのに curl や Python だけ失敗する場合、**OS の CA は正常でクライアント固有のバンドル**(後述の Python certifi 等)が原因。切り分けの分岐点になる。 ::: チェーンの中身は次でも確認できる。 ```bash openssl s_client -connect example.com:443 -servername example.com -showcerts /dev/null \ | openssl x509 -noout -dates -subject -issuer ``` ```output notBefore=Apr 1 00:00:00 2026 GMT notAfter=Jun 30 23:59:59 2026 GMT subject=CN=example.com issuer=CN=Example Intermediate CA ``` ## CA証明書が古い・欠けている場合は? {#ca-certificates} > **結論**: クライアント側の CA バンドルを更新する。Debian 系は `ca-certificates` を入れて `update-ca-certificates` を実行する。 `Verify return code: 20` で、サーバ証明書自体は正規 CA 発行の場合、ローカルの信頼ストアが古い。 ```bash sudo apt update sudo apt install --reinstall ca-certificates sudo update-ca-certificates ``` ```output Updating certificates in /etc/ssl/certs... 3 added, 0 removed; done. ``` 社内 CA など独自のルート証明書を信頼させたい場合は、PEM 形式(拡張子 `.crt`)を所定ディレクトリに置いて再生成する。 ```bash sudo cp company-root-ca.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates ``` ::: warning `/usr/local/share/ca-certificates/` に置くファイルは **拡張子 `.crt`・PEM 形式が必須**。`.pem` や DER 形式は取り込まれない。DER の場合は `openssl x509 -inform der -in ca.der -out ca.crt` で変換する。 ::: ### RHEL / CentOS / Fedora 系の場合 ディレクトリとコマンドが異なる。 ```bash sudo cp company-root-ca.crt /etc/pki/ca-trust/source/anchors/ sudo update-ca-trust ``` ## サーバの証明書チェーンが不完全な場合は? {#chain} > **結論**: `Verify return code: 21` はサーバ側の設定ミス。中間証明書を含む fullchain を配信させるのが正攻法。クライアント側で回避すべきではない。 `s_client` の出力で `Certificate chain` に **サーバ証明書しか並んでいない**(中間 CA が無い)なら、サーバが中間証明書を送っていない。多くのブラウザは中間証明書をキャッシュ・補完するため「ブラウザでは見えるのに curl で落ちる」が起きる。 正しい対処は **サーバ側で fullchain を配信** すること。 - Nginx: `ssl_certificate` にサーバ証明書 + 中間証明書を連結した `fullchain.pem` を指定する - Apache: `SSLCertificateFile` に fullchain を指定(または `SSLCertificateChainFile` で中間証明書を指定) - Let's Encrypt(certbot): `cert.pem` ではなく `fullchain.pem` を使う 検証はブラウザではなく外部チェッカー(SSL Labs 等)か、別ホストからの `openssl s_client` で行う。 ::: danger サーバを直せない事情があっても、**クライアントで検証を無効化(`-k` / `verify=False`)して放置するのは禁物**。中間者攻撃を検知できなくなる。一時切り分け以外で使わない。 ::: ## システム時刻のズレが原因の場合は? {#clock} > **結論**: `certificate has expired` / `not yet valid` が出て証明書自体は有効なら、システム時刻を疑う。`timedatectl` で同期状態を確認する。 証明書の有効期限(notBefore / notAfter)は **システム時刻と比較** して検証される。コンテナや復帰直後の VM で時刻が大きくズレると、有効な証明書でも `expired` や `not yet valid` と判定される。 ```bash timedatectl ``` ```output Local time: Fri 2026-06-05 12:00:00 UTC Universal time: Fri 2026-06-05 12:00:00 UTC System clock synchronized: yes NTP service: active ``` `System clock synchronized: no` や明らかな日付ズレがあれば NTP を整える。 ```bash sudo timedatectl set-ntp true ``` 時刻同期の詳細は [サーバ時刻ズレの対処](/articles/troubleshooting/ntp-time-skew) を参照。 ## 自己署名・期限切れ証明書への対処は? {#self-signed} > **結論**: 自己署名(return code 19/18)は、その CA を明示的に信頼ストアへ追加する。検証無効化は一時切り分けに限定する。 開発環境や社内プロキシでは自己署名証明書が使われる。正攻法は **その証明書(または発行元 CA)を信頼ストアに追加** すること(前述の `update-ca-certificates` 手順)。 どうしても一時的に検証をスキップする場合のみ、影響範囲を理解した上で使う。 ```bash # 一時切り分け限定。常用しない curl -v https://internal.example.com # まず原因を見る curl -k https://internal.example.com # 検証スキップ(危険) ``` ::: warning `-k`(`--insecure`)や `verify=False` は **暗号化は維持するが相手の正当性を検証しない**。盗聴は防げても、なりすまし(MITM)を防げない。本番・認証情報を送る通信では絶対に使わない。 ::: ## ツール別の対処(curl / Python / git) {#tools} > **結論**: OS の CA を直しても Python だけ失敗するのは certifi 別バンドルが原因。ツールごとに参照する CA ストアが違う点を押さえる。 ### curl / wget OS の CA ストアを参照する。一時的に特定 CA を指定する場合: ```bash curl --cacert /path/to/ca.pem https://example.com wget --ca-certificate=/path/to/ca.pem https://example.com ``` ### Python(requests / urllib) `requests` は OS ではなく **同梱の `certifi` バンドル** を参照する。OS の CA を更新しても直らない典型例。 ```bash # requests が見ている CA バンドルの場所を確認 python3 -c "import certifi; print(certifi.where())" ``` 特定の CA を使わせる場合は環境変数で指定する。 ```bash export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt ``` ::: tip `SSL_CERT_FILE` は Python 標準の `ssl` モジュール(OpenSSL)が参照する。`REQUESTS_CA_BUNDLE` は requests 専用。両方を OS のバンドルに向けると、システム CA と挙動を揃えられる。 ::: ### git ```bash # 特定リポジトリのみ CA を指定 git config http.sslCAInfo /path/to/ca.pem # 環境変数でも指定可能 export GIT_SSL_CAINFO=/path/to/ca.pem ``` `git config --global http.sslVerify false` は検証を完全に無効化するため使わない。 ## やってはいけないこと {#antipatterns} > **結論**: 検証の恒久無効化、無関係な CA の大量追加、時刻を手動固定するなどの「とりあえず通す」対処は、後で必ず事故になる。 ::: danger **避けるべき対処** - `curl -k` / `verify=False` / `sslVerify false` を **設定ファイルやスクリプトに常設** する - 原因不明のまま信頼できない CA を信頼ストアに追加する - 時刻ズレを `date -s` で手動固定し、NTP を止める(再発・ログ不整合の元) - サーバ側チェーン不備をクライアント全台で回避する(直すべきはサーバ 1 台) ::: ## まとめと次に読む {#next} - 原因は **クライアント CA / サーバチェーン / 時刻** の 3 系統。`openssl s_client` の `Verify return code` で一意に切り分く - クライアント側は `update-ca-certificates`、サーバ側は fullchain 配信、時刻は `timedatectl` で同期 - Python は certifi 別バンドル、git は `http.sslCAInfo` と、ツールごとに参照先が違う点に注意 - 検証無効化(`-k` 等)は一時切り分け限定。常設しない - [「Connection refused」の切り分け](/articles/troubleshooting/connection-refused) - [DNS名前解決トラブルシューティング](/articles/troubleshooting/dns-troubleshooting) - [サーバ時刻ズレの対処](/articles/troubleshooting/ntp-time-skew) # 「Stale file handle」の対処 - NFSマウントの再接続 Source: https://penguin-gym-linux.com/articles/troubleshooting/stale-file-handle-nfs ## 「Stale file handle」とは何が起きているのか? {#what} > **結論**: NFS クライアントが握っているファイルハンドルが、サーバ側の実体と一致しなくなった状態(`ESTALE`)。パスではなくハンドルで識別する NFS 特有の現象で、サーバ側の変更が引き金になる。 NFS マウント上で `ls` や `cat`、ファイル保存が次のように弾かれる。 ```output ls: cannot access '/mnt/nfs/data': Stale file handle ``` NFS はファイルを**パス**ではなく**ファイルハンドル**で識別する。ハンドルは大まかに次の 3 要素で構成される。 - **fsid** — エクスポートしているファイルシステムの識別子 - **inode 番号** — 対象ファイル / ディレクトリの inode - **generation 番号** — inode が使い回されたときの世代判別 クライアントはマウント時やファイルアクセス時にこのハンドルをキャッシュし、以降の操作で再利用する。サーバ側でハンドルが指す実体が消える・変わると、次回アクセスでカーネルが `ESTALE`(Stale file handle)を返す。 ::: tip **ポイント**: これはディスク障害ではなく「クライアントが古い参照を握り続けている」状態。多くは**クライアント側の再マウントで復旧**する。サーバを触る前に、まずクライアント側を疑う。 ::: ## なぜ Stale file handle が発生するのか? {#why} > **結論**: サーバ側でファイルハンドルの指す実体が変わったときに起きる。「ファイルの削除・再作成」「エクスポート変更」「サーバ再起動での fsid 変化」「バックアップ復元による inode/generation 変化」が四大原因。 代表的な引き金を、現場での遭遇頻度順に挙げる。 - **A. サーバ側でファイル / ディレクトリを削除・再作成**(最頻出)— クライアントが開いている最中に、別経路でファイルが消されて作り直されると、inode / generation が変わりハンドルが失効する - **B. エクスポート設定の変更** — `/etc/exports` を編集して `exportfs -r` した、エクスポートパスやオプションを変えた、といった操作でハンドルの前提が崩れる - **C. サーバ再起動で fsid が変化** — `/etc/exports` に `fsid=` を明示していないと、再起動時にサーバが自動採番する fsid が変わることがあり、既存ハンドルが無効になる - **D. バックアップ / スナップショットからの復元** — 復元でファイルの inode や generation 番号が変わると、同じパスでもハンドルは別物になる ::: warning A と D は「パスは同じなのに中身(inode)が別物にすり替わった」ケース。ファイル名で見ると正常に見えるため原因究明が難しい。`ESTALE` が出たら「サーバ側で実体が入れ替わった可能性」をまず疑う。 ::: ## まず状況を確認する {#diagnose} > **結論**: どの NFS マウントで起きているか、どのプロセスがそのマウントを掴んでいるかを先に特定する。`findmnt` でマウント、`lsof` / `fuser` で利用プロセスを洗い出す。 ### 1. どの NFS マウントか特定する ```bash $ findmnt -t nfs,nfs4 ``` ```output TARGET SOURCE FSTYPE OPTIONS /mnt/nfs 192.168.10.5:/export nfs4 rw,relatime,vers=4.2,... ``` ### 2. ESTALE を再現確認する ```bash $ ls /mnt/nfs ls: reading directory '/mnt/nfs': Stale file handle ``` ### 3. そのマウントを掴んでいるプロセスを洗い出す 再マウントには対象マウントを誰も使っていない状態が必要。掴んでいるプロセスを先に特定する。 ```bash $ lsof +D /mnt/nfs 2>/dev/null $ fuser -vm /mnt/nfs ``` ::: tip 自分のシェルがマウント配下に `cd` しているだけでも busy になる。まず `cd /` でマウント外に出てから再マウントを試す。これだけで `umount` が通ることも多い。 ::: ## どう復旧するのか?(クライアント側) {#recover} > **結論**: 基本は「アンマウント → 再マウント」でハンドルを取り直す。busy で失敗する場合は遅延アンマウント(`umount -l`)、それでも駄目なら強制(`umount -f`)を段階的に試す。 ### 手順 1: 通常のアンマウント+再マウント ```bash $ cd / $ sudo umount /mnt/nfs $ sudo mount /mnt/nfs ``` `/etc/fstab` にエントリがあれば `mount /mnt/nfs` だけで再マウントできる。これで多くのケースは解決する。 ### 手順 2: busy で失敗する場合は遅延アンマウント `target is busy` で `umount` が失敗するときは、参照中のプロセスを止められないか確認したうえで遅延アンマウントを使う。 ```bash $ sudo umount -l /mnt/nfs # lazy: 参照が切れ次第デタッチ $ sudo mount /mnt/nfs ``` `-l`(lazy)はマウントポイントを名前空間から即座に切り離し、実際の解放は最後の参照が消えたタイミングで行う。busy 環境でも再マウントへ進める。 ### 手順 3: サーバ応答が無く固まる場合は強制アンマウント サーバがダウン・到達不能で I/O がハングしている場合は force を使う。 ```bash $ sudo umount -f /mnt/nfs ``` ::: warning `umount -f` は未書き込みデータを失う可能性がある。書き込み中のアプリがある場合は、可能な限り先にそれを停止する。force / lazy は最終手段として段階的に使うこと。 ::: ### 手順 4: 個別ファイルだけ ESTALE の場合 マウント全体ではなく特定のファイル / ディレクトリだけが Stale なら、そのディレクトリから出て入り直すだけで解消することがある。 ```bash $ cd / $ cd /mnt/nfs/data # ハンドルを取り直す ``` それでも残る場合は手順 1〜3 の再マウントに進む。 ## サーバ側で確認すべきこと {#server} > **結論**: クライアント再マウントで直らない、または全クライアントで多発するならサーバ側を疑う。エクスポート状態と fsid の整合性を確認する。 ### エクスポート状態を確認する ```bash $ sudo exportfs -v ``` エクスポートパス・オプションが意図通りか確認する。`/etc/exports` を変更した直後なら、反映漏れがないか `exportfs -ra` で再エクスポートする。 ```bash $ sudo exportfs -ra ``` ### fsid の固定を確認する サーバ再起動のたびに Stale が出るなら、`fsid` の自動採番が変動している可能性が高い。`/etc/exports` で明示固定する。 ```bash # /etc/exports(サーバ側) /export 192.168.10.0/24(rw,sync,fsid=0,no_subtree_check) ``` `fsid=0` は NFSv4 の擬似ルート用。複数エクスポートには一意な数値(`fsid=1`, `fsid=2`, ...)または UUID を割り当てる。変更後は `exportfs -ra` で反映する。 ## 再発を防ぐには? {#prevent} > **結論**: サーバ側で `fsid` を明示固定し、クライアントが使用中のファイルをサーバ直接操作で削除・再作成しない運用にするのが基本。マウントオプションでハングを緩和することもできる。 - **`fsid=` を明示固定する** — 再起動で fsid が変わる事故を防ぐ最も効果的な対策 - **使用中ファイルをサーバ側で直接いじらない** — 削除・置換はクライアント経由か、クライアントが参照していないタイミングで行う - **エクスポート変更はメンテ枠で** — `/etc/exports` 変更や再エクスポートはクライアントの再マウントとセットで計画する - **`soft` / `hard` の選択を理解する** — `hard`(既定)はサーバ復帰まで再試行し続けるためデータ整合性に有利だが、障害時にハングしやすい。`soft` はタイムアウトで諦めるためハングしにくいが書き込みデータを失うリスクがある。用途で選ぶ ::: tip **コピペ用:クライアント側の復旧テンプレ** ```bash # 1. マウント外へ出る cd / # 2. どのマウントで起きているか・誰が掴んでいるか findmnt -t nfs,nfs4 fuser -vm /mnt/nfs # 3. 再マウント(busy なら -l、ハングなら -f) sudo umount /mnt/nfs || sudo umount -l /mnt/nfs sudo mount /mnt/nfs ``` ::: ## まとめ {#summary} - **Stale file handle (`ESTALE`)** は NFS クライアントが古いファイルハンドルを握り続けている状態で、ディスク障害ではない - 原因はサーバ側の**実体変化**(削除・再作成 / エクスポート変更 / fsid 変化 / 復元) - 復旧の基本は**クライアント側の再マウント**。busy なら `umount -l`、ハングなら `umount -f` を段階的に - 全クライアントで多発・再起動のたびに発生するなら**サーバ側の `fsid` 固定**を確認する ## 次に読む {#next} - [「Read-only file system」の対処](/articles/troubleshooting/read-only-file-system) - [ディスクがいっぱいになったときの調べ方](/articles/troubleshooting/no-space-left-on-device) - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) # 「sudo: unable to resolve host」の解決 - hostname と /etc/hosts Source: https://penguin-gym-linux.com/articles/troubleshooting/sudo-unable-to-resolve-host ## 「sudo: unable to resolve host」とは何を示すのか? {#intro} > **結論**: これはエラーではなく**警告**。sudo が起動時に**自ホスト名を解決できなかった**ことを示す。多くの場合コマンド自体は実行されるが、解決のタイムアウトで**毎回数秒待たされる**。原因はほぼ `hostname` と `/etc/hosts` の不一致に集約される。 `sudo` を実行すると、コマンドの出力の前に次のような行が出る。 ```output $ sudo apt update sudo: unable to resolve host myserver: Name or service not known [sudo] password for user: ``` メッセージは「`myserver` というホスト名を解決できない」という意味だ。重要なのは、これが出ても**多くの場合 sudo は最後まで動く**点。つまり「sudo が壊れた」のではなく「自分自身のホスト名が名前解決の経路に登録されていない」状態を sudo が報告しているにすぎない。 ただし副作用がある。sudo はホスト名解決に失敗するまで**リゾルバのタイムアウトを待つ**ため、`sudo` を打つたびに数秒の遅延が発生する。警告自体は無害でも、この遅延が運用上のストレスになる。 ::: warning **前提(対象環境)** - OS: Ubuntu / Debian 系を中心とした一般的な Linux - `/etc/hostname` / `/etc/hosts` を編集できる権限(直前まで sudo が使えていれば問題ない) - ホスト名を変更した直後・クラウドイメージ・コンテナ等で発生しやすい ::: ## なぜ sudo は自ホスト名を解決しようとするのか? {#why} > **結論**: sudo は `sudoers` の **Host 指定のマッチング**と**ログ記録**のために、自分が動いているホスト名を確定させる必要がある。そのため起動時にホスト名→IP の解決を試み、失敗すると警告を出す。 `sudoers` には特定ホストでだけルールを有効にする `Host` / `Host_Alias` という仕組みがある。これを評価するため、sudo は「いま自分はどのホストで動いているか」を知る必要がある。具体的には `gethostname(2)` で取得した名前をリゾルバで解決し、ホストの同定に使う。 ```output # sudoers の Host 指定の例(NIS/集中管理環境で使われる) user ALL=(ALL) ALL # 全ホスト user web01=(ALL) ALL # web01 でだけ有効 ``` 単一サーバ運用では Host 指定を使わないことも多いが、sudo はその有無に関わらず起動時に解決を試みる。さらに syslog へ「どのホストで誰が何を実行したか」を記録する用途でもホスト名を参照する。だから**ホスト名そのものが解決できない**と、本処理の前に警告が出る。 ::: tip この警告は sudo の**設計上の副作用**であって、ネットワークやインターネット接続とは無関係。オフラインのマシンでも、自ホスト名さえ `/etc/hosts` で引ければ警告は出ない。 ::: ## 原因はどこにあるのか? {#causes} > **結論**: 原因は「`/etc/hostname`(現在のホスト名)」と「`/etc/hosts`(静的名前解決テーブル)」の**食い違い**にほぼ限定される。ホスト名を変えたのに `/etc/hosts` を更新していない、というのが典型。 自ホスト名の解決は、通常まず `/etc/hosts` を見にいく(`/etc/nsswitch.conf` の `hosts:` 行で `files` が先頭にあるため)。ここに現在のホスト名のエントリが無いと、次に DNS へ問い合わせ、そこでも引けずに失敗する。原因を整理すると次のとおり。 | 原因 | 起きやすい状況 | 確認の起点 | | ------------------------------------- | ------------------------------------------ | ------------------------ | | ホスト名変更後 `/etc/hosts` 未更新 | `hostnamectl set-hostname` だけ実行した | `cat /etc/hosts` | | `/etc/hosts` の自ホスト行が消えている | クラウドイメージ / cloud-init / 手編集ミス | `getent hosts NAME` | | `/etc/hostname` を書き換えただけ | 再起動後にホスト名だけ変わり hosts と乖離 | `hostname` | | `nsswitch.conf` の `hosts:` 行が不正 | `files` が無い / 順序がおかしい | `cat /etc/nsswitch.conf` | 切り分けは単純で、**「いまのホスト名」と「`/etc/hosts` に書かれている名前」が一致しているか**を見るだけでよい。一致していなければ、それがそのまま原因になる。 ::: warning クラウド / コンテナ環境では、起動ごとにホスト名が動的に振られたり、cloud-init が `/etc/hosts` を管理していることがある。手編集しても再起動で上書きされる場合は、後述の「恒久変更」セクションで管理経路ごと押さえる。 ::: ## どう確認するのか? {#diagnose} > **結論**: `hostname` で現在の名前を、`cat /etc/hosts` で登録済みの名前を見比べ、`getent hosts <名前>` で実際に解決できるかを最終確認する。`getent` が空なら、それが警告の直接原因。 まず、いま使われているホスト名を確認する。 ```bash $ hostname ``` ```output myserver ``` 次に `/etc/hosts` の中身を見る。ここに上の名前が含まれているかが核心だ。 ```bash $ cat /etc/hosts ``` ```output 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback ``` この例では `myserver` の行がどこにも無い。`localhost` はあるが、肝心の自ホスト名が登録されていない。最後に、リゾルバ経由で実際に解決できるかを `getent` で確認する。 ```bash $ getent hosts myserver ``` ```output (何も出力されない = 解決できていない) ``` `getent hosts <名前>` が**何も返さない**なら、その名前は名前解決の経路上どこにも存在しない。これが `sudo: unable to resolve host` の直接の引き金だ。逆に、正常なホストでは次のように IP とともに表示される。 ```output $ getent hosts web01 127.0.1.1 web01 ``` ::: tip `getent hosts` は `/etc/nsswitch.conf` の `hosts:` 行の設定に従って `files`(=/etc/hosts)→ DNS の順で解決を再現する。sudo が内部でやっている解決とほぼ同じ経路をたどるため、切り分けに最適。 ::: ## どう直すのか? {#fix} > **結論**: `/etc/hosts` に**現在のホスト名の行を追加**するだけで直る。Debian/Ubuntu 系では `127.0.1.1 ` を `127.0.0.1 localhost` とは別の行として加えるのが慣習。 `hostname` で得た名前を `/etc/hosts` に登録する。直前まで sudo が(遅延付きでも)使えていれば、その sudo で編集できる。 ```bash # 現在のホスト名を変数に取る $ hostname myserver # /etc/hosts を編集(任意のエディタで) $ sudo nano /etc/hosts ``` 編集後の `/etc/hosts` は次のようにする。`127.0.1.1` の行を追加した点に注目。 ```output 127.0.0.1 localhost 127.0.1.1 myserver ::1 localhost ip6-localhost ip6-loopback ``` 保存したら、すぐに解決できるか確認する。 ```bash $ getent hosts myserver ``` ```output 127.0.1.1 myserver ``` IP とホスト名が返れば修正完了だ。以降の `sudo` は警告も遅延も出なくなる。設定はファイルに書いた瞬間に有効で、再起動やサービス再起動は不要。 ::: warning **なぜ `127.0.0.1` ではなく `127.0.1.1` なのか**: Debian の慣習で、FQDN を持つホストでは `localhost`(`127.0.0.1`)と自ホスト名(`127.0.1.1`)を**別の行に分ける**。同じ行に同居させると、一部のソフトが「自ホスト名 = localhost」と解釈して誤動作することがあるため。固定 IP を割り当てている場合はその実 IP を使ってもよいが、DHCP 環境では `127.0.1.1` が無難。 ::: ::: tip ワンライナーで追記したい場合は次のようにする。ただし二重登録を避けるため、先に `grep` で存在確認するのが安全。 ```bash $ grep -q "$(hostname)" /etc/hosts || echo "127.0.1.1 $(hostname)" | sudo tee -a /etc/hosts ``` ::: ## ホスト名を恒久的に変更するには? {#hostnamectl} > **結論**: ホスト名を変えるときは `hostnamectl set-hostname` と `/etc/hosts` の更新を**必ずセットで**行う。片方だけだと、まさにこの警告を自分で作り込むことになる。 ホスト名そのものを変更したい(それが原因で警告が出ている)場合は、systemd の `hostnamectl` を使う。これは `/etc/hostname` を書き換え、稼働中のシステムにも即時反映する。 ```bash # 新しいホスト名に変更 $ sudo hostnamectl set-hostname web01 # 反映を確認 $ hostnamectl status ``` ```output Static hostname: web01 Icon name: computer-vm ... ``` `hostnamectl` は `/etc/hostname` は更新するが、`/etc/hosts` は**自動では更新しない**。そのため変更後は `/etc/hosts` の旧ホスト名を新しい名前に直す。 ```output 127.0.0.1 localhost 127.0.1.1 web01 ::1 localhost ip6-localhost ip6-loopback ``` 最後に新しい名前で解決できることを確認する。 ```bash $ getent hosts "$(hostname)" ``` ```output 127.0.1.1 web01 ``` cloud-init を使う環境では `/etc/cloud/cloud.cfg` の `preserve_hostname: true` を設定しないと再起動でホスト名が戻ることがある。手編集が消える場合はこの管理経路を疑う。 ::: tip 変更直後に `sudo` を試すと、シェルがキャッシュした古い認証情報で混乱することがある。`sudo -k` で認証キャッシュを破棄してから試すと、純粋に名前解決だけの問題かを切り分けやすい。 ::: ## それでも直らないときのチェックリスト {#checklist} > **結論**: 警告が消えないなら、`hostname` と `/etc/hosts` の文字列が**完全一致**しているか、`nsswitch.conf` の `hosts:` 行に `files` があるかを順に疑う。タイポと全角・余分な空白が定番の落とし穴。 - [ ] `hostname` の出力と `/etc/hosts` のエントリが**1 文字も違わず**一致しているか - [ ] `/etc/hosts` に `127.0.1.1 `(または実 IP)の行を追加したか - [ ] `getent hosts "$(hostname)"` が IP を返すようになったか - [ ] `/etc/nsswitch.conf` の `hosts:` 行の先頭に `files` があるか(`hosts: files dns`) - [ ] FQDN を使う場合、`127.0.1.1 web01.example.com web01` のように FQDN と短縮名を併記したか - [ ] クラウド / コンテナで再起動後に戻るなら cloud-init の `preserve_hostname` を確認したか - [ ] 編集後に `sudo -k` で認証キャッシュを破棄して再確認したか ## 次に読む {#next} - [DNSが引けない/遅い:dig/nslookup と resolv.conf/resolvectl の基本](/articles/troubleshooting/dns-troubleshooting) - [Permission denied の直し方 - Linux での原因切り分け(chmod / chown / sudo)](/articles/troubleshooting/permission-denied-fix) - [「Host key verification failed」の解決 - known_hosts の扱い](/articles/troubleshooting/host-key-verification-failed) # swap スラッシング(thrashing)の診断と対処 - メモリ逼迫で遅い Source: https://penguin-gym-linux.com/articles/troubleshooting/swap-thrashing ## swap スラッシング(thrashing)とは何が起きているのか? {#intro} > **結論**: 物理メモリが足りず、カーネルがページを swap(ディスク)へ追い出し/読み戻しを**絶え間なく繰り返す**状態。CPU はほぼアイドルなのにディスク I/O だけが飽和し、システム全体が極端に遅くなる。原因は「メモリ不足」だが、症状は「ディスクが遅い」「load average だけ高い」に化けて見える。 メモリが逼迫すると、Linux は使用頻度の低いページを swap 領域へ退避(swap-out)してメモリを空ける。ここまでは正常な動作だ。問題は、退避したページがすぐに必要になり、読み戻し(swap-in)→ 別ページを追い出し → またすぐ必要に…という循環に陥ったとき。これがスラッシング(thrashing)で、CPU は本来の計算ではなく**ページの出し入れの待ち**に時間を費やす。 ```output $ uptime 14:32:10 up 8 days, 3:11, 2 users, load average: 18.40, 16.92, 12.05 ``` load average は跳ね上がるのに `top` の CPU 使用率(us/sy)は低く、`wa`(I/O 待ち)ばかりが高い。「CPU は暇なのに遅い」というちぐはぐな症状がスラッシングの典型だ。 ::: warning **前提(対象環境)** - OS: Ubuntu / 一般的な Linux - 症状: サーバが急激に遅くなる・反応が返らない・SSH すら重い - swap が有効(`free` の Swap 行が 0 でない) - `vmstat` / `free` / `/proc` を参照できる前提(恒久設定は `sudo` 必須) ::: ## なぜ swap 使用量ではなく「入れ替え速度」を見るのか? {#why-rate} > **結論**: swap が使われていること自体は問題ではない。アイドルなプロセスのページが swap に置かれたまま静かに眠っているのは健全。危険なのは swap-in / swap-out が**継続的に高速で流れている**状態で、これだけが体感の遅さに直結する。判断は使用量(`free`)ではなく速度(`vmstat` の si/so)で行う。 `free -h` で Swap が埋まっていても、それが「過去に追い出されてそのまま」なら無害だ。逆に Swap 使用量が中程度でも、毎秒大量に出し入れしていればシステムは止まったように遅くなる。 | 観点 | 健全 | スラッシング | | --------------------- | ------------ | ---------------------- | | swap 使用量(`free`) | 多くても安定 | 増減を激しく繰り返す | | si/so(`vmstat`) | ほぼ 0 | 継続的に大きい | | CPU | 通常通り | `wa`(I/O 待ち)が高い | | 体感 | 問題なし | 全操作が遅い | ::: tip スラッシングの一次指標は「swap がいくつ埋まっているか」ではなく「いま毎秒どれだけ出し入れしているか」。最初に見るべきは `vmstat` の `si` / `so` 列だと覚えておく。 ::: ## まず何で観測するのか?(vmstat / free) {#observe} > **結論**: `vmstat 1` を実行し、`si`(swap-in KB/s)と `so`(swap-out KB/s)が継続的に大きい値を示すならスラッシング確定。`free -h` で残量を、`top` で `wa` の高さを併せて確認する。 最初に `vmstat` を 1 秒間隔で流す。最初の 1 行は起動以降の平均だが、2 行目以降は直近 1 秒の状況なので、si/so の「流れ」が見える。 ```bash $ vmstat 1 ``` ```output procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 2 14 2087600 51200 10240 102400 4096 5120 4200 5300 3100 8900 3 4 8 85 0 1 12 2091200 48300 10100 101800 3800 4900 3900 5100 2980 8500 2 3 10 85 0 ``` `si` / `so` が毎秒数 MB 規模で流れ続け、`wa`(I/O 待ち)が 80% 超。`b`(ブロック中プロセス数)も多い。これがスラッシングの動かぬ証拠だ。次に残量を見る。 ```bash $ free -h ``` ```output total used free shared buff/cache available Mem: 3.8Gi 3.5Gi 120Mi 12Mi 180Mi 90Mi Swap: 2.0Gi 1.9Gi 80Mi ``` `available` が極端に少なく、Swap もほぼ満杯。物理メモリが枯渇し swap に逃がしきれなくなっている。`top` でも裏取りする。 ```bash $ top -bn1 | head -5 ``` ```output top - 14:32:40 up 8 days, load average: 18.40, 16.92, 12.05 Tasks: 210 total, 1 running, 209 sleeping %Cpu(s): 3.0 us, 4.0 sy, 0.0 ni, 8.0 id, 85.0 wa, 0.0 hi, 0.0 si MiB Mem : 3891.0 total, 120.0 free, 3591.0 used, 180.0 buff/cache MiB Swap: 2048.0 total, 80.0 free, 1968.0 used ``` `wa` が支配的で `id`(アイドル)はわずか。CPU は計算ではなく I/O 完了を待っている。 ::: warning `sar`(`sysstat` パッケージ)が使えるなら、`sar -W` の `pswpin/s` `pswpout/s`(スワップイン/アウト)や `sar -B` の `pgpgin/s` `pgpgout/s`(ページング)の履歴で「いつから始まったか」を遡れる。リアルタイムの `vmstat` と過去ログの `sar` を併用すると発生時刻を特定しやすい。 ::: ## どのプロセスが swap を食っているのか? {#culprit} > **結論**: プロセス別の swap 使用量は `smem -s swap -r` が最も読みやすい。`smem` が無ければ `/proc//status` の `VmSwap` を集計する。最大の swap 消費プロセスがスラッシングの主因であることが多い。 `smem` はプロセスごとの swap 使用量を直接表示できる(`sudo apt install smem`)。 ```bash $ sudo smem -s swap -r | head ``` ```output PID User Command Swap USS PSS RSS 2314 www-data java -Xmx3g -jar app.jar 1245184 210432 215300 240128 1190 mysql /usr/sbin/mysqld 412300 98200 101400 130560 1532 root /usr/bin/dockerd 120400 40100 42300 61440 ``` `Swap` 列が突出したプロセスが犯人だ。`smem` を入れられない(新規パッケージ導入が憚られる)場合は、`/proc` を直接集計する。 ```bash # 全プロセスの VmSwap を多い順に表示(KB) $ for f in /proc/[0-9]*/status; do awk '/^Name:/{n=$2} /^VmSwap:/{print $2, n, FILENAME}' "$f" done 2>/dev/null | sort -rn | head ``` ```output 1245184 java /proc/2314/status 412300 mysqld /proc/1190/status 120400 dockerd /proc/1532/status ``` メモリ確保量の設定ミス(JVM の `-Xmx` が物理メモリ超過、DB のバッファプール過大など)が典型的な原因。上限を物理メモリに見合う値へ下げるのが根本対処になることが多い。 ::: tip swap 使用量が多い = 即犯人、とは限らない。「いま激しく出し入れしているプロセス」を見るには、`vmstat` でスラッシング中に `smem` を複数回取り、Swap 値が変動しているプロセスに注目する。静止しているなら眠っているだけのページだ。 ::: ## 今すぐ遅さを止めるには?(応急処置) {#mitigate} > **結論**: 暴走・過大なプロセスを止める/メモリ上限を下げて再起動するのが最短。swap の溜まりをリセットしたいなら `swapoff -a && swapon -a` だが、空きメモリが無い状態での実行は OOM を誘発するため危険。応急処置は「メモリを空ける」方向に限る。 最も安全で効くのは、`smem` で特定した過大プロセスを停止/再設定すること。 ```bash # 原因プロセスを正規の手順で再起動(設定を見直してから) $ sudo systemctl restart myapp.service ``` swap に溜まったページを物理メモリへ戻して「リセット」したい場合は `swapoff` → `swapon` を使う。ただし swap の中身を一旦すべてメモリに載せ直すため、**空き物理メモリが swap 使用量を下回ると OOM killer が発動**する。 ```bash # 危険: 空きメモリが swap 使用量より少ないと OOM を招く $ free -h # available > Swap used を確認してから $ sudo swapoff -a && sudo swapon -a ``` ::: danger スラッシングの最中に安易に `swapoff -a` を叩かない。swap 上のページを全部メモリに戻そうとして、足りなければカーネルがプロセスを kill する。必ず先に原因プロセスを止めて `free` に余裕を作ってから実行する。 ::: ::: warning 「とりあえず再起動」で症状は消えるが、メモリ設定やリークを直さなければ必ず再発する。応急処置の後に必ず原因(過大設定 / リーク / 単純な物理メモリ不足)の特定へ進む。 ::: ## swappiness を調整すべきか? {#swappiness} > **結論**: `vm.swappiness` は「どれだけ積極的に swap を使うか」の傾向値(0〜200、既定 60。100 超は zram/zswap など高速 swap 向け)。下げるとアプリのページを swap に追い出しにくくなり、対話的サーバの体感が改善することがある。ただし**物理メモリ不足そのものは解決しない**。根本対処はメモリの増設か使用量の削減。 現在値を確認する。 ```bash $ cat /proc/sys/vm/swappiness ``` ```output 60 ``` 一時的に下げて挙動を見る(再起動で戻る)。 ```bash # 0 ではなく 10 程度から試すのが無難 $ sudo sysctl -w vm.swappiness=10 ``` 効果を確認できたら恒久化する。 ```bash $ echo 'vm.swappiness = 10' | sudo tee /etc/sysctl.d/99-swappiness.conf $ sudo sysctl --system ``` | swappiness | 傾向 | 向くケース | | --------------- | ----------------------- | ----------------------------- | | 0〜10 | swap をできるだけ避ける | 対話サーバ・低レイテンシ重視 | | 60(既定) | バランス | 汎用 | | 100 | 積極的に swap | バッチ・スループット重視 | | 100 超(〜200) | さらに積極的に swap | zram/zswap など高速 swap 向け | ::: warning swappiness を 0 にしても swap が完全に無効になるわけではない(メモリ逼迫時には依然 swap する)。また下げ過ぎるとファイルキャッシュとのバランスが崩れ、別の遅さを生むことがある。極端値は避け、`vmstat` で si/so が落ち着くかを観測しながら調整する。 ::: ## 特定サービスの swap を cgroup で抑えるには? {#cgroup} > **結論**: systemd 管理下のサービスなら `MemoryMax` / `MemoryHigh` でメモリ上限を課し、1 サービスがホスト全体を巻き込むスラッシングを封じ込められる。上限超過時はそのサービスだけが reclaim / OOM の対象になり、他は守られる。 特定サービス(例: 過大な Java アプリ)にメモリ上限を設定する。 ```bash $ sudo systemctl edit myapp.service ``` `[Service]` セクションに上限を書く。 ```output [Service] MemoryHigh=2G MemoryMax=2.5G ``` `MemoryHigh` は超えると積極的に reclaim される「ソフト上限」、`MemoryMax` は超えると OOM kill される「ハード上限」。保存後に反映する。 ```bash $ sudo systemctl daemon-reload $ sudo systemctl restart myapp.service $ systemctl show -p MemoryMax myapp.service ``` これで該当サービスのメモリ使用が cgroup で頭打ちになり、システム全体のスラッシングへ波及しなくなる。 ::: tip `MemoryHigh` を `MemoryMax` より少し低く設定すると、ハード上限で突然 kill される前に「絞られて遅くなる」緩衝帯ができる。本番では `MemoryMax` 単独より両方指定が安全。 ::: ## 物理メモリ不足が真因なら?(swap 増設・増設判断) {#capacity} > **結論**: 設定ミスもリークも無く、単に常時メモリが足りないならハードの増設(RAM 追加)が本筋。swap を増やすのは「OOM で落ちるよりはマシ」な緩衝に過ぎず、増やしてもスラッシングは速度が遅いまま続く。swap 追加は延命、RAM 追加が解決。 swap ファイルを追加して当座の OOM を避ける手順(クラウド等で即時 RAM 増設できない場合)。 ```bash # 2GB の swap ファイルを作成 $ sudo fallocate -l 2G /swapfile $ sudo chmod 600 /swapfile $ sudo mkswap /swapfile $ sudo swapon /swapfile ``` 恒久化は `/etc/fstab` に追記する。 ```output /swapfile none swap sw 0 0 ``` ::: warning swap を増やしても、メモリが足りない限り出し入れは続き「遅い」ままだ。swap 増設は OOM kill を避ける延命策であって、スラッシングの遅さそのものは解消しない。慢性的にスラッシングするなら、RAM 増設かワークロード削減(プロセスのメモリ上限引き下げ・同時実行数の削減)が正攻法。 ::: ## それでも直らないときのチェックリスト {#checklist} > **結論**: スラッシングは「物理メモリ不足」が確定した状態。`vmstat` で si/so を確認 → `smem` で犯人プロセスを特定 → 過大設定 / リークを直す → 足りなければ RAM 増設、の順で原因はこの層に収束する。swappiness や cgroup は対症であって、根本はメモリ量とのバランス。 - [ ] `vmstat 1` の `si` / `so` が継続的に大きいことを確認したか(使用量ではなく速度) - [ ] `top` で `wa`(I/O 待ち)が高く `us`/`sy` が低いことを確認したか - [ ] `free -h` で `available` と Swap 残量を確認したか - [ ] `smem -s swap -r` または `/proc/*/status` の VmSwap で犯人プロセスを特定したか - [ ] そのプロセスのメモリ設定(JVM `-Xmx`・DB バッファ等)は物理メモリに見合うか - [ ] メモリリークの可能性を排除したか(時間経過で単調増加していないか) - [ ] 応急処置(`swapoff`)の前に `available > Swap used` を確認したか - [ ] 慢性的なら RAM 増設 / 同時実行数削減を検討したか ## 次に読む {#next} - [OOM killer 発動時の対処 - メモリ不足でプロセスが落ちた](/articles/troubleshooting/oom-killer-handling) - [メモリ不足の調べ方:free/top/ps と OOM Killer の見分け](/articles/troubleshooting/memory-troubleshooting) - [load averageが高い時の診断 - CPU待ち・I/O待ちの見極め](/articles/troubleshooting/high-load-average-diagnosis) # 「syntax error near unexpected token」の読み解き方 Source: https://penguin-gym-linux.com/articles/troubleshooting/syntax-error-unexpected-token ## この記事で解決できること {#intro} - `syntax error near unexpected token` の **メッセージの読み方** が分かる - token が指す位置と **本当の原因がずれる理由** を理解できる - `fi` / `then` / `done` / `(` / `newline` / `end of file` など **token 別に原因を切り分け** できる ::: tip **結論(読み解きの型)** `syntax error near unexpected token 'X'` の `X` は「bash がそこで構文として行き詰まった地点」であり、**ミスは多くの場合その手前**にある。 1. **token 名を見て分類する**(`fi`/`then`/`done` なら制御構文、`(` なら括弧・特殊文字、`newline`/`end of file` なら閉じ忘れ) 2. **エラー行とその直前を読む**(token の位置ではなく手前を疑う) 3. **`bash -n script.sh` で構文だけ検査**(実行せず行番号を特定) 4. **改行コード・不可視文字を `cat -A` で確認** ::: ::: warning **前提(対象環境)** - シェル: bash(Ubuntu / Debian 系の既定) - 対象: 自分で書いた `.sh` スクリプト、または対話シェルで打ったコマンド - `/bin/sh`(dash)はメッセージの文言が異なる(後述) ::: ## syntax error near unexpected token とは何か? {#what} > **結論**: bash がコマンドを解析(パース)する段階で、文法上そこに来てはいけない単語(token)に出くわした、というエラー。実行前の「構文チェック」で止まっている状態。 bash はコマンドを実行する前に、まず文法どおりに並んでいるかを解析する。その途中で「ここにこの単語が来るのは文法的におかしい」と判断すると、実行せずにこのエラーを出す。 ```bash $ echo hello world) bash: syntax error near unexpected token `)' ``` ここで `)` が `unexpected token`(予期しない単語)として報告されている。`echo hello world` までは正しく読めたが、対応する `(` が無いまま `)` が現れたため、bash は文法エラーと判断した。 `command not found` が「コマンドは見つからない(が文法は正しい)」エラーなのに対し、`syntax error` は「そもそも文として成立していない」エラーである。実行は一切行われていない。 ## なぜ token の位置と本当の原因がずれるのか? {#parser} > **結論**: bash は左から順に読み進め、「これ以上正しく解釈できない」最初の地点で停止する。報告される token はその停止地点であり、原因(閉じ忘れ・付け忘れ)はその手前にあることが多い。 これがこのエラー最大のハマりどころである。例を見る。 ```bash if [ "$x" -gt 0 ] echo "big" fi ``` ```bash $ bash script.sh script.sh: line 3: syntax error near unexpected token `fi' ``` 報告されたのは 3 行目の `fi` だが、**本当の原因は 1 行目の `then` 忘れ**である。bash は `if [ ... ]` の後に `then` が来るはずだと待っているのに、`echo` が来て、さらに `fi` が来て、ようやく「もう正しく解釈できない」と判断した。だから止まった地点(`fi`)が報告される。 ::: tip **読み方のコツ** token が「閉じ・終端」を表す単語(`)` / `}` / `fi` / `done` / `esac` / `end of file`)のときは、**対応する開き側に問題がある**と疑う。token の位置を直すのではなく、手前の構造を見直す。 ::: ## token が `fi` / `then` / `done` のときは? {#control-flow} > **結論**: 制御構文の区切り(`;` や改行)の付け忘れ、`then` / `do` の欠落、ブロックの閉じ忘れが原因。`if`〜`fi`、`for`〜`done` の対応を確認する。 `if` / `for` / `while` / `case` の構造が崩れていると、これらの予約語が `unexpected token` になる。 ### then の前に区切りが無い `if` の条件と `then` の間には `;` か改行が必要。 ```bash # NG: ] と then の間に区切りが無い if [ "$x" = "1" ] then echo ok fi ``` ```bash $ bash script.sh script.sh: line 3: syntax error near unexpected token `fi' ``` ```bash # OK: ; で区切る(または then を次の行へ) if [ "$x" = "1" ]; then echo ok fi ``` ### done / fi の閉じ忘れ・余り ```bash # NG: do を付け忘れている for f in *.txt echo "$f" done ``` ```bash $ bash script.sh script.sh: line 2: syntax error near unexpected token `echo' script.sh: line 2: ` echo "$f"' ``` `for ... in ...` の後には `do` が必要。`do` が無いと、`in` リストの次に来た行(ここでは 2 行目の `echo`)で文法違反として停止する(`done` まで到達しない)。 ::: warning コピペで一部の行だけ貼り付けたとき、`if` だけ・`do` だけが欠けて構造が片肺になりやすい。ブロックは**開きと閉じをセット**で確認する。 ::: ## token が `(` や記号のときは? {#paren} > **結論**: クォートしていない `(` `)` `&` `;` `|` `<` `>` などのシェル特殊文字が原因。ファイル名や文字列に含まれる記号は引用符で囲む。 `(` `)` はサブシェルや関数定義に使う特殊文字なので、ファイル名や引数に裸で書くと文法エラーになる。 ```bash # NG: ファイル名の () が特殊文字として解釈される $ cp report(final).txt /backup/ bash: syntax error near unexpected token `(' ``` 対処はクォートまたはエスケープ。 ```bash # OK: クォートで囲む $ cp 'report(final).txt' /backup/ # OK: バックスラッシュでエスケープ $ cp report\(final\).txt /backup/ ``` 同じことが `&`(バックグラウンド実行)や `;`(コマンド区切り)でも起きる。 ```bash # NG: & がバックグラウンド指定として解釈される $ echo Tom & Jerry ``` 文字列として渡したい記号は必ず引用符で囲む。 ::: tip **貼り付け時の落とし穴** Web やドキュメントからコピーすると、`"` が全角の `"` `"`(スマートクォート)に化けていることがある。見た目は似ていても bash は別物として扱い、引用が閉じられず syntax error になる。`cat -A` で確認するか、手で打ち直す。 ::: ## token が `newline` や `end of file` のときは? {#eof} > **結論**: クォート・括弧・ヒアドキュメント・制御ブロックの閉じ忘れが原因。bash がファイル末尾まで読んでも「閉じ」が見つからず終端で停止している。 `unexpected token 'newline'` や `unexpected end of file` は「開いたものが閉じられていない」サイン。 ### クォートの閉じ忘れ ```bash $ echo "hello > ``` 開いた `"` が閉じられていないため、bash は次の行を文字列の続きと見なして入力を待ち続ける(`>` プロンプト)。スクリプトなら末尾で停止する。 ```bash echo "hello echo "world" ``` ```bash $ bash script.sh script.sh: line 2: unexpected EOF while looking for matching `"' ``` ### ヒアドキュメントの終端ラベル不一致 ```bash # NG: 終端ラベルが本文 EOF と一致していない(行頭でない / スペース混入) cat < **結論**: bash 専用構文(`[[ ]]`・配列・プロセス置換)を `sh script.sh` で実行すると構文エラーになる。bash で実行するか shebang を `#!/bin/bash` にする。 Ubuntu の `/bin/sh` は dash であり、bash の拡張構文を解釈できない。bash なら通るスクリプトでも `sh` で動かすと止まる。 ```bash # bash 専用の配列・[[ ]] を含むスクリプト arr=(a b c) [[ -n "$1" ]] && echo "$1" ``` ```bash # NG: sh(dash)で実行 $ sh script.sh script.sh: 1: Syntax error: "(" unexpected ``` dash のメッセージは `Syntax error: "(" unexpected` で文言が異なるが、原因は同じ「POSIX sh では未対応の構文」。対処は次のいずれか。 ```bash # 1. bash で明示的に実行する $ bash script.sh # 2. shebang を bash にして直接実行する $ head -1 script.sh #!/bin/bash $ ./script.sh ``` ::: warning `./script.sh` は shebang のインタプリタで動くが、`sh script.sh` は shebang を無視して dash で動く。意図せず `sh` を付けて実行していないか確認する。詳しくは[「bad interpreter」エラーの直し方](/articles/troubleshooting/bad-interpreter-shebang)を参照。 ::: ## CRLF 改行が原因のときは? {#crlf} > **結論**: Windows で編集したスクリプトは行末に `\r`(CR)が混入し、構文エラーや謎の挙動を起こす。`cat -A` で `^M` を確認し `dos2unix` で変換する。 Windows のエディタで保存したスクリプトは改行が CRLF(`\r\n`)になり、各行末に見えない `\r` が付く。これが予約語や引用符にくっつき、構文エラーや `command not found` を誘発する。 ```bash # 行末の ^M が CR の混入を示す $ cat -A script.sh #!/bin/bash^M$ if [ "$x" = "1" ]; then^M$ echo ok^M$ fi^M$ ``` `$` が行末、`^M` が CR。これを LF に変換する。 ```bash # dos2unix があれば最短 $ dos2unix script.sh # 無ければ sed / tr で除去 $ sed -i 's/\r$//' script.sh ``` ::: tip エディタ側で「改行コード: LF」「文字コード: UTF-8」に設定して保存すれば再発を防げる。`.gitattributes` に `*.sh text eol=lf` を書く運用も有効。 ::: ## 切り分けチェックリスト {#summary} > **結論**: 「token 名で分類 → 手前を疑う → `bash -n` で構文検査 → `cat -A` で不可視文字確認」の順にたどれば、syntax error near unexpected token はほぼ特定できる。 上から順に確認する。 - [ ] token 名を確認した(`fi`/`then`/`done` → 制御構文、`(` → 特殊文字、`newline`/`end of file` → 閉じ忘れ) - [ ] token の位置ではなく**その手前の行**を読み直した - [ ] `bash -n script.sh` で構文だけ検査し、報告行を特定した - [ ] `if`〜`fi` / `for`〜`done` の開き・閉じが対応しているか確認した - [ ] クォート・括弧・ヒアドキュメントが閉じているか確認した - [ ] ファイル名や引数の特殊文字を引用符で囲んだ - [ ] `sh script.sh` ではなく `bash script.sh` / `./script.sh` で実行した - [ ] `cat -A` で `^M`(CR)が無いか確認し、あれば `dos2unix` 次に読む記事: - [「bad interpreter」エラーの直し方](/articles/troubleshooting/bad-interpreter-shebang) - [自作スクリプトが「command not found」になる](/articles/troubleshooting/bash-command-not-found-path) - [crontabが動かない時のチェックリスト](/articles/troubleshooting/crontab-not-running) # systemd サービスが起動しない - Failed to start の診断手順 Source: https://penguin-gym-linux.com/articles/troubleshooting/systemd-service-wont-start ## この記事で解決できること {#intro} - `systemctl start` が **Failed to start** で失敗する原因を切り分けられる - `status` と `journalctl` の **どこを読むか** が分かる - exit code・`ExecStart` のパス・権限・依存関係・start-limit の **定番ハマりどころ** を順に潰せる ::: tip **結論(切り分けの型)** `Failed to start` の原因はほぼ次の流れで特定できる。上から順に確認する。 1. **`systemctl status` で状態と Result 行を読む** 2. **`journalctl -xeu` で失敗の生ログを精読する** 3. **exit code を読む**(`203/EXEC` `200/CHDIR` `217/USER` 等は systemd 固有の意味を持つ) 4. **`ExecStart` のパス・実行権限・ユーザー・作業ディレクトリを確認する** 5. **依存・start-limit・unit 編集後の `daemon-reload` 漏れを潰す** ::: ::: warning **前提(対象環境)** - systemd 採用ディストリ(Ubuntu / Debian / RHEL / CentOS / Fedora 等) - サービス名は例として `myapp.service` を使用。自分のサービス名に読み替える - システムサービス(root 管理)を主対象とする。ユーザーサービスは `--user` を付ける ::: ## まず何を見ればいいのか? {#status} > **結論**: 起点は `systemctl status `。`Active:` 行の状態、`Main PID` の exit code、末尾の直近ログ 10 行で当たりがつく。`failed` か `activating (auto-restart)` かで方向が分かれる。 ```bash $ systemctl status myapp ``` ```output × myapp.service - My Application Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled) Active: failed (Result: exit-code) since Fri 2026-06-05 10:00:01 JST; 5s ago Main PID: 12345 (code=exited, status=203/EXEC) CPU: 4ms Jun 05 10:00:01 host systemd[1]: myapp.service: Main process exited, code=exited, status=203/EXEC Jun 05 10:00:01 host systemd[1]: myapp.service: Failed with result 'exit-code'. ``` 読むべき箇所: - **`Loaded:`** — unit ファイルのパスと `enabled` / `disabled`。ここが `not-found` なら unit 自体が見つかっていない。 - **`Active:`** — `failed` なら起動して落ちた。`activating (auto-restart)` なら再起動ループ中。 - **`Result:`** — `exit-code`(プロセスが非0終了)/ `timeout`(起動が間に合わない)/ `signal`(シグナルで死亡)/ `start-limit-hit`(再起動しすぎ)。 - **`status=NNN/NAME`** — exit code。後述のとおり 200 番台は systemd 固有の意味を持つ。 ::: tip `status` の表示は端末幅で末尾ログが切れる。全文は `journalctl` で読む。`Result:` の値が一次的な分岐点になる。 ::: ## journalctl で失敗ログをどう読むか? {#journal} > **結論**: `journalctl -xeu ` が最重要。`-u` でサービス限定、`-e` で末尾へジャンプ、`-x` で systemd の補足説明が付く。アプリ自身が吐いたエラー(`command not found` / `Permission denied` / `bind: address already in use` 等)がここに残る。 ```bash # サービス限定で末尾を読む(最頻出) $ journalctl -xeu myapp # 直近の起動分だけに絞る $ journalctl -u myapp --since "5 min ago" # 今回のブート以降に限定 $ journalctl -b -u myapp ``` `status=203/EXEC` のような systemd 由来コードに対し、アプリ自身のエラーメッセージは journal にしか出ないことが多い。両方を突き合わせる。 ::: warning ログが空・古い場合の注意点: - **`daemon-reload` 漏れ**: unit を編集したのに反映されていない(後述)。 - **時刻ズレ**: `--since` が効かない場合はサーバ時刻を疑う。 - **ユーザーサービス**: `journalctl --user -u myapp` を使う。root の journal には出ない。 ::: ## exit code 203 / 200 / 217 は何を意味するのか? {#exitcode} > **結論**: systemd は起動準備中の失敗に 200〜243 の固有 exit code を割り当てる。代表は `203/EXEC`(実行ファイルが無い・実行権限が無い)、`200/CHDIR`(WorkingDirectory が無い)、`217/USER`(User= のユーザーが存在しない)。アプリが返す一般的な終了コードと区別できる。 `Main PID` 行の `status=NNN/NAME` を読む。200 番台は「アプリが動く前に systemd 側で失敗した」サインで、原因が unit 設定にあることをほぼ確定できる。 | status | 名称 | 典型原因 | | ----------- | ---------- | ---------------------------------------------------- | | `203/EXEC` | EXEC | `ExecStart` のパス誤り / 実行権限なし / shebang 不正 | | `200/CHDIR` | CHDIR | `WorkingDirectory=` のディレクトリが存在しない | | `217/USER` | USER | `User=` で指定したユーザーが存在しない | | `1` 〜 | (アプリ) | アプリ自身が返した一般エラー。journal の本文を読む | ```bash # 203/EXEC の切り分け: パスと実行権限を確認 $ systemctl cat myapp | grep ExecStart ExecStart=/opt/myapp/bin/server --config /etc/myapp.conf $ ls -l /opt/myapp/bin/server # 存在するか / x ビットがあるか $ head -1 /opt/myapp/bin/server # スクリプトなら shebang を確認 ``` ::: tip `ExecStart` の**先頭は絶対パス必須**。`server` のような相対指定・PATH 依存は不可。`command not found` 相当が `203/EXEC` として現れる。 ::: ## unit ファイルの内容と構文をどう確認するか? {#unit} > **結論**: 編集前の元ファイルではなく `systemctl cat ` で「実際に有効な内容」を見る。drop-in(`*.d/*.conf`)の上書きも合算表示される。構文の妥当性は `systemd-analyze verify` で機械的に検査できる。 ```bash # 実際に効いている unit(drop-in 込み)を表示 $ systemctl cat myapp # unit ファイルの構文・参照を検証 $ systemd-analyze verify /etc/systemd/system/myapp.service ``` `systemd-analyze verify` は存在しない設定ディレクティブ、解決できない依存、`ExecStart` の不在などを警告する。出力が無ければ構文上の問題はない。 よくある設定ミス: - **`Type=` の不一致**: フォアグラウンド常駐プロセスなのに `Type=forking` を指定すると、systemd が子プロセスを待ち続けてタイムアウトする。fork してデーモン化しないなら `Type=simple`(既定)にする。 - **`ExecStart` が相対パス**: 前節のとおり絶対パスが必須。 - **環境変数未設定**: 対話シェルの `.bashrc` は読まれない。`Environment=` か `EnvironmentFile=` で明示する。 ::: warning `Type=forking` を使うなら `PIDFile=` を併記するのが安全。指定が無いと systemd が主プロセスを取り違え、`active` 表示なのに実体が落ちている状態になりやすい。確信が無ければ `Type=simple` から始める。 ::: ## unit を編集したのに反映されないのはなぜか? {#daemon-reload} > **結論**: systemd は unit ファイルをメモリにキャッシュする。編集後に `systemctl daemon-reload` を実行しないと旧定義のまま起動する。「直したはずなのに同じエラー」の典型原因。 ```bash $ sudo vim /etc/systemd/system/myapp.service $ sudo systemctl daemon-reload # ← これを忘れると編集が反映されない $ sudo systemctl restart myapp ``` `daemon-reload` 漏れの状態では `systemctl cat` が編集後の内容を表示する一方、起動時の挙動は旧定義のまま、という食い違いが起きる。編集→`daemon-reload`→`restart` を 1 セットで習慣化する。 ::: tip unit ファイルを直接 `vim` する代わりに `systemctl edit myapp`(drop-in 作成)/ `systemctl edit --full myapp`(全体編集)を使うと、保存時に `daemon-reload` 相当が自動で走る。編集漏れ事故を構造的に防げる。 ::: ## 権限・依存・タイムアウトの切り分け {#perm-deps} > **結論**: アプリは動くのにサービスだと落ちる場合、実行ユーザーの権限不足・依存サービスの未起動・起動タイムアウトのいずれかが多い。`User=` 権限、`After=`/`Requires=`、`TimeoutStartSec` を順に確認する。 ### 権限(手動では動くのにサービスで Permission denied) `systemctl start` は `User=`(既定 root)の権限で実行される。手動実行時とユーザーが違えば、ファイル・ポート・ソケットへのアクセスで `Permission denied` になる。 ```bash # サービスの実行ユーザーで手動再現する $ sudo -u myappuser /opt/myapp/bin/server --config /etc/myapp.conf ``` これでエラーが再現すれば原因はアプリ/権限側。再現しなければ unit 設定側を疑う。権限の基礎は[Permission denied の直し方](/articles/troubleshooting/permission-denied-fix)を参照。 ### 依存関係(先に必要なサービスが上がっていない) DB や network-online を必要とするのに起動順が保証されていないと、起動直後に接続失敗で落ちる。 ```ini [Unit] After=network-online.target postgresql.service Wants=network-online.target ``` `After=` は順序のみ、`Requires=`/`Wants=` は依存関係を表す。「接続先がまだ無い」系の失敗はここを見直す。 ### タイムアウト(`Result: timeout`) `status` で `timeout` と出る場合、既定 90 秒以内に「起動完了」と systemd に通知できていない。重い初期化を持つサービスは `TimeoutStartSec=` を延ばすか、`Type=notify` で準備完了を明示通知する。 ## 再起動ループ「start request repeated too quickly」の対処は? {#start-limit} > **結論**: 短時間に規定回数(既定 `StartLimitIntervalSec=10s` 内 `StartLimitBurst=5` 回)失敗すると、systemd は以降の起動を抑止し `start-limit-hit` を表示する。根本原因を直したうえで `systemctl reset-failed` でカウンタを解除する。 ```output myapp.service: Start request repeated too quickly. myapp.service: Failed with result 'start-limit-hit'. ``` このメッセージは**結果であって原因ではない**。本当の原因は直前の失敗ログにある。手順: ```bash # 1) 本当の失敗理由を遡って読む $ journalctl -xeu myapp # 2) 原因(ExecStart / 権限 / 依存 など)を修正 # 3) 失敗カウンタを解除してから起動 $ sudo systemctl reset-failed myapp $ sudo systemctl start myapp ``` ::: warning `reset-failed` は**カウンタを消すだけ**で原因は直さない。先に失敗理由を潰さないと、再び同じループに入って `start-limit-hit` が再発する。順番を守る。 ::: ## 診断チェックリスト {#summary} > **結論**: 「status → journalctl → exit code → unit 内容 → daemon-reload → 権限・依存 → start-limit」の順に上から潰せば、`Failed to start` の原因はほぼ特定できる。 上から順に確認する。 - [ ] `systemctl status myapp` で `Active:` / `Result:` / `status=NNN` を読んだ - [ ] `journalctl -xeu myapp` でアプリ自身のエラーを確認した - [ ] exit code を判定した(`203/EXEC` `200/CHDIR` `217/USER` は unit 設定起因) - [ ] `systemctl cat myapp` で有効な unit を確認、`ExecStart` は絶対パス - [ ] `systemd-analyze verify` で構文を検査した - [ ] unit 編集後に `systemctl daemon-reload` を実行した - [ ] `Type=` がプロセスの挙動(fork するか)と一致している - [ ] `sudo -u ` で手動再現し、権限・依存・タイムアウトを切り分けた - [ ] `start-limit-hit` は原因修正後に `reset-failed` で解除した ## 次に読む {#next} - [journalctl の使い方](/articles/tutorials/journalctl-basics) - [systemd タイマー vs cron](/articles/tutorials/systemd-timer-vs-cron) - [終了コードとステータスの読み方](/articles/tutorials/exit-codes-and-status) - [Permission denied の直し方](/articles/troubleshooting/permission-denied-fix) # 「Text file busy」の対処 - 実行中バイナリの上書き Source: https://penguin-gym-linux.com/articles/troubleshooting/text-file-busy ## 「Text file busy」とは何のエラーか? {#what} > **結論**: いま実行中のプログラム(または使用中の共有ライブラリ)の本体ファイルを、上書き・切り詰めしようとして弾かれている状態。カーネルが `ETXTBSY`(errno 26)を返している。走っているプロセスを止めるか、上書きではなく「置き換え(rename)」に切り替えれば解決する。 典型的にはバイナリのコピー・ビルド・デプロイで現れる。 ```output cp: cannot create regular file '/usr/local/bin/myapp': Text file busy ``` `Text file busy` の "text" は、歴史的な Unix 用語でプログラムの **コードセグメント(text segment)** を指す。ファイルの中身が日本語テキストかどうかとは無関係で、「実行コードのファイルが使用中」という意味。 カーネルがこのエラーを返すのは、おおむね次の操作のとき。 - **実行中のバイナリを書き込みモードで開こうとした**(`cp` での上書き、`> file` でのリダイレクトなど) - **使用中の共有ライブラリ(`.so`)を上書きしようとした**(メモリに mmap 済み) - 書き込み用に開いているファイルを `execve()` で実行しようとした(ビルド直後の競合など) ::: warning `Text file busy` は「権限がない(Permission denied)」とも「読み取り専用FS(Read-only file system)」とも別物。権限・FS は正しくても、**実行中である**という一点で書き込みが拒否される。エラー文を取り違えると対処を誤る。 ::: ## なぜ実行中のバイナリは上書きできないのか? {#why} > **結論**: Linux はプロセスが実行中の実行ファイル(とロード中の共有ライブラリ)の inode を「書き込み禁止」として保護する。走行中のコードを途中で書き換えるとクラッシュや未定義動作を招くため、カーネルが `ETXTBSY` で先回りして防いでいる。 プログラムを起動すると、カーネルはその実行ファイルの内容を **mmap でメモリにマップ** して実行する。ページは必要に応じて遅延読み込みされるため、ファイルの実体は実行中ずっと参照され続ける。 ここで誰かがファイルを書き換えると、まだ読み込んでいないページが別物に変わり、整合性が崩れる。これを防ぐためカーネルは inode 単位で「実行中フラグ」を立て、書き込みオープン(`O_WRONLY` / `O_RDWR`)や切り詰め(`truncate`)を `ETXTBSY` で拒否する。共有ライブラリも実行コードを含むため同じ保護対象になる。 ::: tip 逆方向の保護もある。あるファイルを **書き込み用に開いたまま** そのファイルを `execve()` で実行しようとしても `ETXTBSY` になる。ビルドスクリプトが出力ファイルを閉じ忘れたまま実行に進むと、このパターンに当たる。 ::: なお、**シェルスクリプトは通常このエラーにならない**。スクリプトは `bash` などのインタプリタが「データとして読む」だけで、保護対象の実行イメージにはならないため。`ETXTBSY` は主に ELF バイナリと共有ライブラリで起きる。 ## 原因プロセスを特定するには? {#find} > **結論**: `lsof <ファイル>` または `fuser <ファイル>` で、そのバイナリを掴んでいるプロセスを一覧する。出てきた PID が「止めるべき相手」。サービス管理下のプロセスなら `systemctl` で止めるのが安全。 ### lsof でファイル単位に見る `lsof` に上書きしたいファイルパスを渡すと、それを開いている・実行しているプロセスが分かる。 ```bash lsof /usr/local/bin/myapp ``` ```output COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME myapp 4821 deploy txt REG 259,1 6291456 131080 /usr/local/bin/myapp ``` `FD` 列の `txt` が「実行中のテキスト(コード)として使用」を示す。この `myapp`(PID 4821)が原因。共有ライブラリの場合は `mem`(mmap 済み)として現れる。 ### fuser で素早く PID を出す PID だけ手早く知りたいなら `fuser`。 ```bash fuser /usr/local/bin/myapp ``` ```output /usr/local/bin/myapp: 4821e ``` 末尾の `e` は **実行中(executable being run)** を意味する。`fuser -v` で USER / COMMAND まで確認できる。 ::: warning 原因が常駐サービスやデーモンの場合、`kill` で直接落とすと自動再起動や中途半端な状態を招く。**サービスは `systemctl stop` で止める**のが筋。手動起動のプロセスのみ `kill` を検討する。 ::: ## 上書きせずに安全に置き換えるには? {#fix} > **結論**: 走行中プロセスを止められないなら、`cp` での上書きをやめて `mv`(rename)や `install` で **新しいファイルに差し替える**。rename は inode を作り直してディレクトリエントリを張り替えるだけなので、実行中プロセスは古い inode を保持したまま、新バイナリと衝突しない。 ### なぜ `mv` は通って `cp` は弾かれるのか - `cp new /usr/local/bin/myapp` … 既存の inode を `O_TRUNC` で開いて中身を書き換える(in-place)→ 実行中なので `ETXTBSY` - `mv new /usr/local/bin/myapp` … 新しい inode を `myapp` という名前に **rename** し、古い名前を置き換える → 実行中 inode には触れないので成功 ポイントは「同じ場所のファイルシステム内」で完結させること。`/tmp`(別FS)から `mv` すると内部的にコピー+削除になり、上書きパスを踏む場合がある。**同一ディレクトリに新ファイルを置いてから rename** するのが定石。 ```bash # 同じディレクトリにダウンロード/ビルドしてから rename cp myapp.new /usr/local/bin/myapp.new # 別名でまず配置 mv /usr/local/bin/myapp.new /usr/local/bin/myapp # 原子的に差し替え ``` 走行中のプロセスは古いバイナリ(ディレクトリから消えたが inode は生存)で動き続け、次回起動時から新バイナリになる。 ### install を使うとより簡潔 `install` は既存ファイルを削除してから新規ファイルとして作り直す(実行中の inode に上書きしない)ため `ETXTBSY` を避けられ、権限・所有者もまとめて設定できる。デプロイ用途に向く。 ```bash sudo install -m 0755 -o root -g root myapp.new /usr/local/bin/myapp ``` ### どうしても同じ inode に書きたいなら先に削除 `rm` でディレクトリエントリを消してから書けば、新規作成扱いになり `ETXTBSY` を避けられる(unlink 自体は実行中でも可能)。 ```bash sudo rm /usr/local/bin/myapp # 実行中でも unlink は通る sudo cp myapp.new /usr/local/bin/myapp ``` ::: tip 最も確実なのは「**プロセスを止めてから上書き**」。サービスなら `systemctl stop myapp && cp ... && systemctl start myapp`。無停止で差し替えたい場合に rename / install 方式を使う、と覚えると判断が速い。 ::: ## デプロイやビルドで再発させないには? {#deploy} > **結論**: デプロイは「上書き」ではなく「rename による原子的差し替え」を原則にする。ビルドでは出力ファイルのディスクリプタを確実に閉じ、実行中の同名バイナリへ直接書かない。 再発を防ぐチェックポイント。 - **デプロイスクリプトで `cp -f` 直書きをやめる** … 一時名へ配置 → `mv` / `install` で差し替え。多くのデプロイツール(`go build` の出力差し替え等)がこの方式を採る - **稼働中サービスは停止→更新→再起動を基本に** … 無停止更新が必要なら rename 方式、または systemd の `ExecReload` / ソケットアクティベーションを検討 - **ビルド直後の実行は出力 fd を閉じてから** … スクリプトで `make && ./out` のように繋ぐとき、ビルドが書き込みハンドルを残していると `ETXTBSY`。明示的に閉じる・別ステップに分ける - **共有ライブラリ更新も同様** … 使用中の `.so` は上書きせず rename で差し替え。パッケージマネージャ(apt/dnf)はこの手順を内部で行うため、手動 `cp` での `.so` 上書きは避ける ::: warning NFS など一部のネットワークファイルシステムでは、別ホストで実行中のバイナリに対して `ETXTBSY` の挙動が一貫しないことがある。共有ストレージ上の実行ファイルを更新する際は、各ホストでプロセスを止めてから差し替えるのが安全。 ::: ## まとめ / 次に読む {#next} - [「device is busy」でアンマウントできない時の対処 - fuser/lsof で原因特定](/articles/troubleshooting/device-or-resource-busy) - [「Too many open files」の対処 - ファイルディスクリプタ枯渇](/articles/troubleshooting/too-many-open-files) - [Permission denied の直し方 - chmod/chown/sudo](/articles/troubleshooting/permission-denied-fix) - [「Read-only file system」の対処 - 再マウントとfsck](/articles/troubleshooting/read-only-file-system) # 「Too many authentication failures」の解決 - 鍵の送りすぎ Source: https://penguin-gym-linux.com/articles/troubleshooting/too-many-authentication-failures ## 「Too many authentication failures」とは何が起きているのか? {#intro} > **結論**: サーバが許す認証試行回数(`MaxAuthTries`、既定6回)を、正しい鍵に到達する前に使い切って切断された状態。鍵が間違っているのではなく「送りすぎ」が原因。 このエラーは SSH の接続自体は成立し、サーバの `sshd` まで会話が届いている。ネットワークの問題でも、鍵そのものが無効なわけでもない。 ```output Received disconnect from 203.0.113.10 port 22:2: Too many authentication failures Disconnected from 203.0.113.10 port 22 ``` `sshd` は1接続あたりの認証試行回数に上限(`MaxAuthTries`、デフォルト `6`)を設けている。**公開鍵認証では、クライアントが鍵を1つ提示するたびに1回の試行としてカウントされる**。`ssh-agent` に大量の鍵を抱えていると、ssh は接続先が受け付けるかどうかに関係なく手持ちの鍵を順に提示し、正しい鍵にたどり着く前に上限へ達する。 ::: warning **前提(対象環境)** - クライアント: Ubuntu / macOS(考え方は共通) - サーバ: Ubuntu(OpenSSH server) - `MaxAuthTries` のデフォルトは `6`(OpenSSH `sshd_config` の既定値) ::: ## まず何を確認すべきか? {#first} > **結論**: `ssh -v` で `Offering public key:` の行数を数える。切断前に何個の鍵を提示しているかが、そのまま原因の証拠になる。 最初にやることは「自分がいくつ鍵を送っているか」の可視化だ。 ```bash $ ssh -v user@server.example.com ``` 注目する行: ```output debug1: Offering public key: /home/user/.ssh/id_rsa RSA SHA256:... debug1: Offering public key: /home/user/.ssh/id_ed25519 ED25519 SHA256:... debug1: Offering public key: ssh-ed25519 SHA256:... agent debug1: Offering public key: ssh-rsa SHA256:... agent ... Received disconnect from 203.0.113.10 port 22:2: Too many authentication failures ``` `Offering public key` が6回前後並んだ直後に切断されていれば、原因は確定だ。提示している鍵が多すぎて、正しい鍵を出す前に `MaxAuthTries` を超えている。 ::: tip 末尾に `agent` と付く行は `ssh-agent` が保持している鍵、ファイルパスが出る行は設定や既定の鍵ファイルに由来する。両方が合算されて提示回数になる。 ::: ## なぜ鍵を送りすぎると拒否されるのか? {#why} > **結論**: `sshd` は1接続で許す認証試行を `MaxAuthTries` 回に制限する。公開鍵は提示するだけで1回消費するため、鍵が多いと「正しい鍵を試す前に」枠が尽きる。 仕組みを整理すると次のとおり。 1. クライアントは利用可能な鍵を**順番に**サーバへ提示する。 2. サーバは提示された鍵が `authorized_keys` にあるか確認し、無ければ「次へ」と促す。 3. この1往復が**1試行**としてカウントされる。 4. 試行回数が `MaxAuthTries` に達した時点で、サーバは認証成功を待たずに切断する。 つまり、鍵が10個あって正解が9番目にある場合、6回目で打ち切られて正解に届かない。これが「送りすぎ」の正体だ。 | 観点 | `Permission denied (publickey)` | `Too many authentication failures` | | ---------------- | ------------------------------- | ------------------------------------ | | 何が起きたか | 正しい鍵が見つからず認証失敗 | 正しい鍵に到達する前に試行枠が尽きた | | 主因 | 鍵不一致 / 権限 / 設定 | 提示する鍵が多すぎる | | 典型的な切り分け | `authorized_keys` を確認 | 提示鍵を1つに絞る | ::: warning `ssh-add -l` で大量の鍵が登録されていると、どのサーバへ接続してもまず agent の鍵が全部提示される。1台のサーバ専用の鍵でも、agent 経由で他の鍵が先に消費されてしまう点に注意する。 ::: ## 今すぐ繋ぐ応急処置は? {#quickfix} > **結論**: `-o IdentitiesOnly=yes` と `-i 鍵` を併用して、提示する鍵を1つだけに限定する。これで試行回数が1回に収まり、その場で接続できる。 ```bash $ ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 user@server.example.com ``` `IdentitiesOnly=yes` が要だ。これが無いと、`-i` で鍵を**指定しても** ssh は `ssh-agent` の鍵を**追加で**提示してしまい、提示回数は減らない。ここが最大の落とし穴になる。 ::: danger `-i` だけでは不十分。`-i ~/.ssh/id_ed25519` を付けても `IdentitiesOnly=yes` が無ければ、ssh は「指定鍵 + agent の全鍵」を提示する。応急処置では必ず両方をセットで使う。 ::: 正しい鍵が分からない場合は、`-v` を併用して1つずつ試す。 ```bash $ ssh -v -o IdentitiesOnly=yes -i ~/.ssh/id_rsa user@server.example.com ``` `Authentication succeeded (publickey)` が出れば、その鍵が正解だ。次の手順で恒久設定に落とし込む。 ## ~/.ssh/config で恒久対処するには? {#config} > **結論**: 接続先ごとに `IdentityFile` と `IdentitiesOnly yes` を `~/.ssh/config` に書く。以後は鍵が自動で1つに絞られ、コマンドにオプションを付ける必要がなくなる。 ```output Host myserver HostName server.example.com User user IdentityFile ~/.ssh/id_ed25519 IdentitiesOnly yes ``` これで `ssh myserver` だけで、指定した鍵のみを提示する接続になる。 複数のサーバを使い分けるなら、ホストごとにブロックを分ける。 ```output Host prod HostName prod.example.com User deploy IdentityFile ~/.ssh/id_prod IdentitiesOnly yes Host gitlab HostName gitlab.example.com User git IdentityFile ~/.ssh/id_gitlab IdentitiesOnly yes ``` ::: tip `IdentitiesOnly yes` は「`config` と `-i` で**明示した鍵だけ**を使う」という意味。agent の中身に引きずられなくなるため、鍵が多い環境ほど効果が大きい。 ::: ## ssh-agent の鍵を整理するには? {#agent} > **結論**: `ssh-add -l` で登録数を確認し、多すぎるなら `ssh-add -D` で一旦全削除して必要な鍵だけ入れ直す。agent の肥大化が「送りすぎ」の根本原因になりやすい。 まず現状を確認する。 ```bash $ ssh-add -l ``` ```output 256 SHA256:... user@host (ED25519) 3072 SHA256:... old-key (RSA) 256 SHA256:... another (ED25519) ... ``` ここに6個以上並んでいれば、`IdentitiesOnly` を使わない限りどのサーバでも試行枠を食い潰す。不要な鍵を消す。 ```bash # 全削除して入れ直す $ ssh-add -D $ ssh-add ~/.ssh/id_ed25519 ``` 特定の鍵だけ外す場合: ```bash $ ssh-add -d ~/.ssh/id_rsa ``` ::: warning agent への鍵の自動ロード(`AddKeysToAgent yes` やログイン時スクリプト)が設定されていると、削除してもすぐ戻ることがある。再発するなら、ロード元の設定(`~/.ssh/config` の `AddKeysToAgent`、シェルの起動スクリプト)も見直す。 ::: ## サーバ側で MaxAuthTries を調整すべきか? {#server} > **結論**: サーバを管理しているなら `MaxAuthTries` を上げて回避できるが、総当たり耐性が下がる対症療法。原則はクライアント側で鍵を絞るのが正攻法。 サーバの実効値は `sshd -T` で確認する。 ```bash $ sudo sshd -T | grep -i maxauthtries ``` ```output maxauthtries 6 ``` どうしても多数の鍵を提示する運用が避けられない場合のみ、`/etc/ssh/sshd_config` で値を上げる。 ```output MaxAuthTries 10 ``` ```bash $ sudo systemctl reload ssh ``` ::: danger `MaxAuthTries` を上げると、1接続あたりに許す認証試行が増え、パスワード総当たり・鍵総当たりに対する耐性が下がる。公開向けサーバでは安易に上げない。まずクライアント側の `IdentitiesOnly` で解決できないかを優先する。 ::: ::: tip サーバ側で根本対処するなら、`MaxAuthTries` を上げるより、対象ユーザーの `authorized_keys` を整理し、クライアントに正しい鍵を1つだけ提示させる運用へ寄せるほうが安全だ。 ::: ## 再発を防ぐチェックリスト {#checklist} > **結論**: 「提示鍵を1つに絞る」が解決の本質。`config` の `IdentitiesOnly`、agent の整理、サーバ設定の確認を順に潰せば再発しない。 - [ ] `ssh -v` で `Offering public key` の数が `MaxAuthTries`(既定6)を超えていないか - [ ] 応急処置として `-o IdentitiesOnly=yes -i 鍵` で接続できるか - [ ] `~/.ssh/config` の対象 Host に `IdentityFile` と `IdentitiesOnly yes` を書いたか - [ ] `ssh-add -l` の登録鍵が多すぎないか(不要なら `ssh-add -D`) - [ ] 鍵が自動で戻る場合、`AddKeysToAgent` や起動スクリプトを確認したか - [ ] サーバ管理者の場合、`sshd -T` で `maxauthtries` の実効値を確認したか ## 次に読む {#next} - [「Permission denied (publickey)」の解決](/articles/troubleshooting/permission-denied-publickey) - [SSH 接続全般のトラブルシュート](/articles/troubleshooting/ssh-troubleshooting) - [SSHが勝手に切れるときの対策](/articles/troubleshooting/ssh-connection-closed-timeout) # "Too many open files" の対処 - ファイルディスクリプタ枯渇 Source: https://penguin-gym-linux.com/articles/troubleshooting/too-many-open-files ## この記事で解決できること {#intro} - `Too many open files` エラーの原因(プロセス上限 vs システム上限)が分かる - `ulimit` と `lsof` で **現在の FD 使用状況** をすぐ確認できる - `limits.conf` / `sysctl.conf` / systemd オーバーライドで **恒久的に上限を上げる** 方法が分かる ::: tip **結論(最短 3 ステップ)** 1. `ulimit -n` または `/proc//limits` で **どの上限に当たっているか** を確認 2. `lsof -p | wc -l` で **プロセスが実際に開いている FD 数** を確認 3. 原因に応じて `/etc/security/limits.conf`(PAM 経由プロセス)または `LimitNOFILE=`(systemd サービス)で恒久設定 ::: ::: warning **前提(対象環境)** - OS:Ubuntu / Debian / RHEL 系 - 権限:`sudo` が使える前提 - systemd ベースのサービス管理前提 ::: ## 「Too many open files」とはどういうエラーか {#what} ファイルディスクリプタ(FD)は、Linux がファイル・ソケット・パイプなどを管理するための **整数の識別子**。プロセスはファイルを開くたびに FD を 1 つ消費する。 Linux には 2 種類の FD 上限がある。 - **プロセス上限(EMFILE)**: 1 プロセスが同時に開ける FD の数。`ulimit -n` で確認できる - **システム上限(ENFILE)**: OS 全体で開ける FD の総数。`/proc/sys/fs/file-max` で確認できる 多くの場合、エラーは **プロセス上限への到達**(デフォルト 1024)が原因。高トラフィックな Web サーバ・データベース・Node.js アプリは 1024 では不足することが多い。 ::: highlight **典型症状** - Nginx / Apache / MySQL / Node.js が負荷時に突然エラーを吐く - ログに `Too many open files (EMFILE)` や `accept4: Too many open files` が出る - `ulimit -n` の値(デフォルト 1024)付近で接続数が詰まる ::: ## 1. FD 使用状況を確認する {#diagnose} ### 1-1. 現在のシェル・プロセスの上限を確認 ```bash $ ulimit -n # ソフト上限(実効値) $ ulimit -Hn # ハード上限(プロセスが自分で引き上げられる上限) ``` ```output 1024 1048576 ``` ソフト上限(1024)が実効値。プロセス自身がハード上限まで引き上げることができる。 ### 1-2. 特定プロセスの上限と使用数を確認 ```bash $ cat /proc//limits | grep 'open files' ``` ```output Limit Soft Limit Hard Limit Units Max open files 65536 65536 files ``` 実際に開いている FD 数を確認する: ```bash $ ls /proc//fd | wc -l # または $ sudo lsof -p | wc -l ``` ### 1-3. システム全体の FD 使用状況 ```bash $ cat /proc/sys/fs/file-nr ``` ```output 13472 0 524288 ``` 3 列の意味:**現在の使用数 / 解放予約済(常に 0) / システム上限**。 システム上限値の確認: ```bash $ sysctl fs.file-max fs.file-max = 524288 ``` ::: tip プロセス上限(`ulimit -n`)がシステム上限(`fs.file-max`)より先に詰まるのが通常。システム上限に達することは稀。 ::: ## 2. 原因プロセスを特定する {#find-culprit} ### 2-1. FD 使用数の多いプロセスをランキング ```bash $ sudo lsof 2>/dev/null | awk '{print $1, $2}' | sort | uniq -c | sort -rn | head -20 ``` ```output 4821 nginx 1234 3102 mysqld 5678 1204 node 9012 ``` ### 2-2. 特定プロセスが何を開いているか確認 ```bash $ sudo lsof -p ``` ```output COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nginx 1234 www 0r REG 8,1 1234 /var/log/nginx/access.log nginx 1234 www 3u IPv4 56789 0t0 TCP *:80 (LISTEN) ``` ソケットが大量にある場合、コネクションリーク(close し忘れ)が原因である可能性が高い。 ### 2-3. ソケット状態の確認 ```bash $ ss -s ``` ```output Total: 12034 TCP: 11820 (estab 8000, closed 200, orphaned 20, timewait 200) ``` `estab`(確立済み)が異常に多い場合、アプリ側の接続管理を見直す。 ## 3. 一時的な上限引き上げ(応急処置) {#temp-fix} 現在のシェルセッションのみに有効な一時設定。ログアウト後は元に戻る。 ```bash $ ulimit -n 65536 ``` 既に起動しているプロセスの上限を変えたい場合は `prlimit` を使う: ```bash $ sudo prlimit --pid --nofile=65536:65536 ``` ::: warning `ulimit` の変更はそのシェルセッションのみ有効。恒久設定は次セクションを参照。 ::: ## 4. 恒久的な設定変更 {#permanent-fix} ### 4-1. PAM 経由プロセス(ログインセッション・一般デーモン) `/etc/security/limits.conf` または `/etc/security/limits.d/*.conf` に追記する。 ```bash $ sudo vi /etc/security/limits.d/99-fd-limits.conf ``` ``` # * は全ユーザー対象。特定ユーザーの場合はユーザー名を指定 * soft nofile 65536 * hard nofile 65536 # 特定ユーザーのみ変える場合 www-data soft nofile 65536 www-data hard nofile 65536 ``` 設定を反映するには **再ログイン**(またはサービス再起動)が必要。確認: ```bash $ su - www-data -s /bin/bash -c 'ulimit -n' 65536 ``` ::: tip Ubuntu 18.04 以降は `/etc/systemd/system.conf` の `DefaultLimitNOFILE=` でシステムワイドなデフォルトも変更できる。ただし全サービスに影響するため、サービス単位の設定(4-3)を優先する。 ::: ### 4-2. システム全体の上限(fs.file-max)を上げる 通常はプロセス上限の対処で十分。システム上限に達した場合のみ変更する。 ```bash $ sudo sysctl -w fs.file-max=1048576 # 即時反映(再起動で消える) ``` 永続化するには `/etc/sysctl.d/99-fd.conf` を作成する: ```bash $ sudo vi /etc/sysctl.d/99-fd.conf ``` ``` fs.file-max = 1048576 ``` ```bash $ sudo sysctl --system # 全 sysctl.d/*.conf を再読み込み ``` ### 4-3. systemd 管理サービスの場合 **systemd は PAM を経由しないため `limits.conf` は無効**。サービス単位でオーバーライドを作る。 ```bash $ sudo systemctl edit nginx # エディタが開く ``` 以下を追記して保存する: ```ini [Service] LimitNOFILE=65536 ``` または手動でファイルを作成する: ```bash $ sudo mkdir -p /etc/systemd/system/nginx.service.d/ $ sudo vi /etc/systemd/system/nginx.service.d/override.conf ``` ```ini [Service] LimitNOFILE=65536 ``` ```bash $ sudo systemctl daemon-reload $ sudo systemctl restart nginx ``` 設定が反映されているか確認する: ```bash $ cat /proc/$(pgrep -o nginx)/limits | grep 'open files' Max open files 65536 65536 files ``` ::: tip `LimitNOFILE=infinity` はカーネル 5.15 以降で使用可能。それより古い環境では数値で指定する(`65536` または `1048576`)。 ::: ## 5. やってはいけないこと {#dont} ::: danger - **`ulimit -n unlimited` を恒久設定する**: プロセスがバグで FD をリークした場合にシステム全体が詰まる。適切な上限(65536〜1048576)を設定する - **原因を調べずに上限だけ上げる**: FD リーク(接続を閉じ忘れる実装バグ)が原因の場合、上限を上げても一時しのぎにしかならない - **`limits.conf` を書いて systemd サービスに適用されると思い込む**: systemd 管理下のサービスは PAM を経由しないため `limits.conf` は無効 - **`fs.file-max` を不必要に大きくする**: カーネルが FD ごとにメモリを確保するため、無制限に上げるとメモリを圧迫する ::: ## 次に読む {#next} - [OOM killer 発動時の対処](/articles/troubleshooting/oom-killer-handling) - [メモリ不足の調べ方](/articles/troubleshooting/memory-troubleshooting) - [CPU 使用率 100% の原因調査](/articles/troubleshooting/cpu-high-load) # ufw でSSHが繋がらない時:許可ルールの確認と戻し方 Source: https://penguin-gym-linux.com/articles/troubleshooting/ufw-ssh-troubleshooting ## この記事で解決できること {#intro} - `ufw` が原因で SSH が繋がらない状況を、順番に切り分けできます - 「許可したのに繋がらない」「急に繋がらなくなった」を、最短で復旧できます - ありがちな事故(自分で自分を締め出す)を避ける"安全な進め方"が分かります ::: tip **結論(最短ルート)** SSHが繋がらない時は、これを**上から順に**やるのが最も安全です。 1. **SSHのポート番号を確定**(22とは限らない) 2. **ufwが原因か確認**:`sudo ufw status verbose` 3. **許可ルールを確認**:`sudo ufw status numbered` 4. **必要な許可を追加**:`sudo ufw allow /tcp` 5. **まだダメなら"ufw以外"を疑う**(ポート待受、sshd、セキュリティグループ等) ::: ::: warning **重要** この記事は「サーバ側に入れる状態(コンソール/別経路でログイン可)」を前提に書いています。既にSSHで入れないなら、クラウドのコンソールやVPSのWebコンソール等で作業してください。 ::: ## 1. まず状況を整理(ここを飛ばすと迷子になります) {#clarify} > **結論**: 接続先ホスト・接続ポート・接続元の3点を最初に確定しないと原因調査で迷子になるため必ず先に整理する。 あなたが確認すべきは以下の3点です。 - **接続先ホスト**:ドメイン or IP - **接続ポート**:`22` なのか、`2222` 等に変えているのか - **接続元**:自宅、社内、踏み台など(IP制限する場合に必要) ## 2. ufw が原因かどうか確認する(最初にやる) {#ufw-check} > **結論**: `ufw status verbose`でinactiveならufwは原因外、activeなら次のルール確認へ進むことで切り分けが完結する。 ufwが無効なら、ufwは原因ではありません(別要因の可能性が高い)。 ```bash $ sudo ufw status verbose ``` 出力例(有効): ```output Status: active Logging: on (low) Default: deny (incoming), allow (outgoing), disabled (routed) ``` 出力例(無効): ```output Status: inactive ``` - `inactive` → ufw以外(sshd停止、ポート違い、待受なし、クラウド側FWなど) - `active` → 次へ(ルール確認) ## 3. "SSHのポート番号"を確定する(22固定だと事故る) {#port} > **結論**: `sshd -T | grep port`で実際に採用されているポート番号を確定し、22固定の思い込みによる許可ルール誤設定を防ぐ。 SSHのポートは22が多いですが、セキュリティ目的で変更されていることがあります。 ```bash $ sudo grep -nE '^\s*Port\s+' /etc/ssh/sshd_config ``` 追加で確認(includeされている場合に備える): ```bash $ sudo sshd -T | grep -i '^port ' ``` ::: tip `sshd -T` は "sshdが実際に採用している設定" を出してくれるので確実です。 ::: ## 4. ufw のルールを「番号付き」で確認する {#rules} > **結論**: `ufw status numbered`でSSHポートのALLOW IN有無・DENY混入・接続元IP制限を確認してから操作することで事故を防ぐ。 まずは現状把握です。推測で操作しない。 ```bash $ sudo ufw status numbered ``` 見るポイント: - SSHポート(例:22/tcp または 2222/tcp)が **ALLOW IN** になっているか - **DENY** が入っていないか(優先順位で詰むケースあり) - `From` が絞られていないか(自分のIPが変わると繋がらない) ## 5. 最小の復旧:SSHポートを許可する {#allow} > **結論**: まず`ufw allow /tcp`で繋がる状態を取り戻してからIP制限などで固めるという順序を守るのが安全。 まず "繋がる状態を取り戻す" のが優先です。その後、IP制限などで固めればOKです。 ### 5-1. 標準22番を許可 ```bash $ sudo ufw allow 22/tcp ``` ### 5-2. 2222番を許可(例) ```bash $ sudo ufw allow 2222/tcp ``` 追加したら必ず確認: ```bash $ sudo ufw status numbered ``` ## 6. より安全:接続元IPを限定して許可する(推奨) {#ip-limit} > **結論**: IP制限は`ufw allow from to any port `で設定するが自宅IP変動に注意しVPN/踏み台との併用を検討する。 SSHはインターネット全開放より、IP制限のほうが堅いです。 ```bash $ sudo ufw allow from 203.0.113.10 to any port 2222 proto tcp ``` ::: warning 注意:自宅回線のIPが変わる環境だと、翌日繋がらなくなることがあります。その場合は「固定IP」「VPN」「踏み台」なども検討対象です。 ::: ## 7. 「許可したのに繋がらない」原因トップ5 {#other-causes} > **結論**: ufw許可後も繋がらない場合はsshd停止・待受なし・クラウドFW・ルール優先順位・IPv6設定の5原因を順に確認する。 ### 原因1:sshd が起動していない / 落ちている ```bash $ sudo systemctl status ssh $ sudo systemctl start ssh $ sudo journalctl -u ssh -n 200 ``` ### 原因2:サーバがそのポートで待ち受けていない ```bash $ sudo ss -lntp | grep ':22 ' $ sudo ss -lntp | grep ':2222 ' ``` ### 原因3:クラウド/ホスティング側のFWで止まっている AWSならセキュリティグループ、GCPならVPC FW、VPSなら管理画面FWなど。ufwだけ直しても外から入れません。 ```bash $ nc -vz example.com 2222 ``` - `timed out` → 経路/FW(クラウド側含む) - `refused` → サーバ側で待受なし - `succeeded` → ネットワーク的には届いている ### 原因4:ufwルールの優先順位や deny が効いている ```bash $ sudo ufw status numbered $ sudo ufw delete 3 ``` ### 原因5:IPv6 側だけ閉じている / 逆にIPv6だけ開いてる 環境によってIPv6が絡みます。statusで `Anywhere (v6)` も確認します。 ## 8. 失敗例とエラーの読み方 {#errors} > **結論**: timed outはFW/経路、refusedは待受なし、permission deniedは認証失敗でufwではないと、エラー文から原因の階層が判別できる。 ### 8-1. Connection timed out 意味:パケットが届かない(FW/SG/経路/DNS/相手ダウン) ### 8-2. Connection refused 意味:到達しているが、そのポートで待受がない ### 8-3. Permission denied (publickey) 意味:通信は通っているが、認証で落ちている(ufwではない) ### 8-4. Host key verification failed 意味:known_hostsの不一致(ufwではない) ```bash $ ssh-keygen -R example.com ``` ## 9. やってはいけないこと {#mistakes} > **結論**: SSH許可前のufw enable・原因不明のdeny量産・IP制限後の自分のIP変動確認漏れが自己ロックアウトを招く典型的な失敗パターン。 ### やってはいけない1:SSH許可なしで ufw を有効化する SSHが許可されていない状態で `ufw enable` をやると、リモートから入れなくなります。 **安全な順番:** 1. `sudo ufw allow /tcp` 2. `sudo ufw enable` 3. `sudo ufw status numbered` ### やってはいけない2:原因不明のまま deny を量産する denyが増えると優先順位が絡んで、復旧が遅くなります。 ### やってはいけない3:IP制限を入れてから「自分のIP」を確認しない 自宅IPが変わりやすいなら、IP制限は"踏み台/VPN"とセットでないと運用事故になります。 ::: tip **コピペ用:典型テンプレ** ```bash # SSH(2222)を全開放で許可(まず復旧) sudo ufw allow 2222/tcp sudo ufw status numbered # SSH(2222)を特定IPだけ許可(運用用) sudo ufw allow from 203.0.113.10 to any port 2222 proto tcp sudo ufw status numbered # denyルールを番号指定で削除 sudo ufw status numbered sudo ufw delete 3 sudo ufw status numbered # sshdが起動しているか確認 sudo systemctl status ssh sudo journalctl -u ssh -n 200 sudo ss -lntp | grep ':2222 ' # クライアント側疎通確認 nc -vz example.com 2222 ``` ::: ::: tip **まとめ** - ufwのトラブルは **「ufw有効か」→「SSHポート確定」→「ルール確認」→「許可追加」** の順で潰す - `timed out / refused / publickey` の違いで、原因の階層が分かる - いきなり `ufw enable/deny` でいじらず、**番号付きで現状確認**してから操作する ::: ## 次に読む {#next} - [ポート疎通の確認方法](/articles/troubleshooting/port-connectivity) - [SSH接続の認証エラー対処](/articles/troubleshooting/ssh-troubleshooting) - [システムログの確認方法](/articles/tutorials/journalctl-basics) # ゾンビプロセス(zombie process)の正体と対処 Source: https://penguin-gym-linux.com/articles/troubleshooting/zombie-process ## ゾンビプロセスとは何か? {#what} **終了したのにプロセステーブルから消えないプロセス**がゾンビプロセス(状態コード `Z`)。プロセス自体の実行はすでに終わっており、CPU・メモリは消費しない。ただし PID とプロセステーブルのエントリだけが残り続ける。 ```bash $ ps aux | grep Z ``` ```output USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND www-data 1234 0.0 0.0 0 0 ? Z 10:01 0:00 [defunct] ``` `STAT` 列が `Z` または `[defunct]` と表示されるのがゾンビの特徴。 ## なぜゾンビプロセスが発生するのか? {#why} **親プロセスが子の終了ステータスを回収していない**ことが原因。 Linux では子プロセスが終了すると、カーネルは終了ステータスをプロセステーブルに保存して親に `SIGCHLD` シグナルを送る。親が `wait()` / `waitpid()` でステータスを回収するまで、子のエントリはテーブルに残り続ける。 - 親が `wait()` を呼ばずに子を終了させた - 親が `SIGCHLD` ハンドラを適切に実装していない - 親自体にバグがあり、シグナルを処理できない状態になっている ::: tip ゾンビは**親プロセスが消えない限り自分では消えない**。`kill -9` をゾンビに送っても効果なし(すでに実行中ではないため)。 ::: ## ゾンビプロセスの詳細な確認方法 {#detect} ### 親プロセスを特定する ```bash $ ps -el | grep Z ``` ```output F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 4 Z 1000 1234 1100 0 80 0 - 0 - pts/0 00:00:00 process ``` `PPID`(親プロセス ID)が `1100` とわかる。次に親プロセスを確認する。 ```bash $ ps aux | awk 'NR==1 || $2==1100' ``` ### top でゾンビ数を監視する ```bash $ top ``` ```output Tasks: 142 total, 1 running, 141 sleeping, 0 stopped, 2 zombie ``` `zombie` の数が継続的に増加している場合は問題あり。 ## ゾンビプロセスの対処法 {#fix} ### 方法 1: 親プロセスに SIGCHLD を送る 親プロセスが `SIGCHLD` を処理できる実装なら、シグナルを送ると `wait()` を呼び出してゾンビを回収する。 ```bash $ kill -SIGCHLD ``` ### 方法 2: 親プロセスを終了させる 親が終了すると、子(ゾンビ)の養親が `init`(PID 1)または `systemd` に変わり、自動的に回収される。 ```bash $ kill ``` 親が応答しない場合: ```bash $ kill -9 ``` ::: warning サービスプロセス(nginx、mysqld 等)を親ごと kill する場合は、サービスへの影響を事前に確認すること。 ::: ### 方法 3: システム再起動 ゾンビが大量発生して正常な状態に戻せない場合の最終手段。システム再起動でプロセステーブルは初期化される。 ## ゾンビプロセスは危険か? {#risk} **少数のゾンビは実害なし**。CPU・メモリを消費しないため、1〜2 個程度であれば即座に対処する必要はない。 ただし次の場合は問題になる: - **PID 枯渇**: Linux のデフォルト PID 上限(`/proc/sys/kernel/pid_max`)は 32768。ゾンビが大量に蓄積すると PID が尽き、新しいプロセスを起動できなくなる - **プロセステーブルの肥大化**: カーネルリソースを消費し続ける ::: tip 現在の PID 上限確認: ```bash $ cat /proc/sys/kernel/pid_max 32768 ``` ::: ## ゾンビを生まないアプリケーション設計 {#prevention} ゾンビはアプリケーションのバグが表出したもの。根本対処はコード修正。 - `SIGCHLD` ハンドラで `waitpid(-1, NULL, WNOHANG)` をループ呼び出し - double fork パターンで孫プロセスを `init` に引き取らせる - `SIGCHLD` を `SIG_IGN` に設定する(POSIX 準拠の実装では子の自動回収が保証される) ## 次に読む {#next} - [CPU使用率100%の原因調査](/articles/troubleshooting/cpu-high-load) - [メモリ不足の調べ方](/articles/troubleshooting/memory-troubleshooting) - [OOM killer 発動時の対処](/articles/troubleshooting/oom-killer-handling) # 基本ファイル管理 - cp/mv/rm/touch とワイルドカード【LPIC-1 103.3】 Source: https://penguin-gym-linux.com/articles/lpic/basic-file-management ## この記事で達成できること {#intro} - `cp` / `mv` / `rm` の主要オプション(`-r` `-a` `-i` `-p` `-u` `-f`)を使い分けられる - `touch` で空ファイルを作成し、タイムスタンプを更新できる - `mkdir -p` / `rmdir` でディレクトリを安全に作成・削除できる - ワイルドカード(`*` `?` `[ ]` `[!...]`)とブレース展開(`{ }`)を正しく書ける - グロブを展開するのはコマンドではなく**シェル**だと説明できる LPIC-1 主題 103.3「基本的なファイル管理を行う」の中核。日常作業の 8 割を占めるファイル操作を、事故なく確実に行う型を押さえる。 ## どのコマンド・オプションを選ぶか {#flow} ファイル操作は「何をしたいか」と「事故をどう防ぐか」で選ぶ。下表が判断の起点になる。 | やりたいこと | コマンド | 事故防止の定番 | | ------------------------ | ---------- | ---------------------- | | コピー | `cp` | `-i`(上書き確認) | | バックアップ(属性保持) | `cp -a` | `-a` = 再帰+属性保持 | | 移動・リネーム | `mv` | `-i`(上書き確認) | | 削除 | `rm` | `-i`(1件ずつ確認) | | 空ファイル作成・更新 | `touch` | 既存は中身を壊さない | | ディレクトリ作成 | `mkdir -p` | 親ごと一括作成 | | 空ディレクトリ削除 | `rmdir` | 空でなければ失敗で安全 | 迷ったら「破壊系(`rm` / `mv` の上書き)には `-i` を付ける」を基本姿勢にする。`rmdir` が空でないと失敗するのは欠点ではなく、誤削除を防ぐ安全弁だと理解しておく。 ## cp / mv / rm の使い方 {#crud} ### cp - コピーとオプションの違い `cp` はファイル・ディレクトリを複製する。ディレクトリには `-r`(再帰)が必須。バックアップ用途では属性まで保持する `-a` を使う。 ```bash cp file1.txt file2.txt cp -r src/ backup/ cp -a src/ archive/ ``` ```output $ ls -l file2.txt -rw-r--r-- 1 user user 0 May 30 10:00 file2.txt ``` 主要オプションの意味は次のとおり。GNU coreutils の `cp` を前提とする。 | オプション | 意味 | | ---------- | --------------------------------------------------------- | | `-r`, `-R` | ディレクトリを再帰的にコピー | | `-a` | アーカイブモード。`-dR --preserve=all` と同等(属性保持) | | `-i` | 上書き前に確認を求める | | `-p` | 更新時刻・所有者・パーミッションなどの属性を保持 | | `-u` | コピー先が古い、または存在しない場合のみコピー(更新) | `-r` は「中身ごとコピー」、`-a` は「中身ごと+属性・シンボリックリンク・タイムスタンプも丸ごと保持」。単純な複製は `-r`、バックアップは `-a` と覚える。 ::: tip `-u`(update)は差分同期に便利。`cp -au src/ dst/` で「新しいものだけを属性保持でコピー」になり、簡易バックアップに使える。 ::: ### mv - 移動とリネーム `mv` は移動とリネームを兼ねる。同じディレクトリ内での `mv old new` がリネーム、別ディレクトリへの `mv file dir/` が移動。上書きが怖い場面では `-i` を付ける。 ```bash mv draft.txt report.txt mv report.txt /home/user/docs/ mv -i report.txt /home/user/docs/ ``` ```output $ mv -i report.txt /home/user/docs/ mv: overwrite '/home/user/docs/report.txt'? n ``` | オプション | 意味 | | ---------- | ----------------------------------------- | | `-i` | 上書き前に確認を求める | | `-f` | 確認せず強制的に上書き(`-i` を打ち消す) | `mv` は `cp` と違い、ディレクトリ移動に `-r` は不要。同一ファイルシステム内ならデータの実体は動かさずパス情報だけ書き換えるため高速。 ### rm - 削除と -rf の危険性 `rm` はファイルを削除する。ディレクトリ削除には `-r`、確認なしの強制削除には `-f` を使う。`rm` に「ゴミ箱」はなく、削除即消滅と考える。 ```bash rm unwanted.txt rm -i unwanted.txt rm -r olddir/ ``` ```output $ rm -i unwanted.txt rm: remove regular file 'unwanted.txt'? y ``` | オプション | 意味 | | ---------- | ---------------------------------------- | | `-r`, `-R` | ディレクトリを再帰的に削除 | | `-f` | 存在確認・確認プロンプトを抑止し強制削除 | | `-i` | 削除前に1件ずつ確認を求める | ::: danger `rm -rf` は確認なしでディレクトリツリーを一括削除する。引数の打ち間違い(例: `rm -rf / tmp/foo` のように `/` の後ろにスペースが入る)でシステム全体を破壊しうる。実行前に必ず対象パスを目視確認し、不安なら `-i` を併用する。 ::: ## touch / mkdir / rmdir / file {#dir-touch} ### touch - 空ファイル作成とタイムスタンプ更新 `touch` は存在しないファイルを空で作成し、既存ファイルにはアクセス時刻・更新時刻を現在時刻に更新する(中身は変えない)。 ```bash touch newfile.txt touch existing.txt touch -c missing.txt ``` ```output $ ls -l --time-style=+%H:%M newfile.txt -rw-r--r-- 1 user user 0 10:05 newfile.txt ``` | オプション | 意味 | | ---------- | --------------------------------------------- | | `-a` | アクセス時刻のみ更新 | | `-m` | 更新時刻(modification time)のみ更新 | | `-c` | ファイルが存在しなければ作成しない | | `-t` | 指定した日時(`[[CC]YY]MMDDhhmm[.ss]`)に設定 | 既存ファイルに `touch` してもデータは消えない。ビルドの再実行を促す(make のタイムスタンプ判定)など、更新時刻の操作目的でも使う。 ### mkdir / rmdir - ディレクトリの作成と削除 `mkdir` はディレクトリを作る。途中の親ディレクトリがない深い階層は `-p` で一括作成する。`rmdir` は**空の**ディレクトリだけを削除する。 ```bash mkdir project mkdir -p project/src/lib rmdir project/src/lib ``` ```output $ rmdir project rmdir: failed to remove 'project': Directory not empty ``` `-p` がないと、存在しない親の下にはディレクトリを作れずエラーになる。`rmdir` が「空でないと失敗」するのは安全機構で、中身ごと消したいなら `rm -r` を使う(その分だけ危険も増す)。 ### file - 中身からファイル種別を判定する `file` は拡張子ではなく**中身**を調べてファイルの種別を判定する。拡張子が偽っていても実体を見抜ける。 ```bash file report.pdf script.sh archive.gz photo ``` ```output report.pdf: PDF document, version 1.7 script.sh: Bourne-Again shell script, ASCII text executable archive.gz: gzip compressed data photo: JPEG image data, JFIF standard 1.01 ``` 拡張子のない `photo` でも JPEG と判定できる。`cat` で開いて文字化けする前に `file` で種別を確認するのが安全な習慣。 ## ワイルドカードとシェル展開 {#glob} ワイルドカード(グロブ)はファイル名のパターンを一括指定する仕組み。重要なのは、展開するのは `cp` や `rm` ではなく**シェル**だという点。シェルがパターンを実ファイル名に置き換えてからコマンドに渡す。 | パターン | マッチ対象 | 例 | | -------- | ----------------------- | ------------------------------------- | | `*` | 任意の0文字以上の文字列 | `*.txt` → すべての .txt | | `?` | 任意の1文字 | `file?.txt` → file1.txt, fileA.txt | | `[abc]` | 角括弧内のいずれか1文字 | `file[12].txt` → file1.txt, file2.txt | | `[a-z]` | 範囲内のいずれか1文字 | `[a-c]*` → a/b/c で始まる名前 | | `[!abc]` | 角括弧内**以外**の1文字 | `file[!0].txt` → file0.txt 以外 | ブレース展開 `{ }` はグロブと別物で、**既存ファイルの有無に関係なく**文字列を機械的に生成する。 ```bash echo file{A,B,C}.txt echo {1..3}.log mkdir -p project/{src,test,docs} ``` ```output fileA.txt fileB.txt fileC.txt 1.log 2.log 3.log ``` `{A,B,C}` はカンマ区切りの列挙、`{1..3}` は連番。`mkdir -p project/{src,test,docs}` で 3 ディレクトリを一発生成できる。 ::: warning ブレース展開はファイルが存在しなくても文字列を作る(生成系)。一方グロブ `*` `?` `[ ]` は既存ファイル名へのマッチ(検索系)。`*.txt` は .txt が1つもなければマッチせず、デフォルトの bash では**パターン文字列がそのまま**コマンドに渡る点に注意。 ::: ```bash ls *.nonexistent echo *.nonexistent ``` ```output ls: cannot access '*.nonexistent': No such file or directory *.nonexistent ``` グロブが0件のとき、bash はデフォルトでパターンをそのまま渡す(`echo` はその文字列を表示し、`ls` は「そんなファイルはない」と言う)。これは `shopt -s nullglob` を設定すると「0件なら空に展開」へ変えられる。 ## よくあるミスと対処 {#mistakes} 実務・試験の両方でつまずきやすい点を整理する。 - **`cp` でディレクトリに `-r` を付け忘れる**: `cp dir/ dst/` は `omitting directory` エラー。ディレクトリコピーは常に `-r` か `-a`。 - **`-r` と `-a` を混同する**: 単純複製は `-r`、所有者・パーミッション・リンク・時刻まで保持するバックアップは `-a`。`-r` だけだと属性が変わる。 - **`mv` で無確認に上書きする**: デフォルトの `mv` は既存ファイルを黙って上書きする。重要ファイルには `-i` を習慣化する。 - **`rm *` をルートや重要ディレクトリで実行**: グロブを展開するのはシェル。実行前に `echo rm *` のように `echo` を前置して、何に展開されるか確認すると安全。 - **`rmdir` で「空でない」エラーに戸惑う**: 仕様どおりの安全動作。中身ごと消すなら `rm -r`、ただし対象を必ず確認。 ## トラブルシューティング {#troubleshooting} ### 症状: cp で「omitting directory」と出てコピーされない **原因**: ディレクトリを `-r`(または `-a`)なしでコピーしようとしている **確認**: ```bash file src ``` **対処**: ディレクトリなら `cp -r src/ dst/`、属性も保つなら `cp -a src/ dst/` を使う。 ### 症状: rm -rf を実行したらファイルが消えてしまった **原因**: `rm -rf` は確認なしで再帰削除する。`rm` に削除取り消しはない **確認**: ```bash echo rm -rf target/ ``` **対処**: 実行前に `echo` を前置して展開結果を目視する。不安なら `-i` を併用(`rm -ri`)。消えたファイルはバックアップからのみ復旧可能。 ### 症状: ワイルドカードを書いたのにマッチしない/パターンがそのまま表示される **原因**: 一致するファイルが0件。bash はデフォルトでパターン文字列をそのまま渡す **確認**: ```bash shopt nullglob ls -d *.txt ``` **対処**: 対象ディレクトリと拡張子を見直す。0件時に空展開させたい場合は `shopt -s nullglob` を設定する。 ## 作業完了チェックリスト {#checklist} - [ ] `cp -r` / `cp -a` の違いを説明して使い分けた - [ ] `mv -i` / `rm -i` で上書き・削除前の確認を有効にした - [ ] `touch` で空ファイル作成とタイムスタンプ更新を確認した - [ ] `mkdir -p` と `rmdir` の挙動の違いを確認した - [ ] `*` `?` `[ ]` とブレース展開 `{ }` を書き分けた - [ ] グロブを展開するのがシェルだと理解した ## まとめ {#summary} | 場面 | コマンド | ポイント | | ------------------ | -------------------- | ------------------------------ | | コピー | `cp -r` / `cp -a` | `-a` は属性まで保持 | | 移動・リネーム | `mv -i` | デフォルトは黙って上書き | | 削除 | `rm -ri` | 取り消し不可、`-rf` は最大注意 | | 作成・更新 | `touch` / `mkdir -p` | 既存の中身は壊さない | | 空ディレクトリ削除 | `rmdir` | 空でなければ失敗(安全) | | 種別判定 | `file` | 拡張子でなく中身で判定 | | 一括指定 | `*` `?` `[ ]` `{ }` | 展開するのはシェル | 基本ファイル管理は LPIC-1 で最も使用頻度が高い領域。ここを固めたら、ファイルの実体を指すリンク機構や、FHS とファイル検索へ進むと体系が完成する。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [コマンドラインの基本操作](/articles/lpic/command-line-basics) - [ハードリンクとシンボリックリンク](/articles/lpic/hard-symbolic-links) - [FHS とファイル検索(find / locate / which)](/articles/lpic/fhs-and-finding-files) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # システム起動と systemd - GRUB/runlevel/systemctl【LPIC-1 101.2/101.3】 Source: https://penguin-gym-linux.com/articles/lpic/boot-and-systemd ## この記事で達成できること {#intro} - ブートプロセスの 4 段階(BIOS/UEFI → ブートローダ → カーネル → init/systemd)を順に説明できる - GRUB2 の設定を `/etc/default/grub` 経由で安全に変更できる - `systemctl` でサービスの起動・停止・自動起動を制御できる - ブートターゲットと SysVinit ランレベルの対応を答えられる - `shutdown` で時刻指定の再起動・停止を実行できる - `dmesg` / `journalctl` でブート時のログを調査できる LPIC-1 主題 101.2「システムのブート」と 101.3「ランレベル/ブートターゲットを変更してシステムをシャットダウン・リブートする」の中核。サーバー運用の起点となる知識をまとめる。 ## ブートプロセスはどう進むのか {#boot-process} 電源投入から利用可能になるまで、Linux は 4 段階を順にたどる。各段階が次の段階をロードする「バトンリレー」構造を押さえれば、どこで止まったかを切り分けられる。 | 段階 | 担当 | 主な役割 | | ----------------- | ------------ | -------------------------------------------------- | | 1. ファームウェア | BIOS / UEFI | ハードウェア初期化、ブートデバイス選択 | | 2. ブートローダ | GRUB2 | カーネルと initramfs をメモリへロード | | 3. カーネル | Linux kernel | ハードウェア検出、ルートファイルシステムをマウント | | 4. init | systemd | サービス起動、ターゲット到達 | BIOS は MBR(ディスク先頭 512 バイト)からブートローダを読み込み、UEFI は EFI システムパーティション上のブートローダ(`*.efi`)を読み込む。どちらの環境でも、現代の主要ディストリビューションは GRUB2 をブートローダに、systemd を init システムに採用している。 ::: tip カーネルが読み込む initramfs(初期 RAM ファイルシステム)は、ルートファイルシステムをマウントするために必要なドライバ群を含む一時的な環境。これによりルートが暗号化・LVM・特殊なストレージ上にあってもマウントできる。 ::: ## GRUB2 の設定はどこを編集するのか {#grub} GRUB2 の動作は生成済みの `grub.cfg` ではなく、`/etc/default/grub` と `/etc/grub.d/` を編集してから再生成するのが正しい手順。`grub.cfg` を直接書き換えてはいけない。 GRUB2 の主要ファイルは次の通り。 | ファイル | 役割 | | --------------------------------- | ------------------------------------------------------ | | `/boot/grub/grub.cfg`(Debian系) | 生成される最終設定。**直接編集しない** | | `/boot/grub2/grub.cfg`(RHEL系) | 同上。ディストリでパスが異なる | | `/etc/default/grub` | タイムアウト、既定エントリ、カーネルパラメータの定義元 | | `/etc/grub.d/` | メニューエントリ生成スクリプト群 | `/etc/default/grub` の代表的な項目。 ```bash GRUB_TIMEOUT=5 GRUB_DEFAULT=0 GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" ``` `GRUB_TIMEOUT` はメニュー表示秒数、`GRUB_DEFAULT` は既定で選択されるエントリ、`GRUB_CMDLINE_LINUX_DEFAULT` はカーネルに渡す起動パラメータ。 ### grub.cfg を再生成する 編集後は `grub.cfg` を再生成して反映する。コマンド名はディストリビューションで異なる。 ```bash sudo grub-mkconfig -o /boot/grub/grub.cfg ``` ```output Generating grub configuration file ... Found linux image: /boot/vmlinuz-6.1.0-18-amd64 Found initrd image: /boot/initrd.img-6.1.0-18-amd64 done ``` Debian / Ubuntu 系には上記をラップした `update-grub` がある。RHEL / Fedora 系では `grub2-mkconfig -o /boot/grub2/grub.cfg` を使う。 | ディストリビューション | 再生成コマンド | 出力先 | | ---------------------- | ------------------------------------------- | ---------------------- | | Debian / Ubuntu | `update-grub` または `grub-mkconfig -o ...` | `/boot/grub/grub.cfg` | | RHEL / Fedora / CentOS | `grub2-mkconfig -o ...` | `/boot/grub2/grub.cfg` | ::: warning `grub.cfg` を直接編集しても、次回 `grub-mkconfig` 実行時に上書きされて消える。恒久的な変更は必ず `/etc/default/grub` または `/etc/grub.d/` を編集してから再生成する。 ::: ## systemctl でサービスをどう操作するのか {#systemctl} systemd では `systemctl` がサービス制御の中心。起動・停止・状態確認・自動起動設定をすべてこのコマンドで行う。 systemd が管理する対象は「ユニット」と呼ばれ、種別ごとに拡張子が異なる。 | ユニット種別 | 拡張子 | 内容 | | ------------ | ---------- | -------------------------------------- | | サービス | `.service` | デーモン・プロセス | | ターゲット | `.target` | ユニットのグループ(旧ランレベル相当) | | ソケット | `.socket` | ソケット起動の待ち受け | | マウント | `.mount` | ファイルシステムのマウント点 | ### 主要な systemctl サブコマンド ```bash systemctl status sshd systemctl start sshd systemctl stop sshd systemctl restart sshd systemctl enable sshd systemctl disable sshd ``` ```output ● sshd.service - OpenSSH server daemon Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; preset: enabled) Active: active (running) since Fri 2026-05-30 09:00:00 JST; 2h ago ``` `status` の `Active:` 行が現在の稼働状態、`Loaded:` 行末の `enabled`/`disabled` が自動起動設定。`start` は今すぐ起動、`enable` は次回ブート以降の自動起動を設定する。 ::: warning `start` と `enable` は別物。`start` は即時起動するが再起動後は元に戻り、`enable` は自動起動を設定するが今すぐは起動しない。両方を同時に行うなら `systemctl enable --now sshd` を使う。 ::: ## ブートターゲットとランレベルはどう対応するのか {#target} systemd のブートターゲットは、SysVinit のランレベル(0〜6)を置き換えた概念。試験では両者の対応表が頻出する。 | SysVinit ランレベル | systemd ターゲット | 状態 | | ------------------- | ------------------- | ------------------------------ | | 0 | `poweroff.target` | システム停止(電源オフ) | | 1 | `rescue.target` | シングルユーザー(レスキュー) | | 2, 3, 4 | `multi-user.target` | マルチユーザー(CUI) | | 5 | `graphical.target` | マルチユーザー+GUI | | 6 | `reboot.target` | 再起動 | ランレベル 2・3・4 はいずれも `multi-user.target` に対応する点に注意。GUI 起動が `graphical.target`(旧ランレベル 5)、CUI 起動が `multi-user.target`(旧ランレベル 3)と覚えると整理しやすい。 ### 既定ターゲットの確認と変更 ```bash systemctl get-default sudo systemctl set-default multi-user.target ``` ```output graphical.target Removed "/etc/systemd/system/default.target". Created symlink /etc/systemd/system/default.target → /usr/lib/systemd/system/multi-user.target. ``` `get-default` は次回ブート時のターゲットを表示し、`set-default` でそれを変更する。`default.target` は実体がシンボリックリンクで、これが指す先が既定ターゲットになる。 ### 実行中にターゲットを切り替える ```bash sudo systemctl isolate multi-user.target runlevel ``` ```output N 5 ``` `isolate` は再起動せずに現在のターゲットを切り替える(指定ターゲットに属さないユニットは停止される)。SysVinit 互換の `runlevel` コマンドは「直前のランレベル」「現在のランレベル」を表示する(上記は直前なし `N`、現在 5)。 ## システムを安全に停止・再起動するには {#shutdown} 停止・再起動は `shutdown` コマンドを基本とする。時刻指定と警告メッセージにより、他ユーザーへの影響を抑えられる。 ### shutdown の時刻指定 ```bash sudo shutdown -h now sudo shutdown -r +10 sudo shutdown -h 23:30 sudo shutdown -c ``` ```output Shutdown scheduled for Fri 2026-05-30 23:30:00 JST, use 'shutdown -c' to cancel. ``` `-h` は停止(halt/poweroff)、`-r` は再起動。時刻は `now`(即時)、`+m`(m 分後)、`hh:mm`(時刻指定)で渡す。`shutdown -c` は予約された停止をキャンセルする。 ### 関連コマンドの整理 | コマンド | 動作 | | ---------------- | -------------------------------------------- | | `shutdown -h +m` | m 分後に停止(推奨。警告を全ユーザーに通知) | | `shutdown -r +m` | m 分後に再起動 | | `reboot` | 即時再起動(`systemctl reboot` 相当) | | `poweroff` | 即時に電源オフ(`systemctl poweroff` 相当) | | `halt` | システム停止(電源オフは伴わない場合がある) | | `wall` | ログイン中の全ユーザーへメッセージ送信 | ```bash echo "10分後にメンテナンス再起動します" | wall ``` `shutdown` は予約時に自動で `wall` 相当の警告を送る。手動で任意の通知を送りたい場合は `wall` を単体で使う。 ## ブートのトラブルはどこを見るのか {#logs} ブートに関する問題は、カーネルメッセージとブートログから切り分ける。`dmesg` と `journalctl` が二本柱。 ```bash dmesg | less journalctl -k journalctl -b journalctl -b -1 ``` ```output [ 0.000000] Linux version 6.1.0-18-amd64 ... [ 1.234567] EXT4-fs (sda1): mounted filesystem ... ``` `dmesg` はカーネルリングバッファ(ハードウェア検出・ドライバのメッセージ)を表示する。`journalctl -k` はカーネルメッセージのみ、`journalctl -b` は今回のブート、`journalctl -b -1` は前回のブートのログを表示する。直前のブートで失敗した原因を `-b -1` で追えるのが systemd の利点。 ::: tip `systemd-analyze` を使うとブート所要時間を分析できる。`systemd-analyze blame` は各サービスの起動にかかった時間を降順で表示し、起動が遅い原因の特定に役立つ。 ::: ## よくあるミスと対処 {#pitfalls} 実務と試験の両方でつまずきやすいポイントを整理する。 ### ミス1: ランレベルとターゲットの対応を逆に覚える ランレベル 5 が `graphical.target`(GUI)、ランレベル 3 が `multi-user.target`(CUI)。「数字が大きい 5 が GUI」と方向で覚える。ランレベル 2・3・4 がすべて `multi-user.target` に集約される点も頻出。 ### ミス2: grub.cfg を直接編集する `grub.cfg` への直接編集は再生成で消える。`/etc/default/grub` を編集 → `grub-mkconfig`(または `update-grub`)で反映が正しい手順。 ### ミス3: enable と start を混同する `enable` は自動起動設定で今すぐは起動せず、`start` は即時起動で再起動後は戻る。両方なら `enable --now`。 ### ミス4: shutdown の時刻指定を誤る `shutdown -h +10` は「10 分後」であって「10 時」ではない。時刻を指定するなら `shutdown -h 22:00` の形式を使う。引数なしの `shutdown`(時刻省略)は環境により `+1` 相当になり即時停止しないため、即時なら明示的に `now` を付ける。 ### ミス5: get-default と isolate を混同する `set-default` は次回ブートの既定ターゲットを変える(永続)。`isolate` は今すぐ切り替えるが永続しない。永続変更と一時変更を区別する。 ## トラブルシューティング {#troubleshooting} ### 症状: enable したのに再起動後サービスが起動していない **原因**: `enable` のみ実行し、依存するターゲットに wantedby されていない、または `start` していないだけで設定自体は正しい **確認**: ```bash systemctl is-enabled sshd systemctl status sshd ``` **対処**: `is-enabled` が `enabled` なら設定は正常。即起動も必要なら `systemctl enable --now sshd` を使う。`disabled` なら `enable` を再実行する。 ### 症状: GUI で起動してしまう(CUI にしたい) **原因**: 既定ターゲットが `graphical.target` になっている **確認**: ```bash systemctl get-default ``` **対処**: `sudo systemctl set-default multi-user.target` で CUI を既定にする。今すぐ切り替えるなら `sudo systemctl isolate multi-user.target` も併用する。 ### 症状: カーネルパラメータを変えたのに反映されない **原因**: `/etc/default/grub` を編集しただけで `grub.cfg` を再生成していない **確認**: ```bash cat /proc/cmdline ``` **対処**: `sudo grub-mkconfig -o /boot/grub/grub.cfg`(Debian系は `update-grub`、RHEL系は `grub2-mkconfig`)を実行し再起動する。`/proc/cmdline` で現在の起動パラメータを照合できる。 ## 作業完了チェックリスト {#checklist} - [ ] ブートの 4 段階を順に説明できる - [ ] `/etc/default/grub` 編集後に `grub-mkconfig` で再生成した - [ ] `systemctl enable --now` で自動起動と即起動を区別できた - [ ] ランレベルとターゲットの対応表を答えられる - [ ] `shutdown -r +10` で時刻指定再起動を実行できた - [ ] `journalctl -b` でブートログを確認できた ## まとめ {#summary} | 場面 | コマンド | 目的 | | -------------- | ------------------------------------- | ---------------------- | | サービス操作 | `systemctl start/enable --now` | 起動と自動起動 | | 状態確認 | `systemctl status` / `is-enabled` | 稼働・自動起動の確認 | | 既定ターゲット | `systemctl get-default/set-default` | 次回ブートの状態 | | 一時切替 | `systemctl isolate ` | 再起動せず切り替え | | GRUB 設定 | `/etc/default/grub` → `grub-mkconfig` | 起動オプション変更 | | 停止・再起動 | `shutdown -h/-r` | 時刻指定で安全に | | ログ調査 | `dmesg` / `journalctl -b` | ブート時の問題切り分け | システム起動と systemd はサーバー運用のすべての起点。ログ管理やプロセス制御と組み合わせると、障害時の切り分け力が一段上がる。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [システムログの確認と管理 - journalctl/rsyslog](/articles/lpic/system-logging) - [プロセス優先度の制御 - niceとreniceの仕組み](/articles/lpic/process-priorities-nice) - [LPIC-1 出題範囲完全ガイド - 101 / 102 全トピック対応表](/articles/lpic/exam-scope-101-102) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # LPIC-1 チートシート・コマンド一覧・用語集(101/102 完全リファレンス) Source: https://penguin-gym-linux.com/articles/lpic/cheat-sheet-commands ## この記事で達成できること {#intro} - LPIC-1 試験 101 / 102 の頻出コマンド 73 種以上を 1 ページで一覧確認できる - 重要ファイルパス・設定ファイルを分野別に整理して暗記できる - 試験頻出用語の定義を 1〜2 行で素早く確認できる - 暗記カード形式で試験直前の総点検ができる - 仮想ターミナルへの導線から実際にコマンドを実行して記憶を定着させられる LPIC-1(101-500 / 102-500)は Objectives v5.0 に基づき、101 試験が Topic 101〜104、102 試験が Topic 105〜110 を出題範囲とする。このチートシートは試験直前の総点検・暗記補助を目的とした索引記事だ。個別トピックの詳細は各リンク先記事で学習する。 ## LPIC-1 試験構造と Topic 番号対応表 {#exam-structure} LPIC-1 は 2 つの独立した試験で構成される。どちらも Weight 合計 60、試験時間 90 分、60 問形式で実施される。 | 試験 | Topic 番号 | 主要範囲 | | ------- | -------------- | ------------------------------------------------------------------------------- | | 101-500 | Topic 101〜104 | システムアーキテクチャ / パッケージ管理 / GNU・Unix コマンド / ファイルシステム | | 102-500 | Topic 105〜110 | シェル / GUI / 管理タスク / システムサービス / ネットワーク / セキュリティ | 各試験は独立して受験可能。合格点は 500 点(200〜800 点スコア方式)。Objectives v5.0 では v4.0 から Topic 104.4(ディスククォータの管理)が削除されたため、旧問題集との差分に注意する。 ### Topic 別 Weight(出題比重) Weight が高いほど出題頻度が高い。学習優先度の指標として活用する。 **試験 101-500(Topic 101〜104)** | Topic | 内容 | Weight 合計 | | ----- | ------------------------------------- | ----------- | | 101 | システムアーキテクチャ | 8 | | 102 | Linux のインストールとパッケージ管理 | 12 | | 103 | GNU と Unix コマンド | 28 | | 104 | デバイス、Linux ファイルシステム、FHS | 12 | **試験 102-500(Topic 105〜110)** | Topic | 内容 | Weight 合計 | | ----- | ------------------------ | ----------- | | 105 | シェル・シェルスクリプト | 8 | | 106 | ユーザーインターフェース | 4 | | 107 | 管理タスク | 12 | | 108 | 必須システムサービス | 10 | | 109 | ネットワークの基礎 | 14 | | 110 | セキュリティ | 10 | ## LPIC-1 101 試験 頻出コマンド一覧 {#cmd-101} ### Topic 101: システムアーキテクチャ(101 試験) ハードウェア認識・起動シーケンス・ランレベル / ブートターゲット操作が範囲。 | コマンド | 用途 | Objective | | ------------ | -------------------------------------------- | --------- | | `lsusb` | USB デバイス一覧を表示 | 101.1 | | `lspci` | PCI デバイス一覧を表示 | 101.1 | | `lsmod` | ロード済みカーネルモジュール一覧 | 101.1 | | `modprobe` | カーネルモジュールの追加・削除 | 101.1 | | `dmesg` | カーネルリングバッファのメッセージ表示 | 101.1 | | `lsblk` | ブロックデバイス一覧を表示 | 101.1 | | `systemctl` | systemd ユニットの起動・停止・ステータス確認 | 101.3 | | `journalctl` | systemd ジャーナルのログ閲覧 | 101.3 | | `shutdown` | システムのシャットダウンとリブート | 101.3 | | `reboot` | システムの即時再起動 | 101.3 | ### Topic 102: Linux のインストールとパッケージ管理(101 試験) Debian 系・RPM 系パッケージ管理ツールと共有ライブラリ管理が範囲。 | コマンド | 用途 | Objective | | ----------- | -------------------------------------------------- | --------- | | `dpkg` | Debian パッケージの直接操作(インストール / 照会) | 102.4 | | `apt` | Debian 系パッケージ管理(推奨フロントエンド) | 102.4 | | `apt-get` | Debian 系パッケージ管理(スクリプト向け) | 102.4 | | `apt-cache` | パッケージキャッシュの検索・情報表示 | 102.4 | | `rpm` | RPM パッケージの直接操作 | 102.5 | | `yum` | RPM 系パッケージ管理(RHEL / CentOS 7 以前) | 102.5 | | `dnf` | RPM 系パッケージ管理(RHEL / CentOS 8 以降) | 102.5 | | `ldd` | 共有ライブラリの依存関係を表示 | 102.3 | | `ldconfig` | 共有ライブラリのキャッシュ更新 | 102.3 | ### Topic 103-104: GNU・Unix コマンドとファイルシステム(101 試験) GNU と Unix コマンド(Topic 103)の代表的なファイル管理コマンドと、デバイス・ファイルシステム管理(Topic 104)を統合して列挙する。Weight 合計 28(Topic 103)+ 12(Topic 104)の広範な範囲で 101 試験の核心領域。 | コマンド | 用途 | Objective | | --------- | -------------------------------------------------- | ------------- | | `bash` | Bourne Again Shell の起動、スクリプト実行 | 103.1 | | `echo` | 文字列の標準出力 | 103.1 | | `type` | コマンドの種別(組み込み / 外部 / エイリアス)確認 | 103.1 | | `which` | コマンドのフルパスを表示 | 103.1 | | `history` | コマンド実行履歴の表示と再利用 | 103.1 | | `cat` | ファイル内容の表示と連結 | 103.2 | | `cut` | フィールド・文字単位のテキスト抽出 | 103.2 | | `sort` | テキストの並び替え | 103.2 | | `grep` | パターンマッチによるテキスト行抽出 | 103.2 / 103.7 | | `sed` | ストリームエディタ(置換・削除・変換) | 103.2 | | `ls` | ファイル・ディレクトリ一覧(`-la` で詳細表示) | 103.3 | | `cp` | ファイル・ディレクトリのコピー | 103.3 | | `mv` | ファイル・ディレクトリの移動 / 改名 | 103.3 | | `rm` | ファイル・ディレクトリの削除(`-r` 再帰) | 103.3 | | `mkdir` | ディレクトリの作成(`-p` 親ディレクトリも作成) | 103.3 | | `find` | ファイル検索(名前 / タイプ / パーミッション) | 103.3 / 104.7 | | `tar` | アーカイブ作成・展開(gzip / bzip2 対応) | 103.3 | | `chmod` | ファイルパーミッション変更(数値 / 記号表記) | 104.5 | | `chown` | ファイルの所有者 / グループ変更 | 104.5 | 101 試験の全コマンドを仮想ターミナル [/terminal.html?category=lpic](/terminal?category=lpic) で実行練習できる。 ## LPIC-1 102 試験 頻出コマンド一覧 {#cmd-102} ### Topic 105: シェル・シェルスクリプト(102 試験) シェル環境変数の管理とシェルスクリプト記述が範囲。両 Objective が Weight 4 と高配点。 | コマンド | 用途 | Objective | | -------- | ----------------------------------------------------------- | --------- | | `set` | シェル変数・オプションの表示と設定 | 105.1 | | `unset` | シェル変数・関数の削除 | 105.1 | | `export` | 環境変数としてサブシェルへ継承 | 105.1 | | `env` | 環境変数の一覧表示 / 変更した環境でコマンド実行 | 105.1 | | `alias` | コマンドエイリアスの定義と一覧 | 105.1 | | `source` | シェルスクリプトを現在のシェルで実行(`. file` 形式も同義) | 105.1 | ### Topic 106: ユーザーインターフェースとデスクトップ(102 試験) X Window System の基本設定とディスプレイ管理が範囲。Weight が低いが X11 の基礎は把握する。 | コマンド | 用途 | Objective | | -------- | -------------------------------------- | --------- | | `Xorg` | X Window System サーバーの起動 | 106.1 | | `xrandr` | ディスプレイ解像度・回転の設定 | 106.1 | | `startx` | X セッションの開始(`xinit` ラッパー) | 106.1 | ### Topic 107: 管理タスク(102 試験) ユーザー / グループ管理・cron・at・ロケール設定が範囲。107.1 は Weight 5 で 102 試験最高配点。 | コマンド | 用途 | Objective | | ------------- | --------------------------------- | --------- | | `useradd` | 新規ユーザーアカウントの作成 | 107.1 | | `usermod` | 既存ユーザーアカウントの変更 | 107.1 | | `userdel` | ユーザーアカウントの削除 | 107.1 | | `passwd` | ユーザーパスワードの設定・変更 | 107.1 | | `groupadd` | 新規グループの作成 | 107.1 | | `crontab` | cron ジョブの登録・編集・一覧表示 | 107.2 | | `at` | 指定時刻に一回だけコマンドを実行 | 107.2 | | `locale` | 現在のロケール設定の表示 | 107.3 | | `date` | システム日付・時刻の表示と設定 | 107.3 | | `timedatectl` | systemd のタイムゾーン・NTP 設定 | 107.3 | ### Topic 108: 必須システムサービス(102 試験) syslog / rsyslog・ログローテーション・MTA・時刻同期が範囲。 | コマンド | 用途 | Objective | | ----------- | ---------------------------------------------- | --------- | | `rsyslogd` | syslog デーモン(rsyslog)の起動と設定 | 108.2 | | `logrotate` | ログファイルのローテーション設定と実行 | 108.2 | | `mail` | コマンドラインからメール送受信(MTA テスト用) | 108.3 | | `ntpd` | NTP デーモン(ntpd)による時刻同期 | 108.1 | | `chronyd` | chrony デーモンによる時刻同期(ntpd の代替) | 108.1 | ### Topic 109: ネットワークの基礎(102 試験) IP 設定・ルーティング・ソケット確認・DNS 解決が範囲。Weight 合計 14 と 102 試験で最大。 | コマンド | 用途 | Objective | | ------------ | ----------------------------------------------------------- | --------- | | `ip` | ネットワークインターフェース / ルーティング設定(iproute2) | 109.1 | | `ifconfig` | ネットワークインターフェース設定(旧 net-tools) | 109.1 | | `route` | ルーティングテーブルの表示と設定(旧 net-tools) | 109.1 | | `ss` | ソケット統計・接続状態の確認(iproute2) | 109.1 | | `netstat` | ネットワーク接続・ポートの確認(旧 net-tools) | 109.1 | | `ping` | ICMP Echo による疎通確認 | 109.3 | | `traceroute` | パケット経路の追跡 | 109.3 | | `dig` | DNS クエリの実行と詳細応答確認 | 109.3 | | `host` | DNS 正引き / 逆引き(簡易) | 109.3 | | `nslookup` | DNS 照会(対話モード対応) | 109.3 | ### Topic 110: セキュリティ(102 試験) sudo / su・SSH・GPG・iptables が範囲。実務直結の内容が多く Weight も高い。 | コマンド | 用途 | Objective | | ---------- | ---------------------------------------------- | --------- | | `sudo` | 別ユーザー(通常 root)権限でコマンド実行 | 110.1 | | `su` | ユーザー切り替え(パスワード認証) | 110.1 | | `chage` | パスワード有効期限の確認と設定 | 110.1 | | `ssh` | SSH による安全なリモートログイン | 110.3 | | `gpg` | GnuPG による暗号化・署名・鍵管理 | 110.4 | | `iptables` | Linux ファイアウォール(パケットフィルタ)設定 | 110.2 | 102 試験の全コマンドを仮想ターミナル [/terminal.html?category=lpic](/terminal?category=lpic) で実行練習できる。 ## 重要ファイルパス・設定ファイルキー一覧 {#paths} ### 起動・ブート関連 | ファイルパス | 用途 | | --------------------- | ------------------------------------------------------- | | `/boot/grub/grub.cfg` | GRUB2 の実行時設定ファイル(自動生成、直接編集不可) | | `/etc/default/grub` | GRUB2 のユーザー設定ファイル(編集後 update-grub 実行) | | `/etc/fstab` | ファイルシステムの自動マウント設定 | | `/etc/inittab` | SysV init のランレベル設定(systemd 環境では不使用) | ### 認証・ユーザー管理 | ファイルパス | 用途 | | -------------- | ---------------------------------------------------------- | | `/etc/passwd` | ユーザーアカウント情報(UID / GID / ホームディレクトリ等) | | `/etc/shadow` | パスワードハッシュと有効期限(root のみ読取可) | | `/etc/group` | グループ定義(グループ名 / GID / メンバーリスト) | | `/etc/sudoers` | sudo 実行権限の設定(`visudo` で編集) | ### ネットワーク設定 | ファイルパス | 用途 | | ------------------------- | ------------------------------------------- | | `/etc/hosts` | 静的ホスト名 → IP アドレスのマッピング | | `/etc/resolv.conf` | DNS サーバーアドレスと検索ドメインの設定 | | `/etc/nsswitch.conf` | 名前解決順序(files / dns / nis 等)の設定 | | `/etc/network/interfaces` | Debian 系のネットワークインターフェース設定 | ### ログ | ファイルパス | 用途 | | ------------------- | ------------------------------------------------------- | | `/var/log/messages` | 一般システムメッセージ(RHEL / CentOS 系) | | `/var/log/syslog` | 一般システムメッセージ(Debian / Ubuntu 系) | | `/var/log/auth.log` | 認証・sudo・SSH のログ(Debian 系) | | `journalctl` | systemd ジャーナル(バイナリ形式、`-u` でユニット指定) | ### cron・サービス | ファイルパス | 用途 | | ---------------------- | ------------------------------------------------ | | `/etc/crontab` | システム全体の cron ジョブ定義(ユーザー列あり) | | `/etc/cron.d/` | アプリケーション別の cron ジョブ断片 | | `/etc/systemd/system/` | カスタム systemd ユニットファイルの配置先 | ## LPIC-1 用語集 {#glossary} ### ブート関連 **GRUB**: GRand Unified Bootloader。BIOS / UEFI からカーネルを起動するブートローダ。設定ファイルは `/etc/default/grub`(ユーザー編集)と `/boot/grub/grub.cfg`(自動生成)。 **BIOS / UEFI**: システム起動の最初段階を担うファームウェア。UEFI は GPT パーティションテーブルをサポートし、セキュアブートに対応する。 **initrd / initramfs**: カーネルロード直後に一時的に使われる初期 RAM ディスク / ファイルシステム。本物のルートファイルシステムをマウントするための最小環境を提供する。 **systemd**: Linux の PID 1 として動作するシステム・サービスマネージャ。SysV init を置き換え、並列起動・依存管理・ジャーナルログを提供する。 **SysV init**: 従来の init システム。`/etc/inittab` でランレベルを設定し、`/etc/rc*.d/` 配下のスクリプトでサービスを管理する。 **ランレベル / ターゲット**: SysV init のランレベル(0〜6)と systemd ターゲット(`multi-user.target` / `graphical.target` 等)の対応関係が出題される。 ### パッケージ管理 **APT**: Advanced Package Tool。Debian / Ubuntu 系で使用されるパッケージ管理システム。`apt` / `apt-get` / `apt-cache` がフロントエンド。 **DPKG**: Debian パッケージシステムの低レベルツール。`.deb` パッケージを直接操作する。`dpkg -i`(インストール)/ `dpkg -l`(一覧)/ `dpkg -r`(削除)。 **RPM**: Red Hat Package Manager。`.rpm` パッケージを直接操作する低レベルツール。`rpm -i`(インストール)/ `rpm -q`(照会)/ `rpm -e`(削除)。 **YUM / DNF**: RPM 系パッケージ管理のフロントエンド。YUM は RHEL 7 以前、DNF は RHEL 8 以降(Fedora 22 以降)の標準。リポジトリから依存関係を自動解決してインストールする。 **リポジトリ**: パッケージの配布源。Debian 系は `/etc/apt/sources.list`、RPM 系は `/etc/yum.repos.d/*.repo` で設定する。 ### ファイルシステム **inode**: ファイルのメタデータ(パーミッション / 所有者 / タイムスタンプ / データブロック位置)を格納するデータ構造。ファイル名は inode を参照するエントリとして別管理される。 **スーパーブロック**: ファイルシステム全体の管理情報(サイズ / ブロック数 / inode 数)を格納する領域。 **ext4**: Linux 標準的なジャーナリングファイルシステム。`mkfs.ext4` で作成し `fsck.ext4` で整合性確認する。 **XFS**: 高性能・大容量向けジャーナリングファイルシステム。RHEL 7 以降のデフォルト。 **Btrfs**: スナップショット・RAID・オンラインリサイズ対応のファイルシステム。 **スワップ**: RAM 不足時に仮想メモリとして使用するディスク領域。`swapon` / `swapoff` で有効 / 無効化する。 ### プロセス管理 **nice / renice**: プロセス優先度(-20〜19、低いほど優先)の設定と変更。`nice -n <値> <コマンド>` で起動時指定、`renice` で実行中のプロセスを変更する。 **PID**: Process ID。`ps` / `top` で確認し、`kill ` でシグナルを送信する。 **ulimit**: シェルが起動するプロセスのリソース制限(ファイルサイズ / プロセス数 / ファイルデスクリプタ数等)を設定する。 **シグナル**: プロセスへの非同期通知。SIGTERM(15: 正常終了要求)/ SIGKILL(9: 強制終了、ハンドル不可)/ SIGHUP(1: 再読み込み)が頻出。 ### ネットワーク **TCP/IP**: インターネットの基礎となるプロトコルスタック。TCP(信頼性あり)と UDP(信頼性なし)の違い、ポート番号の役割が出題範囲。 **IPv4 / IPv6**: IPv4 は 32 ビットアドレス(例: 192.168.1.0)、IPv6 は 128 ビットアドレス(例: ::1)。 **CIDR**: Classless Inter-Domain Routing。`/24` のようにプレフィックス長でサブネットを表記する。 **DNS**: Domain Name System。`/etc/resolv.conf` でサーバーを指定し、`/etc/nsswitch.conf` で解決順序を設定する。 **NetworkManager**: デスクトップ / サーバー向けネットワーク設定デーモン。`nmcli` コマンドでコマンドライン操作できる。 ### セキュリティ **SSH**: Secure Shell。公開鍵認証と暗号化通信によるリモートログイン。`~/.ssh/authorized_keys` に公開鍵を登録する。 **GPG**: GNU Privacy Guard。`gpg --gen-key`(鍵生成)/ `gpg -e`(暗号化)/ `gpg -s`(署名)/ `gpg --verify`(検証)。 **sudo**: `sudoers` ファイルで許可されたユーザーが root 権限でコマンドを実行する仕組み。`visudo` で設定を編集する。 **PAM**: Pluggable Authentication Modules。認証処理をモジュール化する仕組み。設定は `/etc/pam.d/` 配下。 **SELinux**: Security-Enhanced Linux。MAC(強制アクセス制御)を実装するセキュリティモジュール。RHEL 系のデフォルト。 ## 試験直前の暗記カード {#flashcards} 試験直前の総点検に活用する。口頭で問いを読み上げて答えを確認するのが効果的だ。 | 問 | 答 | | ---------------------------------------------------- | --------------------------------------------------------- | | GRUB2 のユーザー設定ファイルは? | `/etc/default/grub` | | `update-grub` 後に生成される設定ファイルは? | `/boot/grub/grub.cfg` | | systemd でターゲットを変更するコマンドは? | `systemctl isolate ` | | ランレベル 3 に対応する systemd ターゲットは? | `multi-user.target` | | ランレベル 5 に対応する systemd ターゲットは? | `graphical.target` | | ユーザーのパスワードハッシュが格納されるファイルは? | `/etc/shadow` | | sudo の設定ファイルを安全に編集するコマンドは? | `visudo` | | DNS サーバーを設定するファイルは? | `/etc/resolv.conf` | | 名前解決の順序を設定するファイルは? | `/etc/nsswitch.conf` | | ファイルシステムの自動マウント設定ファイルは? | `/etc/fstab` | | `dpkg -l` で確認できる情報は? | インストール済みパッケージの一覧と状態 | | `rpm -qa` で確認できる情報は? | インストール済み全パッケージの一覧 | | 共有ライブラリのキャッシュを更新するコマンドは? | `ldconfig` | | カーネルモジュールをロードするコマンドは? | `modprobe <モジュール名>` | | ロード済みカーネルモジュールを確認するコマンドは? | `lsmod` | | プロセスに正常終了シグナルを送るコマンドは? | `kill -15 ` または `kill -TERM ` | | プロセスに強制終了シグナルを送るコマンドは? | `kill -9 ` または `kill -KILL ` | | `nice` 値の範囲と優先度の関係は? | -20〜19 の範囲、値が小さいほど優先度が高い | | cron ジョブを編集するコマンドは? | `crontab -e`(ユーザー自身)または `crontab -e -u ` | | `at` コマンドで一時的なジョブを確認するコマンドは? | `atq` | | SSH の公開鍵を配置するファイルは? | `~/.ssh/authorized_keys` | | `iptables -L` で確認できる情報は? | 現在のファイアウォールルール一覧 | | inode に格納されない情報は? | ファイル名(ファイル名はディレクトリエントリが管理する) | | `find / -perm -4000` で検索できるファイルは? | SUID が設定されたファイル | | `tar -czvf` の各オプションの意味は? | c: 作成、z: gzip、v: 詳細表示、f: ファイル名指定 | # クライアント DNS 設定 - resolv.conf/hosts/getent【LPIC-1 109.4】 Source: https://penguin-gym-linux.com/articles/lpic/client-dns ## この記事で達成できること {#intro} - `/etc/resolv.conf` の `nameserver` / `search` / `options` を読み書きできる - `/etc/hosts` による静的名前解決と DNS の優先順位を説明できる - `/etc/nsswitch.conf` の `hosts:` 行で解決順(`files dns`)を制御できる - `getent` で NSS データベースを横断的に照会できる - `systemd-resolved`(`resolvectl`・stub resolver `127.0.0.53`)の挙動を把握できる - `host` / `dig` で名前解決の問題を切り分けられる LPIC-1 主題 109.4「クライアント側の DNS を設定する」の中核。名前解決がどのファイルとどの順序で行われるかを理解すれば、接続トラブルの大半は切り分けられる。 ## クライアント名前解決はどの順で起きるのか {#flow} 名前解決はアプリが GNU C ライブラリ(glibc)の `getaddrinfo()` を呼ぶことで始まり、`/etc/nsswitch.conf` の `hosts:` 行が参照ソースと順序を決める。`files`(= `/etc/hosts`)が `dns` より前にあれば、hosts ファイルが DNS より優先される。 | 段階 | 参照先 | 役割 | | ------- | -------------------- | -------------------------------------------------------- | | 1. 順序 | `/etc/nsswitch.conf` | `hosts:` 行で `files` / `dns` の順を決定 | | 2. 静的 | `/etc/hosts` | 手書きの IP↔ホスト名対応(DNS より先に一致すれば即決定) | | 3. DNS | `/etc/resolv.conf` | 問い合わせ先 `nameserver` と `search` ドメイン | つまり「`getent hosts` の結果が DNS と違う」場合、まず疑うのは `nsswitch.conf` の順序と `/etc/hosts` の記述。試験でも実務でもこの 3 ファイルの関係が最重要。 ::: tip `getent hosts` は nsswitch.conf 経由(hosts と DNS 両方)の結果を返すが、`host` / `dig` は DNS サーバーに直接問い合わせる。両者の結果が食い違うときは `/etc/hosts` の関与を疑う。 ::: ## /etc/resolv.conf の設定 {#resolv-conf} `/etc/resolv.conf` は DNS リゾルバの設定ファイル。`nameserver`(問い合わせ先 IP)、`search`(補完ドメイン)、`options` が主要ディレクティブ。 ### nameserver / search / domain / options ```bash cat /etc/resolv.conf ``` ```output nameserver 192.168.1.1 nameserver 8.8.8.8 search example.com lan options timeout:2 attempts:3 ``` `man resolv.conf(5)` に基づく主要ディレクティブは次のとおり。 | ディレクティブ | 意味 | | ------------------ | ----------------------------------------------------------------------- | | `nameserver IP` | 問い合わせる DNS サーバー。最大 3 件(`MAXNS`)まで。上から順に試行 | | `search dom1 dom2` | 短い名前に補完するドメインのリスト(例 `web` → `web.example.com`) | | `domain name` | ローカルドメイン名。`search` と相互排他で、後に書いた方が有効 | | `options name` | リゾルバ動作の調整(`timeout:n` / `attempts:n` / `rotate` / `ndots:n`) | `search` と `domain` は両方あると最後に出現したものが採用される(`man resolv.conf(5)`)。`ndots:n` はドット数が n 未満の名前を絶対名でなく相対名として扱い `search` を適用する閾値。 ::: warning 多くのディストリビューションで `/etc/resolv.conf` は NetworkManager や systemd-resolved が**自動生成・上書き**する。手編集しても再起動や接続変更で消えることがある。恒久設定は生成元(後述)で行う。 ::: ## /etc/hosts と /etc/nsswitch.conf {#hosts-nsswitch} `/etc/hosts` は IP とホスト名を静的に対応づけるファイル。`/etc/nsswitch.conf` の `hosts:` 行で、この静的定義と DNS のどちらを先に引くかが決まる。 ### /etc/hosts の書式 ```bash cat /etc/hosts ``` ```output 127.0.0.1 localhost ::1 localhost ip6-localhost 192.168.1.50 web.example.com web ``` `man hosts(5)` によると書式は「`IPアドレス 正規ホスト名 別名...`」。1 行に IP とそのホスト名・エイリアスを空白区切りで並べる。DNS を介さず即座に解決されるため、テスト用の名前固定やローカル開発で多用する。 ### nsswitch.conf の hosts: 行 ```bash grep hosts /etc/nsswitch.conf ``` ```output hosts: files dns ``` `hosts:` の値が解決の参照順。`files`(`/etc/hosts`)→ `dns`(`/etc/resolv.conf` のサーバー)の順に試す設定。`files` が先なので `/etc/hosts` に一致があれば DNS は引かれない。systemd 環境では `files resolve [!UNAVAIL=return] dns` のように `resolve`(systemd-resolved の NSS モジュール `nss-resolve`)が入る場合がある。 ::: tip `hosts:` を `dns files` の順に書くと DNS が優先され、`/etc/hosts` の固定が効かなくなる。順序が想定外の挙動を生む典型例なので、トラブル時は必ずこの行を確認する。 ::: ## getent / host / dig での確認 {#verify} `getent` は NSS データベース(`hosts` / `passwd` / `group` 等)を nsswitch.conf 経由で照会するコマンド。`host` / `dig` は DNS サーバーへ直接問い合わせる。両者を使い分けると、名前解決の問題が hosts 側か DNS 側かを切り分けられる。 ### getent で NSS を引く ```bash getent hosts web.example.com getent hosts 8.8.8.8 ``` ```output 192.168.1.50 web.example.com web 8.8.8.8 dns.google ``` `getent hosts NAME` は nsswitch.conf の `hosts:` 行に従い、`/etc/hosts` と DNS を合わせた最終結果を返す。`getent` は `passwd` / `group` / `services` 等のデータベースも引ける(例 `getent passwd root`)。アプリが実際に得る解決結果に最も近いのがこの出力。 ### host / dig で DNS に直接問い合わせる ```bash host example.com dig example.com A +short ``` ```output example.com has address 93.184.216.34 93.184.216.34 ``` `host NAME` は DNS の正引き、`host IP` は逆引きを行う。`dig NAME A` は A レコードを問い合わせ、`+short` で応答のみを抽出する。`dig @8.8.8.8 example.com` のように `@サーバー` で問い合わせ先を明示でき、`/etc/resolv.conf` を介さずに特定サーバーへ直接照会できる。 ::: tip 切り分けの定石は次の対比。`getent hosts X` は成功するが `dig X` が失敗 → `/etc/hosts` で解決されている。`dig X` は成功するが `getent hosts X` が失敗 → nsswitch.conf の順序か NSS モジュールの問題。 ::: ## systemd-resolved の設定 {#systemd-resolved} `systemd-resolved` はネットワークネームレゾリューションを提供するシステムサービス。stub resolver として `127.0.0.53` を listen し、`/etc/resolv.conf` はこのアドレスを指すよう生成されることが多い。状態確認と設定は `resolvectl` で行う。 ### resolvectl で状態と解決を確認する ```bash resolvectl status resolvectl query example.com ``` ```output Global Protocols: -LLMNR +mDNS ... Link 2 (eth0) DNS Servers: 192.168.1.1 DNS Domain: example.com example.com: 93.184.216.34 ``` `resolvectl status` は現在の DNS サーバー・検索ドメインをリンクごとに表示する。`resolvectl query NAME` は resolved 経由で名前解決する。systemd-resolved 環境では `/etc/resolv.conf` の `nameserver` が `127.0.0.53`(stub resolver)になり、実際の上流サーバーは `resolvectl status` で確認する点に注意。 ### resolved.conf と resolv.conf の関係 ```bash cat /etc/resolv.conf grep -v '^#' /etc/systemd/resolved.conf ``` ```output nameserver 127.0.0.53 options edns0 trust-ad search example.com [Resolve] DNS=192.168.1.1 FallbackDNS=8.8.8.8 ``` 恒久的な上流 DNS は `/etc/systemd/resolved.conf` の `[Resolve]` セクション(`DNS=` / `FallbackDNS=`)で指定し、`systemctl restart systemd-resolved` で反映する。`/etc/resolv.conf` 直接編集ではなく、生成元のこのファイルを編集するのが正しい運用。 ::: warning systemd-resolved 環境で `/etc/resolv.conf` の `127.0.0.53` を実 IP に書き換えても、サービスやネットワーク再起動で元に戻る。上流変更は `resolved.conf` で行う。`/etc/resolv.conf` が `/run/systemd/resolve/stub-resolv.conf` へのシンボリックリンクになっている構成もある。 ::: ## /etc/host.conf の役割 {#host-conf} `/etc/host.conf` は古くからあるリゾルバ設定ファイルで、現在の glibc では限定的な項目(主に `multi`)のみが意味を持つ。解決順の中心は nsswitch.conf に移っている。 ```bash cat /etc/host.conf ``` ```output multi on ``` `man host.conf(5)` によると、glibc では `order` 行は無視され、解決順は `/etc/nsswitch.conf` が決める。`multi on` は `/etc/hosts` 内で同一ホストに複数 IP がある場合にすべて返す指定。歴史的経緯で残るファイルであり、現在の解決順制御は nsswitch.conf 側で行う点を押さえておけばよい。 ## よくあるミス {#pitfalls} 名前解決トラブルの多くは設定ファイルの誤解に起因する。試験でも実務でも頻出する 5 つを挙げる。 1. **resolv.conf を手編集して消える**: NetworkManager / systemd-resolved が再生成するため手編集は揮発する。生成元(`resolved.conf` や NetworkManager 接続設定)で変更する。 2. **nsswitch.conf の順序を誤解**: `hosts: dns files` にすると DNS が優先され、`/etc/hosts` の固定が無視される。`/etc/hosts` を効かせたいなら `files` を先に置く。 3. **hosts と DNS の優先順位を取り違える**: `/etc/hosts` に一致があれば(`files` が先なら)DNS は引かれない。DNS を引いていないつもりが hosts に古い行が残っているケース。 4. **systemd-resolved の stub を上流と混同**: `/etc/resolv.conf` の `127.0.0.53` は stub であり実際の上流ではない。上流は `resolvectl status` で確認する。 5. **getent と dig の差を理解していない**: `getent hosts` は `/etc/hosts` を含むが `dig` は DNS のみ。結果が食い違うのは正常で、切り分けに使える。 ## トラブルシューティング {#troubleshooting} ### 症状: 編集した resolv.conf がすぐ元に戻る **原因**: NetworkManager または systemd-resolved が `/etc/resolv.conf` を自動生成・上書きしている **確認**: ```bash ls -l /etc/resolv.conf resolvectl status ``` **対処**: systemd-resolved 環境なら `/etc/systemd/resolved.conf` の `DNS=` を編集し `systemctl restart systemd-resolved`。NetworkManager 環境なら接続プロファイルで DNS を設定する。 ### 症状: 特定ホストだけ古い IP に解決される **原因**: `/etc/hosts` に古いエントリが残り、`files` 優先で DNS より先に一致している **確認**: ```bash getent hosts target.example.com grep target.example.com /etc/hosts dig target.example.com +short ``` **対処**: `/etc/hosts` の該当行を修正または削除する。`getent` と `dig` の結果差が hosts 関与の証拠。 ### 症状: getent hosts は引けるが dig が失敗する **原因**: 名前が `/etc/hosts` で解決されており DNS には登録がない **確認**: ```bash grep name /etc/hosts grep hosts /etc/nsswitch.conf ``` **対処**: 設計どおりなら問題なし。DNS で解決させたい場合は `/etc/hosts` の行を削除し、DNS にレコードを登録する。 ## 作業完了チェックリスト {#checklist} - [ ] `cat /etc/resolv.conf` で nameserver / search を確認した - [ ] `grep hosts /etc/nsswitch.conf` で解決順を確認した - [ ] `getent hosts NAME` で実際の解決結果を確認した - [ ] `host` / `dig` で DNS に直接問い合わせて切り分けた - [ ] systemd-resolved 環境では `resolvectl status` で上流を確認した ## まとめ {#summary} | 場面 | コマンド / ファイル | 目的 | | ------------------ | ------------------------------ | ----------------------------- | | DNS サーバー確認 | `/etc/resolv.conf` | nameserver / search の確認 | | 静的名前解決 | `/etc/hosts` | IP↔ホスト名の固定 | | 解決順の制御 | `/etc/nsswitch.conf` | `hosts: files dns` の順序 | | 実解決の確認 | `getent hosts NAME` | NSS 経由の最終結果 | | DNS 直接問い合わせ | `host` / `dig` | DNS サーバーへ直接照会 | | resolved 管理 | `resolvectl` / `resolved.conf` | systemd-resolved の状態と設定 | クライアント DNS はネットワーク運用の基礎。3 ファイル(resolv.conf / hosts / nsswitch.conf)の関係と `getent` / `host` / `dig` の使い分けを押さえれば、名前解決の切り分けは確実に行える。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [インターネットプロトコルの基礎](/articles/lpic/internet-protocols) - [ネットワーク設定](/articles/lpic/network-configuration) - [ネットワークのトラブルシューティング](/articles/lpic/network-troubleshooting) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # コマンドライン基礎 - シェル・bash・コマンド実行の仕組み Source: https://penguin-gym-linux.com/articles/lpic/command-line-basics ## この記事で達成できること {#intro} - シェルとは何か、bash がコマンドを解釈・実行する流れを説明できる - `type` / `which` でコマンドの種別と実体を正確に判別できる - `PATH` の解決順序を理解し「command not found」の原因を特定できる - 引用符(シングル / ダブル / バックスラッシュ)の挙動の違いを使い分けられる - コマンド履歴とヒストリ展開を実務で活用できる LPIC-1 主題 103.1「コマンドラインで操作する」の中核。シェルの動作モデルを正確に把握することが、以降のテキスト処理・プロセス管理すべての土台になる。 ## シェルとコマンド実行の判断フロー {#flow} シェルはユーザーが入力した行を解釈し、コマンドを起動するプログラム。bash(Bourne-Again Shell)が多くのディストリビューションの既定。コマンドを入力したとき、bash はまずコマンドライン解釈段階でエイリアス(`alias` で確認、例 `ll` → `ls -l`)を展開する。エイリアスは検索順序の構成要素ではなく、検索より前の別段階で先に置換される。展開後の語に対して、bash は以下の順序で実体を解決する。 | 順序 | 種別 | 確認コマンド | 例 | | ---- | ----------------------- | ------------- | --------------------- | | 1 | シェル関数 | `declare -F` | ユーザー定義関数 | | 2 | シェル組込みコマンド | `type -a cmd` | `cd` / `echo` / `pwd` | | 3 | `PATH` 上の外部コマンド | `which cmd` | `/bin/ls` | この順序を把握していないと「`echo` は組込みなのか `/bin/echo` なのか」「`time` が動作しない」といった現象を説明できない。 ## 手順 {#steps} ### Step 1: シェルの種別と版を確認 ```bash echo $SHELL bash --version ``` ```output /bin/bash GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu) ``` `$SHELL` はログインシェル、現在対話実行中のシェルは `$0` または `ps -p $$` で確認する。 ### Step 2: コマンドの種別を判定する ```bash type cd type ls type -a echo ``` ```output cd is a shell builtin ls is aliased to `ls --color=auto' echo is a shell builtin echo is /usr/bin/echo ``` `type` は bash 組込みで、解決順序そのものを反映する。`-a` を付けると同名のすべての候補を表示する。外部コマンドのパスだけが欲しい場合は `which` を使う。 ```bash which cp ``` ```output /usr/bin/cp ``` `which` は `PATH` 上の最初の一致のみを返す。組込みやエイリアスは検出しないため、種別判定には `type` が優先される。 ### Step 3: PATH の解決順序を理解する ```bash echo $PATH ``` ```output /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ``` `PATH` はコロン区切りのディレクトリ一覧。bash は外部コマンドを左から順に検索し、最初に見つかった実行ファイルを起動する。同名コマンドが複数ディレクトリに存在する場合、`PATH` の先頭にある方が優先される。 ### Step 4: 引用符でメタ文字を制御する ```bash name=world echo "hello $name" echo 'hello $name' echo hello \$name ``` ```output hello world hello $name hello $name ``` ダブルクオート(`"`)は変数展開・コマンド置換を行うがワード分割とグロブを抑止する。シングルクオート(`'`)はすべての展開を抑止しリテラルとして扱う。バックスラッシュ(`\`)は直後の 1 文字をエスケープする。 ### Step 5: コマンド履歴を活用する ```bash history 5 !! !123 !grep ``` ```output 120 cd /var/log 121 ls -l 122 grep error syslog 123 tail -f syslog 124 history 5 ``` `!!` は直前のコマンド、`!n` は履歴番号 n、`!string` は string で始まる最新コマンドを再実行するヒストリ展開。`Ctrl+r` で履歴のインクリメンタル検索もできる。 ## なぜこの順序か {#why} bash のコマンド検索順序は厳密には「関数 → 組込み → 外部(`PATH`)」で、エイリアスはこの検索順序の一部ではなくコマンドライン解釈段階(検索より前)で先に展開される。ユーザー定義やシェル内部処理を外部コマンドより優先するための設計である。例えば `echo` は組込みと `/usr/bin/echo` の両方が存在するが、組込みが優先されることでサブプロセス起動のコストを避けている。`time` が「コマンドではない」と振る舞うのも、`time` がパイプライン全体を計測する bash の予約語(キーワード)であり、組込みコマンドより前段で解釈されるため。 `PATH` の前方優先は、`/usr/local/bin` に置いた独自ビルドのコマンドをシステム標準より優先させる運用を可能にする。逆に意図せぬ同名コマンドが先に解決されると事故になるため、種別不明時は必ず `type -a` で全候補を確認する。 ## トラブルシューティング {#troubleshooting} ### 症状: command not found が出る **原因**: コマンドが `PATH` 上に存在しない、または `PATH` 自体が壊れている **確認**: ```bash echo $PATH type -a cmdname ``` **対処**: 実行ファイルの所在を `find / -name cmdname -type f 2>/dev/null` で特定し、そのディレクトリを `PATH` に追加する。`PATH` が空に近い場合は `.bashrc` / `.bash_profile` の設定ミスを疑う。 ### 症状: 変数が展開されない **原因**: シングルクオートで囲っている **確認**: ```bash echo '$HOME' echo "$HOME" ``` **対処**: 変数展開が必要な箇所はダブルクオートを使う。リテラルとして `$` を出したい場合のみシングルクオートまたは `\$` を使う。 ### 症状: which と type で結果が食い違う **原因**: `which` は外部コマンドのみ、`type` はエイリアス・組込みも含めて解決順を反映するため **確認**: ```bash type -a ls which ls ``` **対処**: コマンドが実際にどう実行されるかを知りたい場合は常に `type` を信頼する。`which` はスクリプト内で実行ファイルパスを取得する用途に限定する。 ## 作業完了チェックリスト {#checklist} - [ ] `echo $SHELL` と `bash --version` でシェル環境を確認した - [ ] `type -a` で主要コマンドの種別を確認した - [ ] `echo $PATH` で検索パスの順序を把握した - [ ] シングル / ダブルクオートの展開差を実機で確認した - [ ] `history` とヒストリ展開(`!!` / `!n`)を試した ## まとめ {#summary} | 場面 | コマンド | 目的 | | ---------- | ------------- | --------------------------------- | | 種別判定 | `type -a cmd` | エイリアス/関数/組込み/外部を判別 | | 実体パス | `which cmd` | PATH 上の実行ファイルパス取得 | | パス確認 | `echo $PATH` | コマンド検索順序の把握 | | 履歴再実行 | `!!` / `!n` | 直前/番号指定のコマンド再実行 | | 展開抑止 | `'...'` / `\` | メタ文字をリテラル化 | シェルの解決順序と引用符の挙動は、LPIC-1 全体を通じて前提となる基礎。次は変数を永続化するシェル環境設定に進むと理解が連結する。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全12記事と出題範囲マップ)](/lpic1) - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [シェル環境変数の設定](/articles/lpic/shell-environment) - [テキストストリームフィルタ](/articles/lpic/text-stream-filters) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # データの暗号化 - SSH 鍵/GPG/openssl【LPIC-1 110.3】 Source: https://penguin-gym-linux.com/articles/lpic/data-encryption ## この記事で達成できること {#intro} - `ssh-keygen` で RSA / ED25519 の鍵ペアを生成し、用途を説明できる - `ssh-copy-id` で公開鍵をリモートに登録し、公開鍵認証で接続できる - `ssh-agent` / `ssh-add` でパスフレーズ入力を一度だけにできる - SSH ポートフォワーディング(`-L` / `-R` / `-D`)の用途を区別できる - `gpg` でファイルの暗号化・復号、署名・検証ができる - `openssl` でハッシュ計算と対称暗号の基本を実行できる - 鍵ファイルのパーミッション(600 / 700)が認証失敗にどう関わるかを説明できる LPIC-1 主題 110.3「暗号化によってデータを保護する」の中核。通信路の保護(SSH)とデータ自体の保護(GPG / openssl)を分けて理解するのが攻略の鍵。 ## SSH の公開鍵認証はどう動くのか {#ssh-overview} 公開鍵認証は「秘密鍵を持つことの証明」で認証する方式。クライアントの公開鍵をサーバーの `~/.ssh/authorized_keys` に登録し、秘密鍵は手元から出さない。パスワードを回線に流さないため安全性が高い。 | ファイル / ディレクトリ | 役割 | 推奨パーミッション | | ------------------------ | ---------------------------- | ------------------ | | `~/.ssh/` | SSH 関連ファイル格納 | `700` | | `~/.ssh/id_ed25519` | 秘密鍵(手元のみ) | `600` | | `~/.ssh/id_ed25519.pub` | 公開鍵(配布する) | `644` | | `~/.ssh/authorized_keys` | サーバー側の許可鍵一覧 | `600` | | `~/.ssh/known_hosts` | 接続済みサーバーの公開鍵記録 | `644` | 認証の流れは「サーバーがクライアントの公開鍵で乱数を暗号化 → クライアントが秘密鍵で復号して応答 → 秘密鍵保持を証明」というチャレンジ・レスポンス。秘密鍵はネットワークに流れない。 ## 鍵を生成して接続する手順 {#steps} ### Step 1: ssh-keygen で鍵ペアを生成する ```bash ssh-keygen -t ed25519 -C "user@example.com" ``` ```output Generating public/private ed25519 key pair. Enter file in which to save the key (/home/user/.ssh/id_ed25519): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/user/.ssh/id_ed25519 Your public key has been saved in /home/user/.ssh/id_ed25519.pub ``` `-t` で鍵種別を指定する。`man ssh-keygen` によると `-t` の値は `dsa` / `ecdsa` / `ecdsa-sk` / `ed25519` / `ed25519-sk` / `rsa`。現在は ED25519 が推奨で、RSA を使う場合は `-b 4096` のように鍵長を指定する。`-C` はコメント(公開鍵末尾に付く識別ラベル)。パスフレーズは秘密鍵を保護する追加の鍵で、空にもできるが設定が安全。 ```bash ssh-keygen -t rsa -b 4096 ``` RSA を選ぶ場面は、ED25519 を解釈できない古いサーバーと接続する必要がある場合などに限られる。 ### Step 2: 公開鍵をリモートに登録する ```bash ssh-copy-id user@server.example.com ``` ```output Number of key(s) added: 1 Now try logging into the machine, with: "ssh 'user@server.example.com'" and check to make sure that only the key(s) you wanted were added. ``` `ssh-copy-id` は公開鍵をリモートの `~/.ssh/authorized_keys` に追記し、パーミッションも調整する。手動で行う場合は公開鍵の内容を `authorized_keys` に 1 行追加する。秘密鍵(`.pub` の付かない方)は決して送らない。 ### Step 3: 公開鍵認証で接続する ```bash ssh user@server.example.com ``` ```output Enter passphrase for key '/home/user/.ssh/id_ed25519': Last login: Fri May 30 10:00:00 2026 from 203.0.113.10 user@server:~$ ``` 公開鍵が登録済みなら、パスワードではなく秘密鍵のパスフレーズだけで入れる。問題があるときは `ssh -v user@host` で詳細ログを確認する。 ### Step 4: ssh-agent で鍵をキャッシュする ```bash eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519 ssh-add -l ``` ```output Agent pid 4521 Enter passphrase for /home/user/.ssh/id_ed25519: Identity added: /home/user/.ssh/id_ed25519 (user@example.com) 256 SHA256:abc...xyz user@example.com (ED25519) ``` `ssh-agent` は秘密鍵をメモリに保持する認証エージェント。`ssh-add` で鍵を登録すると、以後の接続でパスフレーズ入力が不要になる。`ssh-add -l` で登録中の鍵を一覧、`ssh-add -D` で全削除する。 ### Step 5: パーミッションを正す ```bash chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519 chmod 644 ~/.ssh/id_ed25519.pub ``` OpenSSH は秘密鍵が他者から読める状態だと利用を拒否する。`~/.ssh` を `700`、秘密鍵を `600` にするのが基本。これを外すと「鍵があるのに認証できない」典型例になる。 ## SSH ポートフォワーディングの 3 種類は何が違うのか {#forwarding} `-L` はローカル転送(手元のポートを経由してリモート側のサービスへ)、`-R` はリモート転送(リモートのポートを手元のサービスへ)、`-D` は動的転送(SOCKS プロキシ)。方向と用途で使い分ける。 | オプション | 名称 | 典型用途 | | ------------------------- | ----------------------- | -------------------------------------- | | `-L local:host:hostport` | ローカルフォワード | 手元から踏み台越しに内部 DB へ接続 | | `-R remote:host:hostport` | リモートフォワード | リモートから手元のサービスへ到達させる | | `-D port` | 動的フォワード(SOCKS) | ブラウザ等のトラフィックを SSH 経由に | ```bash ssh -L 8080:internal-db:5432 user@gateway ssh -R 9000:localhost:3000 user@remote ssh -D 1080 user@gateway ``` `man ssh` の `-L` / `-R` / `-D` の定義どおり、SSH の暗号化された通信路にアプリケーションの通信を載せる仕組み。トンネルを張った上で平文プロトコルを安全に運べる。 ## GPG でデータ自体を暗号化・署名するには {#gpg} GPG(GnuPG)は OpenPGP 規格の実装で、ファイルそのものを暗号化・署名する。SSH が通信路を守るのに対し、GPG は保存・配布するデータを守る。公開鍵で暗号化し、対応する秘密鍵でのみ復号できる。 ### 鍵ペアを生成する ```bash gpg --gen-key gpg --list-keys ``` ```output pub ed25519 2026-05-30 [SC] ABCDEF0123456789ABCDEF0123456789ABCDEF01 uid [ultimate] User Name sub cv25519 2026-05-30 [E] ``` `gpg --gen-key` で名前とメールアドレスを対話入力し、鍵ペアを作る。`--list-keys` で公開鍵、`--list-secret-keys` で秘密鍵を一覧する。 ### 公開鍵を配布・取り込みする ```bash gpg --export -a user@example.com > mypubkey.asc gpg --import friend_pubkey.asc ``` `--export -a` は公開鍵を ASCII 形式(armored)で書き出す。相手はこれを `--import` で取り込む。暗号化して送るには受信者の公開鍵が手元に必要。 ::: warning 配布するのは**公開鍵**。`--export-secret-keys` で出力される**秘密鍵は絶対に渡さない**。秘密鍵が漏れれば、その鍵宛の暗号文がすべて復号され、署名も偽造される。 ::: ### ファイルを暗号化・復号する ```bash gpg -e -r user@example.com secret.txt gpg -d secret.txt.gpg > secret.txt ``` ```output gpg: encrypted with cv25519 key, ID 0123456789ABCDEF, created 2026-05-30 "User Name " ``` `-e`(`--encrypt`)と `-r`(受信者指定)で暗号化すると `secret.txt.gpg` が生成される。復号は `-d`(`--decrypt`)。復号できるのは指定した受信者の秘密鍵を持つ人だけ。 ### 署名と検証 ```bash gpg --sign document.txt gpg --verify document.txt.gpg ``` ```output gpg: Signature made Fri 30 May 2026 10:00:00 JST gpg: using EDDSA key ABCDEF0123456789ABCDEF0123456789ABCDEF01 gpg: Good signature from "User Name " ``` `--sign` は秘密鍵で署名し、改ざんと作成者を保証する。`--verify` は署名者の公開鍵で検証する。暗号化(秘匿)と署名(真正性)は別目的なので混同しないこと。 ## openssl で何ができるのか {#openssl} `openssl` は TLS / 暗号機能を提供するツールキット。ハッシュ計算、対称暗号、鍵・証明書の生成など幅広く扱える。LPIC-1 では基本的なハッシュと暗号化を押さえる。 ```bash openssl dgst -sha256 file.txt openssl enc -aes-256-cbc -pbkdf2 -in file.txt -out file.enc openssl enc -d -aes-256-cbc -pbkdf2 -in file.enc -out file.txt ``` ```output SHA256(file.txt)= 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 enter AES-256-CBC encryption password: ``` `openssl dgst -sha256` はハッシュ値(ファイルの指紋)を計算する。`openssl enc` は対称暗号で、暗号化と復号に同じパスワードを使う。鍵導出には `-pbkdf2` を付ける。GPG が公開鍵暗号でデータを守るのに対し、`enc` はパスワード共有型である点が違う。 ## よくあるミスと対処 {#common-mistakes} ### ミス 1: 秘密鍵や ~/.ssh のパーミッションが緩い `~/.ssh` が `777`、秘密鍵が `644` のように他者から読める状態だと、OpenSSH はセキュリティ上の理由で鍵を無視し、パスワード認証へフォールバックするか接続を拒否する。`chmod 700 ~/.ssh` と `chmod 600 ~/.ssh/id_*` で修正する。 ### ミス 2: known_hosts のホスト鍵変更警告を無視する サーバーを再構築すると公開鍵が変わり、接続時に `REMOTE HOST IDENTIFICATION HAS CHANGED!` の警告が出る。これは中間者攻撃の可能性も示す重要な警告。正当な変更だと確認できたら `ssh-keygen -R hostname` で `known_hosts` の該当行を削除してから再接続する。 ### ミス 3: GPG で公開鍵と秘密鍵を取り違える 暗号化には**受信者の公開鍵**、復号には**自分の秘密鍵**を使う。配布するのは公開鍵だけ。`--export`(公開鍵)と `--export-secret-keys`(秘密鍵)を混同して秘密鍵を共有すると、暗号化の意味が失われる。 ### ミス 4: パスフレーズを設定しない/管理しない 秘密鍵にパスフレーズを設定しないと、鍵ファイルを盗まれただけで成りすましが可能になる。パスフレーズを設定し、`ssh-agent` で入力回数を減らすのが実務的。GPG の秘密鍵パスフレーズも同様に管理する。 ### ミス 5: 公開鍵ではなく秘密鍵をサーバーへ送る `authorized_keys` に登録するのは `.pub` が付いた**公開鍵**。`ssh-copy-id` を使えば自動で正しく登録される。手動コピー時に秘密鍵(`id_ed25519`)を送らないよう注意する。 ## トラブルシューティング {#troubleshooting} ### 症状: 公開鍵を登録したのにパスワードを求められる **原因**: 秘密鍵や `~/.ssh` のパーミッション過多、または `authorized_keys` への登録不備 **確認**: ```bash ssh -v user@host ls -ld ~/.ssh ls -l ~/.ssh/authorized_keys ``` **対処**: `chmod 700 ~/.ssh`、`chmod 600 ~/.ssh/authorized_keys` に修正。`ssh -v` の出力で鍵が提示・拒否される箇所を確認する。 ### 症状: 接続時にホスト鍵変更の警告で止まる **原因**: サーバー側のホスト鍵が変わり、`known_hosts` の記録と一致しない **確認**: ```bash ssh-keygen -l -F hostname ``` **対処**: 正当な変更だと確認できたら `ssh-keygen -R hostname` で古い記録を削除して再接続。心当たりがなければ接続を中止し原因を調査する。 ### 症状: gpg --decrypt で復号できない **原因**: 対応する秘密鍵が鍵束に無い、または受信者指定を誤って暗号化した **確認**: ```bash gpg --list-secret-keys ``` **対処**: 自分の秘密鍵があるか確認。無ければその暗号文は復号できない。暗号化時は `-r` に正しい受信者(自分が復号するなら自分)を指定する。 ## 作業完了チェックリスト {#checklist} - [ ] `ssh-keygen -t ed25519` で鍵ペアを生成した - [ ] `ssh-copy-id` で公開鍵をリモートに登録した - [ ] 公開鍵認証で接続できた - [ ] `ssh-add` で秘密鍵を ssh-agent に追加した - [ ] `~/.ssh` を 700、秘密鍵を 600 に設定した - [ ] `gpg` でファイルの暗号化・復号を確認した - [ ] `openssl dgst` でハッシュ値を計算した ## まとめ {#summary} | 目的 | コマンド | 守る対象 | | ------------------- | ------------------------------ | -------------------------- | | 鍵生成 | `ssh-keygen -t ed25519` | SSH 接続の認証 | | 公開鍵登録 | `ssh-copy-id user@host` | リモートの authorized_keys | | 鍵キャッシュ | `ssh-add` | パスフレーズ入力回数 | | データ暗号化 | `gpg -e -r user file` | 保存・配布データ | | 署名 / 検証 | `gpg --sign` / `--verify` | 真正性 | | ハッシュ / 対称暗号 | `openssl dgst` / `openssl enc` | 完全性・パスワード共有暗号 | SSH は通信路、GPG / openssl はデータそのものを守る。この役割分担と、鍵ファイルのパーミッション規律を押さえれば 110.3 は得点源になる。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [セキュリティ管理の基礎](/articles/lpic/security-administration) - [ホストのセキュリティ強化](/articles/lpic/host-security) - [インターネットプロトコルの基礎](/articles/lpic/internet-protocols) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # LPIC-1 難易度・合格点・配点 完全FAQ - 500点・60問・90分の根拠 Source: https://penguin-gym-linux.com/articles/lpic/difficulty-and-passing-score ## この記事で得られる答え {#intro} 「LPIC-1 の合格点は何点?」「問題数や試験時間の根拠は?」「101 と 102 はどちらが難しい?」——これらの疑問に、LPI 公式一次ソースを根拠として回答する。 この記事で得られる答えは 4 点: - 合格点 500 点・200〜800 点スケールの公式根拠 - 配点の仕組み(weight と出題数の関係) - 101 と 102 の難易度の実質的な違い - 合格率が公式に非公開である理由と、正しい学習戦略への転換 数値情報はすべて LPI 公式サイトの一次ソースを出典として明記する。 ## LPIC-1 合格基準まとめ {#passing-score} LPIC-1(101 試験・102 試験)の合格基準は以下のとおり。 | 項目 | 値 | | -------------- | --------------------------- | | スコアスケール | 200〜800 点 | | 合格点 | 500 点 | | 問題数 | 各試験 60 問 | | 試験時間 | 各試験 90 分 | | 問題形式 | 多肢選択問題 + 穴埋め問題 | | 認定有効期限 | 5 年間 | | 受験順序 | 101・102 どちらから先でも可 | (出典: LPI 公式 FAQ `https://www.lpi.org/ja/about-lpi/frequently-asked-questions` / LPI 公式 LPIC-1 Overview `https://www.lpi.org/ja/our-certifications/lpic-1-overview/`) **合格には 101 試験・102 試験の両方に合格することが必要**。どちらか一方の合格では LPIC-1 認定は取得できない。 スコアは「各問題の難易度が異なるため、合格に必要な正解数は受験内容により変動する」(LPI 公式 FAQ 原文)。つまり難易度の高い問題に正解するほど高スコアになる仕組みで、単純に「60 問中何問以上正解」という基準ではない。 ## 配点の仕組み(weight とは) {#weight} LPIC-1 の出題比重は各トピックに設定された **weight** で決まる。weight は「このトピックから相対的に何問出題されるか」の比率を示す数値で、LPI 公式の Objectives ページに公開されている。 101 試験のトピック別 weight(一部抜粋、出典: LPI 公式 101 Objectives V5.0 `https://www.lpi.org/ja/our-certifications/exam-101-objectives`): | トピック | 内容 | Weight | | -------- | --------------------------------- | ------ | | 103.1 | コマンドライン作業 | 4 | | 103.3 | 基本的なファイル管理 | 4 | | 103.4 | ストリーム・パイプ・リダイレクト | 4 | | 103.5 | プロセス管理 | 4 | | 103.7 | 正規表現検索 | 3 | | 103.8 | ファイル基本編集 | 3 | | 102.4 | Debian パッケージ管理 | 3 | | 102.5 | RPM と YUM 管理 | 3 | | 101.2 | システムの起動 | 3 | | 101.3 | ランレベル / ブートターゲット変更 | 3 | | 104.3 | ファイルシステムマウント | 3 | | 104.5 | ファイルパーミッション・所有権 | 3 | weight の大きいトピック(4)はコマンドライン作業・ファイル管理・プロセス管理など、Linux 実務の核心となる分野。これらを重点的に学習することが効率的な試験対策につながる。 weight の合計に対する各トピックの比率が、そのトピックからの出題問数の目安となる。ただし実際の出題数は試験回によって多少変動するため、weight はあくまで「出題比重の目安」として活用する。 ## 1 問あたりの実質配点 {#per-question} スコアは 200〜800 点スケールで計算され、固定の「1 問 N 点」という配点方式ではない。スコア計算のロジックは LPI が非公開としているが、以下の点は公式から確認できる。 - 難易度の高い問題に正解すると、より高いスコアが得られる - 同じ正解数でも、難易度の高い問題を多く正解した場合は高スコアになる - 合格点 500 点は固定(受験回による変動なし) 実戦的な意味での「1 問あたりの点数」は weight の大きいトピックほど高い傾向にある。コマンドライン作業(103.1、weight 4)やプロセス管理(103.5、weight 4)は出題数が多く、これらを落とすと合格スコアに大きく影響する。 対策: weight 4 のトピックを確実に得点源にしつつ、weight 1〜2 の細かいトピックも最低限カバーする「広く浅く、重要分野は深く」が基本戦略。 ## 101 vs 102 の難易度比較 {#difficulty-comparison} 101 試験と 102 試験を受験した学習者の観察から、難易度には傾向的な差がある。 **101 試験の特徴**(コマンドライン・基礎操作中心): - コマンドライン操作、パイプ、リダイレクト - パッケージ管理(apt / yum / rpm) - プロセス管理、ジョブコントロール - ファイルパーミッション、ハードリンク・シンボリックリンク **102 試験の特徴**(システム管理・ネットワーク・応用中心): - シェルスクリプト(変数・制御構造・関数) - ネットワーク設定(IP アドレス・ルーティング・DNS・SSH) - システム管理(ユーザー管理・cron・ログ・起動設定) - セキュリティ(GPG 鍵・ファイアウォールの基礎) - X Window System / デスクトップ環境 **難易度判定**: 101 試験はコマンドの動作を実機で確かめながら学習しやすく、初学者がとっつきやすい。102 試験はネットワーク・セキュリティなど抽象度の高い概念が増え、学習範囲も広い。一般的に 102 試験の方が難易度は高いと言われる。 ただし「101 から受けると 102 の学習がスムーズになる」という傾向があるため、特別な理由がなければ 101 から先に受験することを推奨する。受験順序に公式の制約はない。 ## 合格率の公式情報と非公式統計の扱い {#pass-rate} LPI 公式は合格率を公開していない。LPI 公式 FAQ(`https://www.lpi.org/ja/about-lpi/frequently-asked-questions`)を確認したが、合格率に関する記述は存在しない。 非公式統計(ブログ / 学習サイトでよく見られる「合格率 X%」という数字)には以下の問題がある: - **出題範囲改訂の影響**: LPIC-1 は V4.0 から V5.0 への改訂など定期的にバージョンアップされる。改訂前後で難易度が変化するため、古い統計は現在の試験に対応しない - **サンプルバイアス**: 学習コミュニティのメンバーや特定スクールの受験者を集計した数値は、受験者全体を代表しない - **検証不能**: 試験スコアは LPI が非公開管理しており、第三者が合格率を正確に算出する手段がない - **公表目的の偏り**: 「合格率 80%以上」と謳う学習サービスは、実績を誇示するためにバイアスのかかったデータを使用している可能性がある 本記事の判断スタンス: 合格率の数字を追うより、「LPI 公式 Objectives の全範囲を weight に応じた優先度で体系的に学習する」ことが合格確率を最大化する。当サイトの体系的学習コンテンツ(ターミナル実習・クイズ・記事群)はこの方針に基づいて設計されている。 ## 受からない人の典型パターンと対策 {#fail-patterns} LPIC-1 に一発合格できない受験者に共通するパターンを 5 つ挙げる。 **パターン 1: 範囲偏り(得意分野だけを深掘り)** 好きなコマンドや使い慣れた分野(ファイル操作、パイプなど)に集中し、苦手な分野(ネットワーク設定、X Window System など)を後回しにする。 対策: LPI 公式 Objectives の全トピックをリストアップし、weight に関係なく 1 件ずつチェックを入れながら学習する。苦手なトピックは後回しにせず、試験 2 週間前までに一通り触れておく。 **パターン 2: 暗記偏重(実機を触らない)** テキストや問題集を読むだけでコマンドを暗記しようとし、実際にターミナルで試さない。オプションの組み合わせや出力形式を実感なく暗記しても、穴埋め問題では対応できないことが多い。 対策: 当サイトの実践ターミナルや Linux 仮想環境で、学習したコマンドを必ず実行して確認する。特に `find`・`grep`・`awk`・`sed` などテキスト処理コマンドは手を動かすことで定着速度が上がる。 **パターン 3: weight の大きいトピックを軽視** 「難しそう」という理由で weight の高いトピック(プロセス管理・ファイル管理・ストリーム処理)を後回しにする。これらは出題比率が高いため、軽視すると合格ラインに届かない。 対策: weight 3 以上のトピックを試験の「主力科目」として扱い、学習時間の 60〜70% を集中投下する。 **パターン 4: 二択時間配分ミス** 全 60 問を 90 分で解くため 1 問あたり 1.5 分が目安。わからない問題で時間をかけすぎ、後半の問題が時間切れになるケースがある。 対策: わからない問題は一旦フラグを立てて次に進み、全問を一周してから戻る。穴埋め問題はコマンド名を知っていれば即答できるため、多肢選択問題より時間を要さないことが多い。 **パターン 5: 試験予約後の準備不足** 「試験日を決めると緊張してやる気が出る」と考えて早めに予約するが、実際には学習が進まずに試験日を迎える。 対策: 試験予約は「全トピックを一通り学習し終えた後、模擬問題で合格ライン付近のスコアが出てから」が目安。当サイトのクイズや模擬問題で自己採点してから日程を決める。 ## よくある質問(FAQ) {#faq} **LPIC-1 の合格点は何点ですか?** 各 LPI 試験は 200〜800 点スコア方式で実施され、合格点は 500 点です。試験は各 60 問・90 分で実施されます。スコアは問題の難易度に基づいて算出されるため、合格に必要な正解数は受験内容によって変動します。(出典: LPI 公式 FAQ) **LPIC-1 の難易度はどのくらいですか?** Linux 初心者には中程度の難易度です。コマンドライン操作・パーミッション・プロセス管理・パッケージ管理など実務的な知識が幅広く問われます。Linux 実務経験が 3〜6 か月程度あれば取り組みやすい試験です。実機を触りながら学習することが難易度を下げる最も効果的な方法です。 **LPIC-1 の問題数と試験時間は?** 101 試験・102 試験ともに 60 問・90 分です。問題形式は多肢選択問題と穴埋め問題で構成されます。(出典: LPI 公式 LPIC-1 Overview) **1 問あたりの点数は固定ですか?** 固定ではありません。スコアは各問題の難易度に基づいて算出されます。weight の大きいトピックほど出題数が多く、これらを落とすとスコアへの影響が大きくなります。 **LPIC-1 の合格率はどのくらいですか?** LPI 公式は合格率を公開していません。当サイトでも非公式統計は採用せず、出題範囲の体系的学習を推奨します。合格率の数字より「全 Objectives を weight に応じた優先度で学習する」ことが合格への近道です。 **101 と 102 はどちらが難しいですか?** 一般的に 102 試験の方が難しいと言われます。101 はコマンドライン・ファイル操作・パッケージ管理が中心ですが、102 はシェルスクリプト・ネットワーク設定・セキュリティ・システム管理まで範囲が広がります。特別な理由がなければ 101 から受験することを推奨します。 **受からない人の典型パターンは何ですか?** 主なパターンは 5 つです。1. 得意分野だけを深掘りして範囲が偏る、2. 暗記偏重で実機を触らない、3. weight の大きいトピックを軽視する、4. 難問で時間をかけすぎて後半が時間切れになる、5. 試験予約後に学習ペースが落ちる——いずれも計画的な対策で回避できます。 **LPIC-1 の認定有効期限は?** 認定有効期限は 5 年間です。有効期限内に上位資格の取得または再試験による更新が必要です。(出典: LPI 公式 LPIC-1 Overview) ## 次に読む / 関連リソース {#next} LPIC-1 合格を目指すなら、以下のコンテンツを活用して体系的に学習することを推奨する。 - **実践ターミナル** (`/terminal.html`): コマンドライン操作を実機で練習できる仮想ターミナル - **理解度チェック** (`/quiz.html`): LPIC-1 範囲のクイズで知識を測定 - **コマンドライン基礎** (`/articles/lpic/command-line-basics.html`): 101 試験の core となる主題 103 系を網羅 - **シェル環境変数の設定** (`/articles/lpic/shell-environment.html`): 103.1 / 103.4 の実践知識 学習の出発点として LPI 公式 Objectives ページ(`https://www.lpi.org/ja/our-certifications/exam-101-objectives`)を参照し、全トピックの weight を把握した上で学習計画を立てることを推奨する。 # LPIC-1 受験料・申し込み完全ガイド - Pearson VUE 予約手順と当日の流れ Source: https://penguin-gym-linux.com/articles/lpic/exam-registration-guide ## この記事で得られる答え {#intro} LPIC-1 受験を検討する段階でぶつかる 5 つの疑問に、LPI 公式 / LPI Marketplace / Pearson VUE 公式の一次ソースを根拠として回答する。 - LPIC-1 の受験料は 1 科目いくらか - 申し込みはどの順序で進めるか(3 ステップの全体像) - テストセンターと OnVUE オンライン試験のどちらを選ぶか - 当日の持ち物・本人確認書類は何が必要か - 不合格時の再受験待機期間は何日か LPIC-1 の制度概要・有効期限・試験構成の詳細は [LPIC-1 とは - 制度・試験構成・有効期限](/articles/lpic/what-is-lpic1) を参照。合格点・問題数・試験時間の詳細は [LPIC-1 難易度・合格点・配点 完全FAQ](/articles/lpic/difficulty-and-passing-score) を参照。 ## LPIC-1 受験料(2026-05 時点){#fee} LPIC-1 の受験料は 101-500 / 102-500 いずれも **15,000 円(税別)** だ。(出典: LPI 公式試験料金ページ `https://www.lpi.org/ja/exam-pricing/`, 2026-05 時点) LPIC-1 認定には 2 科目合格が必要なため、合計 30,000 円(税別)が目安となる。 ### 科目別受験料一覧 | 資格 / 科目 | 試験コード | 受験料(JPY・税別) | USD バウチャー | | ---------------------- | ---------- | ------------------- | -------------- | | LPIC-1(101 試験) | 101-500 | 15,000 円 | $200 | | LPIC-1(102 試験) | 102-500 | 15,000 円 | $200 | | LPIC-2(201 試験) | 201-450 | 18,000 円 | $200 | | LPIC-2(202 試験) | 202-450 | 18,000 円 | $200 | | Linux Essentials(各) | 010-160 | 13,000 円 | $120 | (出典: LPI 公式試験料金ページ / LPI Marketplace `https://global1.lpimarketplace.com/shop/exam-vouchers`, 2026-05 時点) ### 支払い方法 **JPY 決済(LPI Marketplace / Pearson VUE 直接)** LPI Marketplace では Mastercard / Visa / American Express / Discover に対応している。決済は SSL 保護された画面で行われる。(出典: LPI Marketplace, 2026-05 時点) **USD バウチャー(LPI Marketplace)** 海外クレジットカードを持つ場合や円安局面での USD バウチャー購入が選択肢となるが、為替レートにより実質コストが変動する。購入時点のレートを確認すること。 ::: warning 価格は変動する場合がある。受験前に LPI 公式試験料金ページ `https://www.lpi.org/ja/exam-pricing/` で最新価格を確認すること。 ::: ## 申込フロー全体像 {#flow} LPIC-1 の受験申し込みは 3 ステップで完了する。所要時間は計 30 分程度だ。 1. **LPI ID を取得する** — LPI 公式で受験者アカウントを作成 2. **Pearson VUE アカウントを作成** — 試験配信パートナーのアカウントを作成 3. **試験を予約する** — Pearson VUE で日程・会場・言語を選択して決済 ### Step 1: LPI ID を取得する {#lpi-id} LPI ID は LPI のすべての試験で使用する受験者識別子だ。すでに LPI ID を持つ場合はこのステップをスキップできる。 **取得手順** 1. `https://cs.lpi.org/caf/Xamman/register` にアクセス 2. 氏名・メールアドレス・生年月日等を入力(試験当日の本人確認書類と氏名が一致する必要がある) 3. 確認メールのリンクをクリックして登録完了 ::: warning LPI ID の氏名は本人確認書類の表記と一致させること。不一致があると当日の受験を拒否される場合がある。 ::: ### Step 2: Pearson VUE アカウントを作成 {#pearson-account} Pearson VUE は LPIC の試験配信を担当する世界最大の試験配信会社だ。LPI ID とは別に Pearson VUE 側のアカウントが必要となる。 **取得手順** 1. 日本語ロケール URL `https://wsr.pearsonvue.com/testtaker/profile/create/SignUp.htm?clientCode=LINUXPROFESSION&locale=ja_JP` にアクセス 2. LPI ID を入力し、氏名・連絡先等を登録 3. アカウント作成完了後、試験予約画面へ進む ### Step 3: 試験を予約する {#book-exam} Pearson VUE の試験予約画面で以下を選択する。 | 選択項目 | 内容 | | -------------- | ------------------------------------------------------------------------------------------------------------ | | 受験方式 | テストセンター受験 または OnVUE オンライン試験 | | テストセンター | `https://wsr.pearsonvue.com/testtaker/find/testcenter/LINUXPROFESSION?locale=ja_JP` で最寄りのセンターを検索 | | 試験日時 | 希望日時を選択(テストセンターは座席空き状況に依存) | | 試験言語 | 日本語・英語・ドイツ語等から選択(選択肢は受験方式による) | | 決済 | クレジットカードまたは事前購入バウチャー | 予約完了後、確認メールが届く。予約番号を控えておくこと。 ## テストセンター vs OnVUE の選択基準 {#center-vs-onvue} LPIC-1 はテストセンター受験と OnVUE オンライン試験のいずれかを選択できる。(出典: Pearson VUE LPI ページ `https://www.pearsonvue.com/jp/ja/lpi.html`, 2026-05 時点) | 項目 | テストセンター受験 | OnVUE オンライン試験 | | ---------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | | 受験場所 | 全国の Pearson VUE 認定センター | 自宅や職場など静かな個室 | | 対応言語 | 英語・ドイツ語・日本語・ポルトガル語・中国語(簡体字・繁体字)・スペイン語(7 言語) | 英語・ドイツ語・日本語・ポルトガル語・スペイン語(5 言語、中国語なし) | | 本人確認 | 書類 2 点提示 | カメラによるリモート確認 + 書類 2 点 | | 受験環境 | センターが用意(PC / デスク / 静音ブース) | 自分で環境整備が必要 | | PC 要件 | なし(センターの PC を使用) | Windows 10+ または macOS 14+、有線接続推奨 | | 回線要件 | なし | ダウンロード 6 Mbps 以上 / アップロード 2 Mbps 以上 | | 到着時間 | 予約時刻の 15 分前 | 予約時刻の 30 分前(ルームスキャン・システムチェック要) | | 向いているケース | 自宅環境が不安定 / PC 要件を満たせない / 緊張を場で解消したい | 移動時間を節約したい / 静かな個室が確保できる / 回線が安定している | **OnVUE 禁止事項(技術的要件)** 仮想環境(VM)・タブレット・ヘッドホン・VPN・サブモニタは OnVUE では使用できない。試験中の通信安定性が合否に直接影響するため、有線 LAN 接続を強く推奨する。(出典: Pearson VUE OnVUE ページ `https://www.pearsonvue.com/jp/ja/lpi/onvue.html`, 2026-05 時点) ::: tip どちらを選ぶか迷う場合、初回受験はテストセンターを推奨する。設備・監督者が整った環境で試験に集中できるため、環境トラブルによるリスクを排除できる。 ::: ## 当日の持ち物と本人確認 {#id-requirements} ### 本人確認書類(2 点必須) LPIC-1 の受験当日は**本人確認書類 2 点の提示が必須**だ。1 点では受験できない。(出典: Pearson VUE 本人確認ページ `https://www.pearsonvue.com/jp/ja/test-takers/tutorial/identification-1s.html`, 2026-05 時点) **認められる書類グループ** 書類は A〜D の 4 グループに分類される。下表の構成は Pearson VUE 公式本人確認ページ準拠(取得日 2026-05)。 | グループ | 書類例 | 条件 | | ---------- | -------------------------------------------------------------------------------------- | ---------------------------- | | A グループ | 運転免許証・パスポート・マイナンバーカード・在留カード・特別永住者証明書・障がい者手帳 | 顔写真付き政府発行 ID | | B グループ | 顔写真貼付・プラスチック印刷またはラミネート加工の社員証・学生証 | 顔写真付き民間発行 ID | | C グループ | 年金手帳・資格確認書・署名付きクレジットカード・公共施設カード | 署名または氏名記載の補助書類 | | D グループ | 氏名と顔写真または署名が確認できる国内発行書類 | 補助書類 | ※ 各グループに含まれる書類の最新版・詳細条件は [Pearson VUE 本人確認ページ](https://www.pearsonvue.com/jp/ja/test-takers/tutorial/identification-1s.html) を参照すること。 **有効な組み合わせ** - A グループ 2 点 - A グループ 1 点 + B / C / D グループ 1 点 - B グループ 1 点 + C グループ 1 点 ::: danger 書類の氏名が予約時の表記と異なる場合、受験を拒否される場合がある。LPI ID 登録・Pearson VUE アカウント・本人確認書類の氏名表記を事前に統一しておくこと。 ::: ### OnVUE 受験環境の要件 {#onvue-env} OnVUE でオンライン受験する場合、以下の環境要件を満たす必要がある。 | 要件項目 | 内容 | | ------------ | ------------------------------------------------------- | | OS | Windows 10+ または macOS 14+ | | 周辺機器 | ウェブカメラ・マイク・スピーカー(ヘッドホン禁止) | | 回線(下り) | 6 Mbps 以上 | | 回線(上り) | 2 Mbps 以上 | | 禁止機器 | 仮想環境(VM)・タブレット・ヘッドホン・VPN・サブモニタ | | 受験前準備 | 30 分前到着・360 度ルームスキャン・システムチェック | (出典: Pearson VUE OnVUE ページ `https://www.pearsonvue.com/jp/ja/lpi/onvue.html`, 2026-05 時点) ::: warning OnVUE の技術要件は変更される場合がある。最新の要件は [Pearson VUE OnVUE 公式ページ](https://www.pearsonvue.com/jp/ja/lpi/onvue.html) で確認すること。 ::: ## キャンセル・予約変更ポリシー {#cancel} 試験のキャンセルや予約変更は Pearson VUE の受験者ポータルから行う。変更・キャンセルの期限や手数料については Pearson VUE 公式の最新ポリシーを確認すること。 **Pearson VUE サポート窓口** | 窓口 | 電話番号 | 対応時間 | | ---------------------- | ------------ | ------------------------- | | 一般(テストセンター) | 0120-355-173 | 9:00〜18:00(土日祝除く) | | OnVUE 専用 | 0120-355-583 | 9:00〜18:00(土日祝除く) | ::: tip 電話番号・営業時間の最新情報は [Pearson VUE LPI ページ](https://www.pearsonvue.com/jp/ja/lpi.html) を参照すること。番号変更時の陳腐化を防ぐため、受験直前は公式サイトでの再確認を推奨する。(出典: Pearson VUE, 2026-05 時点) ::: 試験当日の緊急連絡や OnVUE の接続トラブル時は OnVUE 専用番号に問い合わせること。 ## リテイク(再受験)ポリシー {#retake} LPIC-1 の再受験に関するポリシーは以下のとおりだ。(出典: LPI 公式 policies `https://www.lpi.org/ja/policies`, 2026-05 時点) | 状況 | 待機期間 | | ---------------------- | ----------------------------------- | | 1 回目不合格後 | 7 日間(1 週間) | | 2 回目以降の不合格後 | 14 日間 | | 合格後の同一試験再受験 | 最低 2 年間、同一試験の再受験は不可 | **異なるバージョンの扱い** 101-400 と 101-500 など、異なるバージョンでも同一試験として扱われる。バージョンを変えて待機期間を回避することはできない。(出典: LPI 公式 policies, 2026-05 時点) ::: warning 再受験ポリシーは LPI 公式 policies ページ `https://www.lpi.org/ja/policies` で最新情報を確認すること。試験によりポリシーが異なる場合がある。 ::: ## よくあるトラブルと対処 {#troubleshoot} ### 遅刻した場合 テストセンター受験で予約時刻に間に合わなかった場合、入室を拒否される場合がある。事前に交通経路を確認し、余裕を持って到着すること。 ### 本人確認書類の氏名不一致 書類の氏名が予約時の表記と異なる場合、受験を拒否されるリスクがある。LPI ID・Pearson VUE アカウント・本人確認書類の氏名を前もって統一しておくこと。 ### OnVUE のシステムチェック失敗 試験開始前のシステムチェックで失敗する主な原因は以下のとおりだ。 - VPN の起動(試験中は VPN を停止する) - ヘッドホンの接続(スピーカーに切り替える) - サブモニタの接続(メインモニタのみにする) - 回線速度不足(有線 LAN に切り替える) OnVUE 試験前に Pearson VUE の接続テストツールを実行して環境を確認しておくことを推奨する。 ### 予約変更のタイミング 試験直前のキャンセルや変更は手数料が発生する場合や不可となる場合がある。学習計画が固まってから予約し、変更が必要な場合は早めに手続きすること。 ## よくある質問(FAQ){#faq} **Q. LPIC-1 の受験料はいくらですか?** 101-500 / 102-500 各 15,000 円(税別、2026-05 時点)だ。2 科目合計 30,000 円が目安。USD バウチャーは $200/科目(LPI Marketplace)。(出典: LPI 公式試験料金ページ, 2026-05 時点) **Q. 受験料に消費税はかかりますか?** 公式表示は税別 15,000 円だ。消費税の取扱いは購入経路(LPI Marketplace 直接購入 / Pearson VUE 直接決済)により異なる可能性がある。決済画面で最終金額を確認すること。 **Q. 申し込みフローは何ステップですか?** 3 ステップだ。(1) LPI ID を LPI 公式で取得 → (2) Pearson VUE で日本語ロケールアカウントを作成 → (3) Pearson VUE で試験を予約。所要時間は計 30 分程度。 **Q. テストセンター受験と OnVUE オンライン試験のどちらが良いですか?** 移動時間を節約したい / 自宅環境が静か / 安定したネット回線がある場合は OnVUE が便利だ。一方、自宅環境が不安定 / PC 要件を満たせない / 試験中のトラブルリスクを下げたい場合はテストセンター受験を推奨する。 **Q. 当日の持ち物は何が必要ですか?** 本人確認書類 2 点が必須だ。A グループ(顔写真付き政府発行 ID)2 点、または A グループ 1 点 + 補助書類 1 点が基本の組み合わせとなる。 **Q. 試験は日本語で受験できますか?** テストセンター受験では 7 言語(日本語含む)、OnVUE では 5 言語(日本語含む)に対応している。予約時に言語を選択する。(出典: LPI 公式 LPIC-1 Overview, 2026-05 時点) **Q. 不合格だった場合、いつ再受験できますか?** 1 回目の不合格後は 7 日間の待機期間、2 回目以降の不合格は 14 日間の待機期間が必要だ。異なるバージョンも同一試験として扱われる。(出典: LPI 公式 policies, 2026-05 時点) **Q. 合格後にスコア向上のため再受験できますか?** 合格後は同一試験を最低 2 年間再受験できない。(出典: LPI 公式 policies, 2026-05 時点) ## まとめ {#conclusion} LPIC-1 の受験料・申し込み・Pearson VUE 予約手順・当日準備の主要 5 ポイントをまとめる。 1. **受験料** — 101-500 / 102-500 各 15,000 円(税別)、2 科目合計 30,000 円が目安(USD バウチャー $200/科目) 2. **申込フロー** — LPI ID 取得 → Pearson VUE アカウント作成 → 試験予約の 3 ステップ(所要約 30 分) 3. **受験方式** — テストセンター受験(全国センター)または OnVUE(自宅・個室・Windows 10+ / macOS 14+・6 Mbps 以上) 4. **本人確認** — 2 点必須(A グループ 顔写真付き政府発行 ID 2 点が最もシンプルな組み合わせ) 5. **再受験** — 不合格後 7 日(1 回目)/ 14 日(2 回目以降)の待機期間、合格後は 2 年間同一試験再受験不可 ## 次に読む {#next} - [LPIC-1 とは - 制度・試験構成・有効期限](/articles/lpic/what-is-lpic1) - [LPIC-1 難易度・合格点・配点 完全FAQ](/articles/lpic/difficulty-and-passing-score) - [LPIC-1 勉強時間・勉強方法・最短攻略ガイド](/articles/lpic/study-time-and-method) - [LPI 公式試験料金(lpi.org)](https://www.lpi.org/ja/exam-pricing/) - [LPI Marketplace バウチャー](https://global1.lpimarketplace.com/shop/exam-vouchers) - [Pearson VUE LPI ページ(pearsonvue.com)](https://www.pearsonvue.com/jp/ja/lpi.html) - [Pearson VUE OnVUE](https://www.pearsonvue.com/jp/ja/lpi/onvue.html) - [Pearson VUE 本人確認](https://www.pearsonvue.com/jp/ja/test-takers/tutorial/identification-1s.html) - [LPI 公式 policies](https://www.lpi.org/ja/policies) - [LPI ID 登録](https://cs.lpi.org/caf/Xamman/register) # LPIC-1 出題範囲完全ガイド - 101 / 102 全トピック対応表 Source: https://penguin-gym-linux.com/articles/lpic/exam-scope-101-102 ## この記事で達成できること {#intro} - LPIC-1 試験 101 / 102 の全トピック構成と各 Weight を把握できる - Weight 配点から学習優先度を判断できる - 既存 12 学習記事がどの Objective に対応するかを一目で確認できる - 試験全体の体系を俯瞰し、学習の抜け漏れを防げる - 公式 Objectives v5.0 の一次ソースにアクセスして詳細確認できる LPIC-1 は 2 つの試験(101-500 / 102-500)で構成され、それぞれ Weight 合計 60 の範囲から出題される。この記事は試験範囲全体を俯瞰するナビゲーション記事であり、個別トピックの詳細学習は各リンク先記事で行う。 ## LPIC-1 出題範囲の全体像 {#overview} LPIC-1 は Linux Professional Institute が提供するエントリーレベルの Linux 認定資格で、101-500 と 102-500 の 2 試験で構成される。どちらも Weight(出題比重)の合計は 60 であり、試験ごとに独立して受験・合格できる。 | 試験 | Topic 範囲 | Weight 合計 | 主な内容 | | ------- | -------------- | ----------- | ------------------------------------------------------------------------------ | | 101-500 | Topic 101〜104 | 60 | システム起動・パッケージ管理・GNU コマンド・ファイルシステム | | 102-500 | Topic 105〜110 | 60 | シェル・デスクトップ・管理タスク・システムサービス・ネットワーク・セキュリティ | Weight はそのトピックから出題される問題数の目安を示す。Weight 4 のトピックは Weight 1 のトピックに比べて約 4 倍の出題頻度になるため、学習時間の配分に直結する指標だ。 現行の Objectives バージョンは v5.0。v4.0 から Topic 104.4(ディスククォータの管理)が削除された点に注意する。 ## 試験 101-500 全トピック一覧 {#exam-101} ### Topic 101 システムアーキテクチャ ハードウェアの認識・検出から、Linux の起動シーケンス、ランレベルとブートターゲットの操作までを扱う。BIOS / UEFI の違い、GRUB の役割、systemd と SysV init の対応関係が頻出。 | Objective | 内容 | Weight | 対応記事 | | --------- | --------------------------------------------------------------------------- | ------ | -------- | | 101.1 | ハードウェア設定の決定と構成 | 2 | — | | 101.2 | システムの起動 | 3 | — | | 101.3 | ランレベル / ブートターゲットの変更とシステムのシャットダウンまたはリブート | 3 | — | 101.1 は `/proc` / `/sys` / `lsusb` / `lspci` によるハードウェア情報取得が中心。101.2 は BIOS から initrd までの起動フロー全体の把握が必要。101.3 は `systemctl isolate` / `systemctl poweroff` 等の systemd 操作と旧来の `telinit` コマンドの両方が対象になる。 ### Topic 102 Linux のインストールとパッケージ管理 ディスクパーティション設計、ブートローダ(GRUB2)のインストール、共有ライブラリの管理、Debian / RPM 系パッケージ管理ツールの操作を扱う。仮想化ゲストとしての Linux 利用も含む。 | Objective | 内容 | Weight | 対応記事 | | --------- | ----------------------------------- | ------ | -------- | | 102.1 | ハードディスクのレイアウト設計 | 2 | — | | 102.2 | ブートマネージャのインストール | 2 | — | | 102.3 | 共有ライブラリの管理 | 1 | — | | 102.4 | Debian パッケージ管理の使用 | 3 | — | | 102.5 | RPM および YUM パッケージ管理の使用 | 3 | — | | 102.6 | Linux を仮想化ゲストとして使用する | 1 | — | 102.4 は `dpkg` / `apt` / `apt-get` / `apt-cache` の使い分けが出題される。102.5 は `rpm` / `yum` / `dnf` の基本操作が対象。102.1 はスワップ領域の配置や `/boot` 分割の判断基準も含む。 ### Topic 103 GNU と Unix コマンド シェル操作、テキスト処理、ファイル管理、パイプ / リダイレクト、プロセス管理、優先度制御、正規表現、テキストエディタと幅広い範囲を扱う。Weight 合計が Topic の中で最大であり、101 試験の核心。 | Objective | 内容 | Weight | 対応記事 | | --------- | ------------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | | 103.1 | コマンドラインで操作する | 4 | [コマンドライン基礎](/articles/lpic/command-line-basics) | | 103.2 | フィルタを使用したテキストストリームの処理 | 2 | [テキストストリームフィルタ](/articles/lpic/text-stream-filters) | | 103.3 | 基本的なファイル管理を行う | 4 | — | | 103.4 | ストリーム、パイプ、リダイレクトの使用 | 4 | [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) | | 103.5 | プロセスの生成、監視、終了 | 4 | [プロセス管理入門](/articles/tutorials/process-management-basics) / [プロセス管理実践](/articles/tutorials/process-management-practical) | | 103.6 | プロセスの実行優先度を変更する | 2 | [プロセス優先度の制御](/articles/lpic/process-priorities-nice) | | 103.7 | 正規表現を使用したテキストファイルの検索 | 3 | [正規表現入門](/articles/lpic/regular-expressions) | | 103.8 | 基本的なファイル編集 | 3 | — | 103.8 は `vi` / `vim` の基本操作(挿入モード移行、保存、終了)が対象。103.3 は `cp` / `mv` / `rm` / `find` / `tar` / `gzip` 等の基本ファイル操作コマンド全体が範囲となる。 ### Topic 104 デバイス、Linux ファイルシステム、ファイルシステム階層標準 パーティション作成からファイルシステムの整合性維持、マウント操作、パーミッション管理、リンク操作、ファイル検索まで扱う。v5.0 では 104.4(ディスククォータ)が削除された。 | Objective | 内容 | Weight | 対応記事 | | --------- | ---------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 104.1 | パーティションとファイルシステムの作成 | 2 | — | | 104.2 | ファイルシステムの整合性の維持 | 2 | — | | 104.3 | ファイルシステムのマウントとアンマウントの制御 | 3 | — | | 104.5 | ファイルのパーミッションと所有権の管理 | 3 | [パーミッション基礎](/articles/tutorials/permissions-basics) / [パーミッション応用](/articles/tutorials/permissions-advanced) / [パーミッション実践](/articles/tutorials/permissions-practical) | | 104.6 | ハードリンクとシンボリックリンクの作成と変更 | 2 | [ハードリンクとシンボリックリンク](/articles/lpic/hard-symbolic-links) | | 104.7 | システムファイルの検索と正しい位置への配置 | 2 | — | 104.5 は `chmod` / `chown` / `chgrp` / SUID / SGID / スティッキービットまで含む広範な範囲。104.7 は `find` / `locate` / `whereis` / `which` の使い分けと FHS(Filesystem Hierarchy Standard)の知識が必要。 ## 試験 102-500 全トピック一覧 {#exam-102} ### Topic 105 シェル・シェルスクリプト シェル環境の構成と変数管理、シェルスクリプトの条件分岐・繰り返し・関数定義を扱う。どちらも Weight 4 と高く、102 試験で最も演習量が必要な領域。 | Objective | 内容 | Weight | 対応記事 | | --------- | ---------------------------------------- | ------ | ------------------------------------------------------------- | | 105.1 | シェル環境のカスタマイズと使用 | 4 | [シェル環境変数の設定](/articles/lpic/shell-environment) | | 105.2 | シェルスクリプトのカスタマイズまたは記述 | 4 | — | 105.2 はシバン行(`#!/bin/bash`)、`if` / `for` / `while` / `case`、関数定義、`test` コマンド、終了ステータス(`$?`)等が出題範囲となる。 ### Topic 106 ユーザーインターフェースとデスクトップ X Window System の設定、グラフィカルデスクトップ環境(GNOME / KDE 等)、アクセシビリティ機能を扱う。Weight が小さく試験全体での比重は低いが、X11 の基本アーキテクチャは理解しておく必要がある。 | Objective | 内容 | Weight | 対応記事 | | --------- | ------------------------ | ------ | -------- | | 106.1 | X11 のインストールと構成 | 2 | — | | 106.2 | グラフィカルデスクトップ | 1 | — | | 106.3 | アクセシビリティ | 1 | — | 106.1 はディスプレイマネージャ(GDM / LightDM)の役割と `DISPLAY` 環境変数の扱いが中心。106.3 は補助入力デバイスや画面拡大機能など、アクセシビリティに関連する設定ツールが対象。 ### Topic 107 管理タスク ユーザー / グループ管理、ジョブスケジューリング(cron / at)、ロケールと国際化設定を扱う。107.1 は Weight 5 と試験 102 で最高配点であり最優先で習得する。 | Objective | 内容 | Weight | 対応記事 | | --------- | -------------------------------------------------------------------------- | ------ | -------- | | 107.1 | ユーザーアカウントとグループアカウントおよび関連するシステムファイルの管理 | 5 | — | | 107.2 | ジョブをスケジューリングしてシステム管理タスクを自動化する | 4 | — | | 107.3 | ローカライズと国際化 | 3 | — | 107.1 は `useradd` / `usermod` / `userdel` / `groupadd` / `passwd` と `/etc/passwd` / `/etc/shadow` / `/etc/group` の構造が必須。107.2 は crontab の書式(分・時・日・月・曜日)と `at` コマンドの使い方が頻出。 ### Topic 108 重要なシステムサービス システム時刻の同期、ログ管理(syslog / rsyslog / systemd-journald)、メール転送エージェントの基本、印刷管理を扱う。 | Objective | 内容 | Weight | 対応記事 | | --------- | -------------------------------- | ------ | -------- | | 108.1 | システム時刻の維持 | 3 | — | | 108.2 | システムのログ記録 | 4 | — | | 108.3 | MTA(Mail Transfer Agent)の基本 | 3 | — | | 108.4 | プリンタと印刷の管理 | 2 | — | 108.1 は `ntpd` / `chrony` / `timedatectl` による時刻同期の仕組みが対象。108.2 は `/var/log/` のファイル構成、`journalctl` の基本オプション、ログローテーション(logrotate)が含まれる。 ### Topic 109 ネットワークの基礎 TCP/IP プロトコル、ネットワークインターフェース設定、基本的なトラブルシューティング、DNS クライアント設定を扱う。109.1〜109.3 はいずれも Weight 4 と高配点で、ネットワーク知識が試験合格の鍵となる。 | Objective | 内容 | Weight | 対応記事 | | --------- | ------------------------------ | ------ | -------- | | 109.1 | インターネットプロトコルの基礎 | 4 | — | | 109.2 | 永続的なネットワーク設定 | 4 | — | | 109.3 | 基本的なネットワークの問題解決 | 4 | — | | 109.4 | クライアント側 DNS の設定 | 2 | — | 109.1 は IPv4 / IPv6 のアドレス体系、サブネットマスク計算、CIDR 表記が必要。109.2 は NetworkManager / `nmcli` / `/etc/network/interfaces` 等の設定方法と起動時の永続化が対象。109.3 は `ping` / `traceroute` / `ss` / `netstat` / `ip route` 等の診断コマンド群が範囲。 ### Topic 110 セキュリティ ユーザーセキュリティ管理、ホストレベルのセキュリティ設定(TCP Wrappers / `nmap`)、暗号化によるデータ保護(GnuPG / OpenSSH)を扱う。110.3 は Weight 4 と高く、SSH 鍵管理の実践知識が問われる。 | Objective | 内容 | Weight | 対応記事 | | --------- | -------------------------- | ------ | -------- | | 110.1 | セキュリティ管理業務の実施 | 3 | — | | 110.2 | ホストのセキュリティ設定 | 3 | — | | 110.3 | 暗号化によるデータの保護 | 4 | — | 110.1 は `sudo` / `su` の使い分け、パスワードエージング(`chage`)、`ulimit` が対象。110.3 は GPG 鍵ペアの生成・管理と SSH 公開鍵認証(`ssh-keygen` / `ssh-agent` / `~/.ssh/authorized_keys`)の操作が頻出。 ## 既存 12 記事マッピング表 {#article-map} Penguin Gym Linux に掲載済みの学習記事と、各記事が対応する Objective の対応表。 | 記事タイトル | 対応 Objective | 試験 | リンク | | -------------------------------- | -------------- | ---- | ------------------------------------------------------------------- | | コマンドライン基礎 | 103.1 | 101 | [記事を読む](/articles/lpic/command-line-basics) | | パイプとリダイレクト入門 | 103.4 | 101 | [記事を読む](/articles/tutorials/pipe-redirect-basics) | | テキストストリームフィルタ | 103.2 | 101 | [記事を読む](/articles/lpic/text-stream-filters) | | 正規表現入門 | 103.7 | 101 | [記事を読む](/articles/lpic/regular-expressions) | | プロセス管理入門 | 103.5 | 101 | [記事を読む](/articles/tutorials/process-management-basics) | | プロセス管理実践 | 103.5 | 101 | [記事を読む](/articles/tutorials/process-management-practical) | | プロセス優先度の制御 | 103.6 | 101 | [記事を読む](/articles/lpic/process-priorities-nice) | | ハードリンクとシンボリックリンク | 104.6 | 101 | [記事を読む](/articles/lpic/hard-symbolic-links) | | パーミッション基礎 | 104.5 | 101 | [記事を読む](/articles/tutorials/permissions-basics) | | パーミッション応用 | 104.5 | 101 | [記事を読む](/articles/tutorials/permissions-advanced) | | パーミッション実践 | 104.5 | 101 | [記事を読む](/articles/tutorials/permissions-practical) | | シェル環境変数の設定 | 105.1 | 102 | [記事を読む](/articles/lpic/shell-environment) | 対応記事のない Objective(—)は今後順次追加予定。記事の追加に合わせてこの表も更新される。 ## 重み配点でみる重点トピック {#priority} Weight は試験問題の出題頻度に対応する。以下は 101 / 102 試験それぞれの高配点 Objective。学習時間を配分する際の優先順位の目安として活用する。 ### 試験 101-500 の高配点 Objective(Weight 4) | Objective | 内容 | | --------- | -------------------------------------- | | 103.1 | コマンドラインで操作する | | 103.3 | 基本的なファイル管理を行う | | 103.4 | ストリーム、パイプ、リダイレクトの使用 | | 103.5 | プロセスの生成、監視、終了 | 4 つすべてが Topic 103(GNU と Unix コマンド)に集中している。試験 101 で合格点を取るには、この 4 つの実践的操作を確実に習得することが最短ルートとなる。 ### 試験 102-500 の高配点 Objective(Weight 4〜5) | Objective | 内容 | Weight | | --------- | ---------------------------------------------------------- | ------ | | 107.1 | ユーザーアカウントとグループアカウントの管理 | 5 | | 105.1 | シェル環境のカスタマイズと使用 | 4 | | 105.2 | シェルスクリプトのカスタマイズまたは記述 | 4 | | 107.2 | ジョブをスケジューリングしてシステム管理タスクを自動化する | 4 | | 108.2 | システムのログ記録 | 4 | | 109.1 | インターネットプロトコルの基礎 | 4 | | 109.2 | 永続的なネットワーク設定 | 4 | | 109.3 | 基本的なネットワークの問題解決 | 4 | | 110.3 | 暗号化によるデータの保護 | 4 | 試験 102 は Weight 4 の Objective が 8 つと多い。特に 107.1 の Weight 5 は全 Objective 中最大で、ユーザー管理コマンドと関連設定ファイルの習得が得点に直結する。 ### 学習順序の指針 1. **101 試験**: Topic 103 を集中的に習得 → Topic 104(パーミッション・ファイルシステム)→ Topic 101〜102 を補完 2. **102 試験**: Topic 107(特に 107.1)と Topic 105 を先行 → Topic 109 のネットワーク操作 → Topic 110 のセキュリティ → Topic 106 / 108 を補完 両試験ともコマンドを実際に打ちながら覚えることが定着の近道。次のセクションで演習環境へのアクセスを案内する。 ## 学習・演習への導線 {#next} 知識の定着には反復演習が不可欠。以下の演習環境を活用する。 - **仮想ターミナル演習**: [ターミナルで練習する](/terminal) — コマンドを実際に打ち込みながら習得できるブラウザ内 Linux 環境 - **クイズで知識確認**: [LPIC-1 クイズに挑戦](/quiz) — 試験形式の問題でアウトプット練習 - **LPIC-1 学習ハブ**: [LPIC-1 対策ページ](/lpic1) — 試験概要・記事一覧・学習ロードマップを一元管理 上記マッピング表にある 12 記事は、Objectives の高配点領域(103.x / 104.5 / 105.1)をカバーしている。各記事末尾の「次に読む」から関連記事を辿ることで体系的に学習を進められる。 ## 出典 {#sources} 本記事の Objectives 情報・Weight 数値はすべて LPI 公式ドキュメント v5.0 に基づく。 - LPI 公式 LPIC-1 概要: [https://www.lpi.org/our-certifications/lpic-1-overview](https://www.lpi.org/our-certifications/lpic-1-overview) - LPI 公式 101-500 / 102-500 Objectives v5.0: [https://www.lpi.org/our-certifications/exam-101-102-objectives/](https://www.lpi.org/our-certifications/exam-101-102-objectives/) - LPI Japan: [https://lpi.or.jp/lpic1/](https://lpi.or.jp/lpic1/) # LPIC-1 落ちた・受からない人向け再受験リカバリガイド - 7 日後 / 14 日後 / 2〜4 週間の戦略 Source: https://penguin-gym-linux.com/articles/lpic/failed-and-retry-recovery ## この記事で達成できること {#intro} - 落ちた直後にやるべきこと(スコアレポート保存・原因の客観視)が分かる - LPI 公式の再受験待機期間(7 日 / 14 日 / 2 年)を一次ソースで確認できる - 落ちる典型 5 パターンに照らして自分の課題を特定できる - 2〜4 週間の再受験学習プランを他 11 記事 + 仮想ターミナルで実行に移せる - 「dumps に頼らずに合格する」現実的なルートを把握できる LPIC-1 に落ちた・受からない経験は珍しくない。weight(出題比重)の大きいトピックを取りこぼした、コマンドを手で動かさず暗記で押し切ろうとした、102 を 101 と同じ感覚で受けた——原因は数パターンに収束する。本記事は LPI 公式の再受験ポリシーを一次ソースとして提示し、待機期間に弱点を潰すための具体的な手順と既存 LPIC 記事への動線をまとめる。 ## 落ちた直後にやること(最初の 24 時間) {#first-24h} 落ちた直後の感情の波は誰にでもある。ただし「落ち込みで学習を止める」のが最大のリスクだ。次の 3 つを 24 時間以内に終わらせると、再受験に向けた立て直しが速くなる。 ### Step 1: スコアレポートを保存する ピアソン VUE 受験後に発行される試験スコアレポートには、トピック別の正答率が記載される。これが弱点分析の唯一の一次データだ。PDF をダウンロード、または画面をスクリーンショットで保存しておく。後から「どこで落ちたか」を客観視するための材料になる。 ### Step 2: 7 日 / 14 日のカレンダーに印を付ける LPI 公式の再受験ポリシー(後述)に従い、1 回目失敗なら 7 日後、2 回目以降なら 14 日後が「再受験可能になる最短日」だ。この日付をカレンダーに記入する。「いつ受け直せるか分からない」状態は不安を増幅させるため、まず最短日を可視化する。 ### Step 3: 「落ちた人は珍しくない」と認識する LPI は合格率を公式公表していない。(出典: LPI 公式 LPIC-1 FAQ)。だが weight の大きいトピックの取りこぼしや 102 試験の範囲拡張に対する準備不足は典型的な失敗パターンで、「自分だけが特別に落ちた」訳ではない。落ちた事実は変わらないが、原因は特定でき、待機期間に潰せる。これが再受験の出発点だ。 ## LPI 公式の再受験ポリシー(一次ソース) {#retake-policy} LPI(Linux Professional Institute)は再受験について次のポリシーを公開している。(出典: [LPI Exam Policies](https://www.lpi.org/our-certifications/policies/), 2026-05 時点) | 状況 | 待機期間 | 補足 | | -------------------- | ----------------------------- | -------------------------------------- | | 1 回目の不合格後 | 7 日(1 週間) | 待機期間中は同一試験の再受験不可 | | 2 回目以降の不合格後 | 14 日 | 3 回目・4 回目も 14 日待機 | | 合格後の再受験 | 2 年 | 同一試験を 2 年間は再受験できない | | 同一試験の扱い | 101-400 と 101-500 は同一試験 | バージョン違いでも待機期間は適用される | > **原文ニュアンスの維持**: LPI 公式は "Anyone who takes an LPI exam once must wait one week before re-taking." / "Anyone who takes an LPI exam a second (and subsequent) time must wait 14 days before re-taking." / "Anyone who passes an LPI exam may not retake that exam for at least two years." と規定している。本記事はこの文言の意味を改変せず日本語要約として提示する。 ### ポリシー解釈の実務ポイント - **再受験予約のタイミング**: 多くのテストセンターは、不合格後すぐに次回予約を入れられる仕様だが、所定の待機期間より前の日付は予約できない。予約画面で「最短予約可能日」を確認すること - **試験コードの違い**: 101-500 と 101-400 のような新旧バージョンは「同一試験」として待機期間の対象。新バージョンへの切り替えで待機期間を回避することはできない - **受験料**: 再受験ごとに通常の受験料が発生する。割引や免除制度は LPI 公式には存在しない。(出典: LPI 公式試験概要ページ) ## 落ちる典型 5 パターン分析 {#failure-patterns} スコアレポートのトピック別正答率を見る前に、行動パターンとして頻出する 5 つを示す。自分がどれに該当するかを 1〜2 個に絞ると、復習方針が決まる。 ### パターン 1: dumps(過去問流出サイト)頼りの暗記 **症状**: 模擬問題は 90% 以上正答できたのに、本番では 500 点に届かない **原因**: dumps の問題と本番の出題が部分一致しているだけで、設問の意図やコマンドの動作原理を理解していない。LPI は dumps 利用を受験規約違反と明示しており、認定取り消しリスクもある。Google スパムポリシー上も問題のあるコンテンツだ。 **対処**: 教材を Ping-t / 書籍付属の模擬問題 / 当サイトのクイズに切り替える。「なぜその答えか」を `man` ページや公式ドキュメントで根拠を取りに行く学習サイクルへ転換する ### パターン 2: コマンドを手で動かさない **症状**: オプション名は覚えているが、本番の穴埋め問題で正確に書けない **原因**: 視覚的な暗記のみで、入力と出力の対応が身体感覚として定着していない **対処**: [LPIC-1 仮想ターミナル演習](/terminal?category=lpic)で各コマンドを実機相当で動かす。`man` で確認したオプションを実際に試し、出力差分を観察する ### パターン 3: weight の大きいトピックを軽視 **症状**: スコアレポートで weight の大きいトピック(例: 101 試験のテキスト処理、ファイル管理、102 試験のシェル環境、システム起動)の正答率が低い **原因**: 「全部均等に」勉強した結果、配点比重の大きいトピックに時間配分が足りない **対処**: LPI 公式試験範囲(v5.0)の weight を確認し、配点上位 5〜6 トピックを集中復習する。詳細は [LPIC-1 出題範囲完全ガイド](/articles/lpic/exam-scope-101-102) を参照 ### パターン 4: 102 試験を 101 と同じ感覚で受ける **症状**: 101 試験は受かったが、102 試験で落ちる **原因**: 102 はシェルスクリプト・ネットワーク設定・セキュリティ・システム管理まで範囲が広がるが、101 と同じ学習時間で挑んでしまう **対処**: 102 試験向けに別途学習時間を確保する。[シェル環境変数](/articles/lpic/shell-environment) や 102 のシステム起動・パッケージ管理・ネットワーク設定トピックを個別に復習する ### パターン 5: 試験予約後に学習ペースが落ちる **症状**: 予約直後は集中したが、試験 1〜2 週間前にモチベーションが落ちる **原因**: 「あと N 日」という締切意識だけで、日次タスクが具体化していない **対処**: 再受験までの 2〜4 週間を 1 日単位のタスクに分解する(後述「再受験までの 2〜4 週間プラン」参照) ## 弱点分析の手順 {#weakness-analysis} スコアレポートと当サイトのクイズを組み合わせて、復習対象を絞る。 ### Step 1: スコアレポートのトピック別正答率を確認 LPI のスコアレポートには、試験範囲の主題(Topic)ごとの正答率が掲載される。weight(出題比重)と正答率を 2 軸で評価し、次のマトリクスで優先度を決める。 | weight × 正答率 | 優先度 | 対処 | | --------------------- | ------ | --------------------- | | weight 大 × 正答率 低 | 最優先 | 復習時間の 60% を配分 | | weight 大 × 正答率 中 | 高 | 復習時間の 25% を配分 | | weight 中 × 正答率 低 | 中 | 復習時間の 10% を配分 | | その他 | 低 | 残り 5% で軽く再確認 | ### Step 2: 当サイトのカテゴリ別クイズで再確認 [LPIC-1 理解度クイズ](/quiz?category=lpic) で、スコアレポートで弱点と判定したトピックを集中演習する。間違えた問題は仮想ターミナルで実コマンドを動かして確認する。 ### Step 3: 関連既存記事で原理を復習 クイズで「なぜその答えか」を言語化できなければ、対応する既存記事を読む。次のセクションでトピック別の導線を示す。 ## 弱点別の復習導線 {#topic-routes} スコアレポートで特定した弱点トピックに応じて、既存 LPIC 記事と仮想ターミナルへ動線をつなぐ。最低 6 トピック分の導線をここで完備する。 ### 101 試験の弱点別 - **コマンドライン操作・シェル・bash の仕組み** → [コマンドライン基礎](/articles/lpic/command-line-basics) - **テキスト処理(cat / sort / uniq / wc / head / tail)** → [テキストストリームフィルタ](/articles/lpic/text-stream-filters) - **正規表現(基本 / 拡張 / grep 応用)** → [正規表現の基礎](/articles/lpic/regular-expressions) - **ファイル管理・ハードリンク・シンボリックリンク** → [ハードリンクとシンボリックリンク](/articles/lpic/hard-symbolic-links) - **プロセス管理・優先度(nice / renice)** → [プロセス優先度と nice](/articles/lpic/process-priorities-nice) ### 102 試験の弱点別 - **シェル環境・環境変数(export / env / .bashrc / .bash_profile)** → [シェル環境変数](/articles/lpic/shell-environment) - **102 範囲全体(シェルスクリプト / システム起動 / パッケージ管理 / ネットワーク / セキュリティ)の出題比重を確認** → [LPIC-1 出題範囲完全ガイド(101 / 102 全トピック対応表)](/articles/lpic/exam-scope-101-102) - **LPIC とは何か・LinuC との違いの再確認** → [LPIC-1 とは(制度・試験構成・有効期限)](/articles/lpic/what-is-lpic1) ### 全範囲の地図確認 - **どのトピックが weight 何点か分からない** → [LPIC-1 出題範囲完全ガイド](/articles/lpic/exam-scope-101-102) - **101 と 102 のどちらから受け直すか迷う** → [LPIC-1 vs LPIC-2 / Linux+ / Linux Essentials / LinuC 比較](/articles/lpic/lpic1-comparison) ### 実機演習 - **コマンドを手で動かして定着** → [LPIC-1 仮想ターミナル演習](/terminal?category=lpic) ## 再受験までの 2〜4 週間プラン {#retake-plan} LPI 公式の最低待機期間(1 回目 7 日 / 2 回目以降 14 日)と、復習に必要な目安時間から、現実的な 2〜4 週間プランを示す。出典は当サイト [LPIC-1 勉強時間ガイド](/articles/lpic/study-time-and-method) の 2 週間プラン(2 科目合計 60〜80 時間)。1 科目あたりに換算すると 30〜40 時間、2 科目復習なら 60〜80 時間が目安となり、再受験が 1 科目なら 2 週間、2 科目以上なら 4 週間プランを推奨する。 ### 2 週間プラン(1 回目不合格 + 弱点 1 科目) | 期間 | タスク | | ---------- | ------------------------------------------------------------------- | | Day 1〜2 | スコアレポート分析・弱点トピック特定・教材選定 | | Day 3〜7 | weight 大 × 正答率低トピックの集中復習(既存記事 + 仮想ターミナル) | | Day 8〜10 | 当サイトクイズ + 書籍付属模擬問題で再確認 | | Day 11〜12 | 全範囲の総点検(誤答ノートを見直す) | | Day 13 | 軽く流す日 + 試験予約最終確認 | | Day 14 | 再受験当日 | ### 4 週間プラン(2 回目以降不合格 + 弱点 2 科目以上) | 期間 | タスク | | ------ | --------------------------------------------------------------------------------------------------- | | Week 1 | スコアレポート分析 + weight 大トピックの再学習(既存記事 + 仮想ターミナル) | | Week 2 | 弱点トピックのクイズと模擬問題で正答率を 80% 以上へ引き上げる | | Week 3 | 全範囲の総点検 + 102 試験の場合は範囲拡張部分(シェルスクリプト・ネットワーク・セキュリティ)の補強 | | Week 4 | 模擬問題セット 1 セットを時間計測で解く → 弱点再確認 → 再受験 | ## 心理ケアと学習継続の工夫 {#mental-care} 落ちた直後の感情の波で学習が止まるケースは多い。次の 3 つを意識すると、再受験までのモチベーションを維持しやすい。 ### 1. 「落ちた事実」と「落ちた原因」を分離する 落ちた事実は変えられないが、落ちた原因はスコアレポートで特定できる。「自分はダメだ」と思考が一般化したら、スコアレポートに戻って「どのトピックの正答率が低かったか」だけに視点を戻す。 ### 2. 1 日単位の小さな勝ちを積む 「全範囲を完璧に」ではなく「今日はテキスト処理のオプションを 3 つ手で動かす」のように、1 日単位で完了可能なタスクに分解する。完了の積み重ねが自信回復につながる。 ### 3. dumps の誘惑を切る 「dumps で楽に受かりたい」という気持ちは落ちた後ほど強くなる。だが LPI 受験規約違反と認定取り消しリスクを天秤にかけると割に合わない。Ping-t / 書籍付属模擬問題 / 当サイトクイズで十分演習できる。 ## 落ちないための注意点(再受験前チェック) {#pitfalls} ### 症状: 模擬試験は通るが本番で再び落ちそうで不安 **確認**: - 正答した問題で「なぜその答えか」を言語化できるか - オプションの意味を `man` ページで確認できるか **対処**: 仮想ターミナルで実コマンドを動かし、入出力を観察する。「覚えた」ではなく「使える」状態を目指す ### 症状: スコアレポートを紛失した **確認**: - ピアソン VUE のマイページで過去のスコアレポートを再ダウンロード可能か - LPI Marketplace(受験者ポータル)でログイン履歴を確認できるか **対処**: ピアソン VUE のサポートに問い合わせるか、LPI Marketplace で履歴を確認する。スコアレポートなしでも、当サイトのカテゴリ別クイズで弱点を再特定できる ### 症状: 待機期間中にモチベーションが切れる **確認**: - 1 日単位のタスクが具体化されているか(「2 時間勉強」ではなく「テキストストリームフィルタの記事 1 本 + 仮想ターミナルで 5 コマンド実行」) - 再受験日をカレンダーに記入しているか **対処**: 上記の「再受験までの 2〜4 週間プラン」を 1 日単位のタスク表に書き写し、毎日チェックを付ける ## 再受験前チェックリスト {#checklist} 再受験予約前に以下をすべて確認する。 - [ ] スコアレポートのトピック別正答率を確認した - [ ] weight 大 × 正答率低のトピックを 5 つ以下に絞った - [ ] 各トピックに対応する既存記事を読み終えた - [ ] 仮想ターミナルで該当コマンドを 1 通り動かした - [ ] カテゴリ別クイズで正答率 80% 以上を達成した - [ ] 模擬問題 1 セットを時間計測で解いた - [ ] LPI 公式の待機期間(7 日 / 14 日)を満たしている - [ ] dumps を使っていない(LPI 受験規約違反 + 認定取り消しリスク回避) ## まとめ {#summary} | 状況 | 待機期間 | 推奨学習期間 | | ----------------------------- | -------- | ------------ | | 1 回目不合格・弱点 1 科目 | 7 日 | 2 週間 | | 2 回目以降不合格・弱点 1 科目 | 14 日 | 2〜3 週間 | | 弱点 2 科目以上 | 14 日 | 4 週間 | 落ちた原因はスコアレポートで特定でき、弱点は既存記事 + 仮想ターミナル + クイズで潰せる。LPI 公式の最低待機期間を満たしつつ、weight の大きいトピックから優先的に時間を配分すれば、再受験で 500 点を超える現実的なルートが描ける。dumps に頼らず、`man` ページと実コマンドを軸にした学習サイクルへ転換することが、合格の最短ルートだ。 理解度クイズで現在地を確認 → 仮想ターミナルで実操作 → 既存記事で原理確認、が当サイトの学習フロー。再受験に向けて 1 つずつ積み上げていけばよい。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全 12 記事と出題範囲マップ)](/lpic1) - [LPIC-1 勉強時間・勉強方法・最短攻略ガイド](/articles/lpic/study-time-and-method) - [LPIC-1 出題範囲完全ガイド](/articles/lpic/exam-scope-101-102) - [LPIC-1 難易度・合格点・配点 完全FAQ](/articles/lpic/difficulty-and-passing-score) - [LPIC-1 理解度クイズ](/quiz?category=lpic) - [LPIC-1 仮想ターミナル演習](/terminal?category=lpic) # FHS とファイル配置・検索 - find/locate と FHS【LPIC-1 104.7】 Source: https://penguin-gym-linux.com/articles/lpic/fhs-and-finding-files ## この記事で達成できること {#intro} - FHS(Filesystem Hierarchy Standard)の主要ディレクトリの用途を説明できる - 「共有可能/非共有」「静的/可変」の区分でファイルの置き場所を判断できる - `find` の主要テスト(`-name` / `-type` / `-size` / `-mtime` / `-perm` / `-user` / `-newer` / `-exec`)を使い分けられる - `locate` / `updatedb` の仕組みと `/etc/updatedb.conf` を理解できる - `find` と `locate`、`which` / `whereis` / `type` の使い分けを根拠付きで答えられる LPIC-1 主題 104.7「システムファイルを見つけ、正しい位置に配置する」の中核。設定ファイルやログがどこにあるべきかを FHS で判断し、`find` / `locate` で実際に探し当てる技術。 ## FHS のどこに何を置くか {#fhs} FHS 3.0 は Linux のディレクトリ構造を標準化した仕様。各ディレクトリには明確な役割があり、ファイルの種類で置き場所が決まる。まずは試験頻出の主要ディレクトリを押さえる。 | ディレクトリ | FHS 上の用途 | | ------------ | -------------------------------------------- | | `/bin` | 全ユーザーが使う必須コマンドのバイナリ | | `/sbin` | システム管理用のバイナリ | | `/etc` | ホスト固有のシステム設定(静的・非共有) | | `/lib` | 必須の共有ライブラリとカーネルモジュール | | `/usr` | 共有可能・読み取り専用のプログラムとデータ | | `/usr/bin` | 一般ユーザー向けの大半のコマンド | | `/usr/sbin` | 起動に必須でないシステム管理コマンド | | `/usr/local` | 管理者がローカルに導入したソフト | | `/var` | 運用中に変化する可変データ | | `/var/log` | ログファイル | | `/var/spool` | 印刷・メール等のスプールデータ | | `/tmp` | 一時ファイル | | `/home` | 一般ユーザーのホームディレクトリ | | `/root` | root ユーザーのホームディレクトリ | | `/boot` | ブートローダの静的ファイルとカーネル | | `/dev` | デバイスファイル | | `/proc` | プロセス・カーネル情報の仮想ファイルシステム | | `/sys` | デバイス・カーネルの仮想ファイルシステム | | `/opt` | 追加アプリケーションソフトウェアパッケージ | | `/mnt` | 一時的にマウントするファイルシステムの場所 | | `/media` | リムーバブルメディアのマウントポイント | | `/srv` | このシステムが提供するサービス用データ | `/bin` と `/sbin` には「`/usr` をマウントする前に必要なコマンド」を置く決まりがある。起動直後やシングルユーザーモードでも `/usr` が別パーティションで未マウントの状況があるため、最低限の復旧コマンドはルートパーティション側に置く設計になっている。 ::: tip `/proc` と `/sys` はディスク上に実体がない仮想ファイルシステム。`/proc/cpuinfo` や `/sys/class/` はカーネルが動的に生成する情報で、編集してファイルを「保存」する場所ではない。 ::: ## 共有可能/非共有・静的/可変とは {#classification} FHS は 2 つの独立した軸でファイルを分類する。「共有可能(shareable)か非共有(unshareable)か」と「静的(static)か可変(variable)か」。この区分が分かると、なぜ `/usr` と `/var` が分かれているのかが腑に落ちる。 - 共有可能: 1 台に置いて複数ホストから使えるファイル(例: `/usr` のプログラム、`/home`) - 非共有: そのホスト固有のファイル(例: `/etc` の設定、デバイスのロックファイル) - 静的: 管理者が手を入れない限り変わらないファイル(バイナリ・ライブラリ・ドキュメント) - 可変: 運用中に通常変化するファイル(ログ・スプール・キャッシュ) | | 共有可能 | 非共有 | | ---- | ------------------------------ | ----------------------- | | 静的 | `/usr`、`/opt` | `/boot`、`/etc` | | 可変 | `/var/mail`、`/var/spool/news` | `/var/run`、`/var/lock` | この分類の狙いは、静的ファイルを読み取り専用メディアに置いたり、可変ファイルだけ別のバックアップ方針にしたりできるようにすること。歴史的に UNIX は両者を混在させていたが、可変ファイルを `/var` に集約することで `/usr` を読み取り専用で別マウントできるようになった。 ## /usr と /usr/local の使い分け {#usr-local} ディストリビューションのパッケージ管理が入れるソフトは `/usr`(`/usr/bin` 等)に、管理者が手動で導入したローカルなソフトは `/usr/local`(`/usr/local/bin` 等)に置く。役割を分ける理由は明確で、混ぜないことがシステム保守の前提になる。 `/usr` 配下はパッケージマネージャ(apt / dnf 等)の管理領域で、OS アップグレードやパッケージ更新で上書きされる可能性がある。一方 `/usr/local` はパッケージ管理が触らない領域なので、ソースからビルドしたソフトや自前スクリプトをここに置けば、OS 更新で消える・衝突する事故を避けられる。 ::: warning 自前ビルドしたコマンドを `/usr/bin` に直接コピーすると、同名パッケージの更新時に上書き・衝突するおそれがある。手動導入は `/usr/local` に置くのが FHS の意図。 ::: ## find の主要テストを使い分ける {#find} `find` は指定ディレクトリ以下を実時間で走査し、条件に合うファイルを探す。`locate` と違って常に最新の状態を反映し、検索後の処理(削除・権限変更等)まで一気にできるのが強み。 ### Step 1: 名前と種類で絞り込む ```bash find /etc -name '*.conf' -type f ``` ```output /etc/ssh/sshd_config.conf /etc/logrotate.conf /etc/resolv.conf ``` `-name` はファイル名(パス末尾のベース名)をシェルパターンで照合する。`-type f` は通常ファイル、`-type d` はディレクトリ、`-type l` はシンボリックリンクを表す。大文字小文字を無視するなら `-iname` を使う。 ### Step 2: サイズと更新日時で探す ```bash find /var/log -type f -size +100M find /home -type f -mtime -7 ``` ```output /var/log/journal/system.journal /home/user/report-draft.md ``` `-size +100M` は 100 MiB を超えるファイル。接尾辞は `c`(バイト)、`k`(KiB)、`M`(MiB)、`G`(GiB)で、`+` は「より大きい」、`-` は「より小さい」を意味する。`-mtime -7` は「7×24 時間以内に更新」。`-mtime +1` は「2 日以上前に更新」を意味する点に注意(端数は切り捨て)。 ### Step 3: 所有者と権限で探す ```bash find /home -user alice -perm -0200 find / -perm /4000 -type f 2>/dev/null ``` ```output /home/alice/notes.txt /usr/bin/passwd ``` `-user alice` は所有者が alice のファイル。`-perm /4000` は「指定ビットのいずれかが立っている」(ここでは SUID)を意味し、`-perm -0664` のように `-` を付けると「指定ビットがすべて立っている」になる。SUID ビットの調査などに使える。 ### Step 4: 検索結果をまとめて処理する ```bash find /tmp -name '*.tmp' -mtime +7 -exec rm {} + find . -name '*.txt' -newer reference.txt -exec ls -l {} \; ``` ```output -rw-r--r-- 1 user user 320 May 30 10:00 ./new-note.txt ``` `-exec command {} \;` は一致したファイルごとにコマンドを 1 回実行し、`{}` がファイル名に置換される。`-exec command {} +` は複数ファイルをまとめて 1 つのコマンドラインに渡す(`xargs` に近い動作)ため効率的。`-newer reference.txt` は基準ファイルより新しく更新されたものを選ぶ。 ::: warning `-exec ... {} \;` のセミコロンはシェルに解釈されないよう `\;` とエスケープする(または `';'`)。これを忘れると `find: missing argument to -exec` 等のエラーになる。 ::: ## locate と updatedb の仕組み {#locate} `locate` はファイルシステムを直接走査せず、あらかじめ作られたファイル名データベースを検索するため非常に高速。多くのディストリビューションでは mlocate 実装が使われる。 ### Step 1: locate で高速検索する ```bash locate sshd_config locate -i readme ``` ```output /etc/ssh/sshd_config /usr/share/doc/openssh-server/README ``` `locate` はデータベースに登録された名前を瞬時に返す。`-i`(`--ignore-case`)で大文字小文字を無視できる。ただし結果は「データベースを最後に更新した時点」のスナップショットである点に注意。 ### Step 2: updatedb でデータベースを更新する ```bash sudo updatedb locate newfile.txt ``` ```output /home/user/newfile.txt ``` `updatedb` がデータベースを再構築する。新規作成・削除したファイルは `updatedb` を実行するまで `locate` の結果に反映されない。多くの環境では cron 等で定期実行されるが、作成直後のファイルを探すなら手動更新が必要。 ### Step 3: /etc/updatedb.conf で対象を調整する ```bash grep -E 'PRUNEPATHS|PRUNEFS' /etc/updatedb.conf ``` ```output PRUNEFS="NFS nfs nfs4 afs binfmt_misc ..." PRUNEPATHS="/tmp /var/spool /media /var/lib/os-prober ..." ``` `/etc/updatedb.conf` で索引対象を調整する。`PRUNEPATHS` は索引から除外するディレクトリ、`PRUNEFS` は除外するファイルシステム種別。これにより `/tmp` やネットワークマウント等が無駄に索引されるのを防いでいる。 ## find と locate はどう使い分けるか {#find-vs-locate} リアルタイム性が必要なら `find`、名前だけ高速に探すなら `locate`。両者は競合せず、用途で使い分けるのが正解。 `find` はディスクを実走査するため遅いが、常に最新で、サイズ・更新日時・権限・所有者といった多彩な条件と、`-exec` による後続処理が使える。`locate` はデータベース参照で一瞬だが、名前ベースの検索に限られ、`updatedb` 後でないと最新状態を反映しない。「さっき作ったファイルがどこか」は `find`、「あの設定ファイルのパスは」は `locate` が向く。 ## which / whereis / type の違い {#which-whereis-type} コマンドの「場所」を調べる 3 つは目的が異なる。実行されるバイナリの場所は `which`、関連ファイル一式は `whereis`、シェルがどう解釈するかは `type`。 ```bash which python3 whereis ls type cd type ll ``` ```output /usr/bin/python3 ls: /usr/bin/ls /usr/share/man/man1/ls.1.gz cd is a shell builtin ll is aliased to `ls -alF' ``` - `which`: `PATH` 上から実行されるコマンドのフルパスを返す - `whereis`: コマンドのバイナリ・ソース・man ページの場所を探す - `type`: シェル組み込み。その名前がエイリアス・組み込み・関数・キーワード・ファイルのどれとして解釈されるかを示す ::: tip `cd` のようなシェル組み込みコマンドは `which` では見つからない(実行ファイルが存在しないため)。「なぜ which cd が空なのか」は `type cd` が組み込みだと教えてくれる。 ::: ## よくあるミスと対処 {#mistakes} - locate の結果が古い: 作ったばかりのファイルが出ない / 消したはずのファイルが残る。`sudo updatedb` でデータベースを更新する - `-exec` のセミコロン忘れ: `find ... -exec rm {} \;` の `\;` を書かず `find: missing argument to -exec` になる。`\;` または `+` で終端する - `/usr` と `/usr/local` の混同: 自前ビルドのソフトを `/usr/bin` に入れてパッケージ更新で衝突。手動導入は `/usr/local` に置く - `-mtime` の符号誤解: `-mtime -1`(24 時間以内)と `-mtime +1`(2 日以上前)を取り違える。「今日のファイル」は `-mtime -1` - which でシェル組み込みを探す: `which cd` が空で戸惑う。組み込み・エイリアスの確認は `type` を使う ## トラブルシューティング {#troubleshooting} ### 症状: locate で作成直後のファイルが見つからない **原因**: locate はデータベースを参照し、`updatedb` 実行時点のスナップショットしか持たない **確認**: ```bash locate newfile.txt ``` **対処**: `sudo updatedb` でデータベースを更新してから再検索する。最新状態を確実に探すなら `find` を使う。 ### 症状: find -exec でエラー「missing argument to -exec」 **原因**: コマンド終端のセミコロンがエスケープされていない **確認**: ```bash find . -name '*.log' -exec ls -l {} \; ``` **対処**: `-exec` のコマンドは `\;`(1 件ずつ実行)または `+`(まとめて実行)で必ず終端する。 ### 症状: find / を実行すると権限エラーが大量に出る **原因**: 一般ユーザーで読めないディレクトリ(`/proc` の一部や他ユーザー領域)を走査している **確認**: ```bash find / -name target.conf 2>/dev/null ``` **対処**: `2>/dev/null` で標準エラーを捨てるか、検索範囲を `/etc` 等に絞る。システム全体を確実に探すなら `sudo find` を使う。 ## 作業完了チェックリスト {#checklist} - [ ] 主要 FHS ディレクトリの用途を表で確認した - [ ] 共有可能/非共有・静的/可変の区分を理解した - [ ] `find` で名前・種類・サイズ・更新日時・権限・所有者の検索を試した - [ ] `-exec {} \;` と `-exec {} +` の違いを確認した - [ ] `locate` 検索後に `sudo updatedb` で更新を反映した - [ ] `which` / `whereis` / `type` を使い分けた ## まとめ {#summary} | 目的 | コマンド / 場所 | ポイント | | ------------------ | ---------------------------- | ---------------------- | | 設定ファイルの場所 | `/etc`(静的・非共有) | ホスト固有 | | 可変データの場所 | `/var`(ログ・スプール) | 運用で変化 | | 手動導入ソフト | `/usr/local` | パッケージ管理と分離 | | 条件付き検索+処理 | `find -size/-mtime/-exec` | 実時間・最新・後処理可 | | 名前の高速検索 | `locate` + `sudo updatedb` | DB 参照・要更新 | | 実行ファイルの場所 | `which` / `whereis` / `type` | 用途で使い分け | FHS はファイルの「正しい置き場所」を判断する地図、`find` / `locate` はその地図上で目的のファイルを探す道具。両方を押さえれば、未知のシステムでも設定やログを迷わず辿れる。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [ハードリンクとシンボリックリンク](/articles/lpic/hard-symbolic-links) - [テキストストリームのフィルタ処理](/articles/lpic/text-stream-filters) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # ハードリンクとシンボリックリンク - lnコマンドとinodeの仕組み Source: https://penguin-gym-linux.com/articles/lpic/hard-symbolic-links ## この記事で達成できること {#intro} - ハードリンクとシンボリックリンクの違いを inode の観点で説明できる - `ln` / `ln -s` を状況に応じて正しく使い分けられる - リンク切れの原因を特定し回避できる - ファイルシステム境界・ディレクトリに関するリンクの制約を理解できる - 試験頻出の「ハードリンクは別 FS をまたげない」を根拠付きで答えられる LPIC-1 主題 104.6「ハードリンクとシンボリックリンクを作成し、管理する」の中核。リンクの理解には inode(ファイルの実体を指す番号)の概念が不可欠。 ## ハードリンクとシンボリックリンクの判断 {#flow} | 観点 | ハードリンク | シンボリックリンク | | ------------------ | ------------------- | -------------------------- | | 作成コマンド | `ln src link` | `ln -s target link` | | 実体 | 同一 inode への別名 | 別 inode、パス文字列を保持 | | 別ファイルシステム | 不可 | 可 | | ディレクトリ対象 | 原則不可 | 可 | | 元ファイル削除時 | データは残る | リンク切れになる | | `ls -li` の inode | 元と同一 | 元と異なる | 「別 FS をまたぐ」「ディレクトリを指す」必要があるならシンボリックリンク一択。同一 FS 内でファイル実体を共有しバックアップ的に多重参照したいならハードリンク。 ## 手順 {#steps} ### Step 1: inode とリンク数を確認する ```bash echo "data" > original.txt ls -li original.txt ``` ```output 1310721 -rw-r--r-- 1 user user 5 May 17 10:00 original.txt ``` 先頭の `1310721` が inode 番号、`1` がハードリンク数。inode はファイルの実体(データブロック・メタ情報)を一意に指す。ファイル名は inode への参照に過ぎない。 ### Step 2: ハードリンクを作成する ```bash ln original.txt hardlink.txt ls -li original.txt hardlink.txt ``` ```output 1310721 -rw-r--r-- 2 user user 5 May 17 10:00 original.txt 1310721 -rw-r--r-- 2 user user 5 May 17 10:00 hardlink.txt ``` 両方が同じ inode `1310721` を指し、リンク数が `2` に増えた。どちらの名前から編集しても同じ実体を更新する。`original.txt` を削除しても inode はリンク数が 0 になるまで解放されない。 ### Step 3: シンボリックリンクを作成する ```bash ln -s original.txt symlink.txt ls -li original.txt symlink.txt ``` ```output 1310721 -rw-r--r-- 2 user user 5 May 17 10:00 original.txt 1310733 lrwxrwxrwx 1 user user 12 May 17 10:01 symlink.txt -> original.txt ``` `symlink.txt` は別の inode `1310733` を持ち、`-> original.txt` というパス文字列を保持する。種別フラグが `l`(リンク)になっている点に注目。 ### Step 4: リンクの解決先を確認する ```bash readlink symlink.txt readlink -f symlink.txt ls -L symlink.txt ``` ```output original.txt /home/user/original.txt -rw-r--r-- 2 user user 5 May 17 10:00 symlink.txt ``` `readlink` はリンク先の生の文字列、`readlink -f` は最終的に解決された絶対パスを返す。`ls -L` はリンクをたどって実体の情報を表示する。 ### Step 5: 壊れたリンクを検出する ```bash rm original.txt cat symlink.txt find . -xtype l ``` ```output cat: symlink.txt: No such file or directory ./symlink.txt ``` 元ファイルを削除すると `symlink.txt` はリンク切れ(dangling symlink)になる。`find . -xtype l` で壊れたシンボリックリンクを一覧できる。一方ハードリンクは元名を削除してもデータは保持される。 ## なぜハードリンクは別FSをまたげないのか {#why} inode 番号はファイルシステムごとに独立した名前空間。`/dev/sda1` の inode 100 と `/dev/sdb1` の inode 100 は無関係な別実体。ハードリンクは「同一 inode への別名」であるため、異なるファイルシステム間では inode を共有できず作成が拒否される。 シンボリックリンクは inode ではなくパス文字列を保持する独立したファイル。パスは FS をまたいで表現できるため、別パーティション・別ディスク・存在しないパスすら指せる。代償としてリンク先が消えれば即座にリンク切れになる。この設計差が「ハードリンクは堅牢だが制約が多い、シンボリックリンクは柔軟だが脆い」というトレードオフを生む。 ディレクトリへのハードリンクが原則禁止なのは、ディレクトリ階層に循環参照が生じ `find` などのツリー走査が無限ループに陥るのを防ぐため。 ## トラブルシューティング {#troubleshooting} ### 症状: ln でハードリンク作成が Invalid cross-device link で失敗する **原因**: リンク元とリンク先が別ファイルシステム **確認**: ```bash df original.txt /mnt/other/ ``` **対処**: 別 FS をまたぐ必要があるなら `ln -s` でシンボリックリンクを使う。`df` で両者が同一マウントか確認する。 ### 症状: シンボリックリンクが意図せぬ場所を指す **原因**: 相対パスでリンクを作成しリンク自体を移動した **確認**: ```bash readlink -f link ``` **対処**: 移動の可能性がある場合は絶対パスでターゲットを指定する(`ln -s /abs/path/target link`)。相対リンクはリンクの位置基準で解決される点に注意。 ### 症状: ln -s で既存リンクを更新できない **原因**: 同名のリンクが既に存在し `File exists` になる **確認**: ```bash ls -l link ``` **対処**: `ln -sf target link` で強制上書きする。ただし対象がディレクトリの場合は `ln -sfn` を併用しないとリンク先内部に作成される事故に注意。 ## 作業完了チェックリスト {#checklist} - [ ] `ls -li` で inode 番号とリンク数を確認した - [ ] ハードリンク作成後にリンク数が増えることを確認した - [ ] シンボリックリンクが別 inode・パス文字列保持であることを確認した - [ ] `readlink -f` で最終解決先を確認した - [ ] `find . -xtype l` で壊れたリンクを検出した ## まとめ {#summary} | 場面 | コマンド | 目的 | | ------------------ | ------------------- | ----------------------- | | ハードリンク | `ln src link` | 同一 FS で実体共有 | | シンボリックリンク | `ln -s target link` | 別 FS・ディレクトリ対応 | | inode 確認 | `ls -li` | リンク数・実体判定 | | 解決先確認 | `readlink -f` | 最終的な絶対パス | | 壊れリンク検出 | `find . -xtype l` | dangling symlink 一覧 | リンク機構はファイルシステム理解の要。次はシェル環境やプロセス優先度に進むと運用知識が連結する。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全12記事と出題範囲マップ)](/lpic1) - [chmod・chownの使い方](/articles/tutorials/permissions-basics) - [chmod・chown・sudoの使い分け](/articles/tutorials/permissions-advanced) - [コマンドライン基礎](/articles/lpic/command-line-basics) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # ホストセキュリティ - 不要サービス停止/TCP Wrapper【LPIC-1 110.2】 Source: https://penguin-gym-linux.com/articles/lpic/host-security ## この記事で達成できること {#intro} - `ss -tulnp` でリッスン中のサービスを洗い出せる - `systemctl stop` / `disable` / `mask` の違いを正確に説明できる - TCP Wrappers の `/etc/hosts.allow` → `/etc/hosts.deny` 評価順を理解する - スーパーサーバ(inetd / xinetd)の役割を把握する - `/etc/nologin`・シャドウパスワード(`pwconv`)など周辺の防御策を扱える LPIC-1 主題 110.2「ホストのセキュリティを設定する」の中核。攻撃面(アタックサーフェス)を減らす「不要サービスの停止」と、接続元を絞る「アクセス制御」の二本柱を押さえる。 ## ホストセキュリティの基本方針とは {#policy} 原則は「動いていないサービスは攻撃されない」。まず不要サービスを止め、残すサービスは接続元を絞る。この優先順位を間違えない。 ホストの堅牢化は次の順序で進めると漏れがない。 1. 何がリッスンしているかを把握する(`ss -tulnp`) 2. 不要なサービスを停止・無効化する(`systemctl`) 3. 残すサービスへの接続元を制限する(TCP Wrappers / ファイアウォール) 4. ログインや認証まわりを締める(`/etc/nologin`・シャドウパスワード) ::: tip TCP Wrappers(libwrap)は新しいディストリビューションでは採用が縮小しており、Debian / Ubuntu は libwrap サポートを廃止する方向にある。ただし LPIC-1 110.2 の出題範囲には含まれるため、本記事でも仕組みを解説する。 ::: ## リッスンしているサービスを確認するには {#listening} まず「今、何が外部接続を待ち受けているか」を可視化する。`ss -tulnp` が現行の標準コマンド。 ```bash ss -tulnp ``` ```output Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process udp UNCONN 0 0 0.0.0.0:68 0.0.0.0:* users:(("dhclient",pid=712,fd=6)) tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=901,fd=3)) tcp LISTEN 0 100 127.0.0.1:25 0.0.0.0:* users:(("master",pid=980,fd=13)) ``` `ss` のオプションは次の意味。`-p` は root 権限がないとプロセス名が表示されない。 | オプション | 意味 | | ---------- | -------------------------------- | | `-t` | TCP ソケット | | `-u` | UDP ソケット | | `-l` | リッスン中(LISTEN)のみ | | `-n` | ポート番号を名前解決せず数値表示 | | `-p` | ソケットを使うプロセスを表示 | ::: tip `ss` は `iproute2` パッケージのコマンドで、レガシーな `netstat -tulnp`(`net-tools`)の後継。出力はほぼ同じ意味で読める。 ::: ここで `0.0.0.0:Port` は全インターフェースで待ち受け(外部到達可能)、`127.0.0.1:Port` はループバックのみ(外部からは到達不可)を示す。上の例では SSH(22)が外部公開、SMTP(25)はローカル限定だと読み取れる。 ## stop・disable・mask の違いとは {#stop-disable-mask} 結論。`stop` は「今止める」、`disable` は「次回起動しない」、`mask` は「二度と起動できなくする」。役割が異なり、組み合わせて使う。 ### 不要サービスを停止・無効化する手順 ```bash systemctl stop avahi-daemon systemctl disable avahi-daemon ``` ```output Removed "/etc/systemd/system/multi-user.target.wants/avahi-daemon.service". ``` `stop` だけでは再起動後にまた立ち上がる。永続的に止めるには `disable` が必須。両方を 1 行で行うのが `--now`。 ```bash systemctl disable --now avahi-daemon ``` `disable --now` は「即時停止(stop 相当)」と「自動起動無効化(disable)」を同時に実行する。実務で最も使う形。 ### mask で起動不能にロックする ```bash systemctl mask avahi-daemon systemctl start avahi-daemon ``` ```output Created symlink /etc/systemd/system/avahi-daemon.service → /dev/null. Failed to start avahi-daemon.service: Unit avahi-daemon.service is masked. ``` `mask` はユニットファイルへのシンボリックリンクを `/dev/null` に張り、手動 `start` も依存関係による起動もすべて拒否する。`disable` より強い。解除は `unmask`。 | コマンド | 即時停止 | 自動起動 | 手動起動 | 用途 | | --------------- | -------- | -------- | -------- | -------------------------- | | `stop` | する | 変わらず | 可能 | 一時的に止める | | `disable` | しない | 無効 | 可能 | 次回から自動起動させない | | `disable --now` | する | 無効 | 可能 | 実務の標準形 | | `mask` | しない | 無効 | 不可 | 絶対に起動させたくない場合 | 状態確認は `systemctl is-enabled svc`(自動起動の有無)と `systemctl is-active svc`(稼働中か)で行う。 ::: warning `mask` したサービスは依存する他サービスの起動も巻き込んで失敗させることがある。本当に不要なものだけに使い、迷ったら `disable` に留める。 ::: ## スーパーサーバ(inetd / xinetd)とは {#superserver} スーパーサーバは複数の小規模サービスを代理で待ち受け、接続が来たときだけ対象デーモンを起動する仕組み。常駐プロセスを減らせる。 歴史的には `inetd`、その拡張版が `xinetd`。設定ファイルの構造は次の通り。 | ファイル / ディレクトリ | 役割 | | ----------------------- | ----------------------------------------------------- | | `/etc/inetd.conf` | inetd の設定(サービスごとに 1 行) | | `/etc/xinetd.conf` | xinetd のメイン設定・デフォルト値 | | `/etc/xinetd.d/` | xinetd のサービス別設定ファイルを格納するディレクトリ | xinetd でサービスを止める基本は、該当サービス定義に `disable = yes` を書いて xinetd をリロードすること。 ```bash cat /etc/xinetd.d/telnet ``` ```output service telnet { socket_type = stream protocol = tcp wait = no user = root server = /usr/sbin/in.telnetd disable = yes } ``` `disable = yes` でそのサービスを無効化する。`/etc/xinetd.conf` の `defaults` ブロックには全サービス共通の設定(ログ形式・接続数上限など)を書く。 ::: tip 現在のディストリビューションは systemd ソケットアクティベーション(`.socket` ユニット)が xinetd の役割を引き継いでいる。試験では inetd / xinetd の概念と設定ファイル位置を問われる。 ::: ## TCP Wrappers のアクセス制御を理解するには {#tcp-wrappers} TCP Wrappers は libwrap でリンクされたサービスへの接続を、ホスト単位で許可・拒否する仕組み。`/etc/hosts.allow` と `/etc/hosts.deny` の 2 ファイルで制御する。 ### 評価順序(最重要) 公式マニュアル hosts_access(5) によると、判定は次の順で行われ、**最初にマッチしたルールで確定**する(first match wins)。 1. `/etc/hosts.allow` を上から検索。マッチすれば**許可**して終了。 2. マッチしなければ `/etc/hosts.deny` を検索。マッチすれば**拒否**して終了。 3. どちらにもマッチしなければ**許可**(デフォルト許可)。 つまり allow が deny より常に優先される。閉じた運用にしたいなら「deny で全拒否してから allow で穴を開ける」のが定石。 ```bash cat /etc/hosts.deny ``` ```output ALL: ALL ``` ```bash cat /etc/hosts.allow ``` ```output sshd: 192.168.1.0/24 sshd: 10.0.0.5 ALL: LOCAL ``` この例では、まず `hosts.deny` で全サービス・全ホストを拒否(`ALL: ALL`)。その上で `hosts.allow` で `sshd` への `192.168.1.0/24` と `10.0.0.5` だけ、および全サービスへのローカル接続を許可している。allow が先に評価されるため、許可した接続は通り、それ以外は deny で落ちる。 ### 書式とワイルドカード ルールは `デーモンリスト : クライアントリスト` の形式(`daemon_list : client_list`)。 | 要素 | 意味 | | ------------------ | ----------------------------------------------------------- | | デーモンリスト | サービスのプロセス名(`sshd`, `vsftpd` など)。`ALL` で全部 | | クライアントリスト | 接続元のホスト・IP・ネットワーク。`ALL` で全部 | | `ALL` | 常にマッチするワイルドカード | | `EXCEPT` | 「A EXCEPT B」= A にマッチするが B は除外 | | `LOCAL` | ドットを含まないホスト名(ローカルホスト) | `EXCEPT` の例は次の通り。 ```bash cat /etc/hosts.allow ``` ```output sshd: ALL EXCEPT 192.168.1.100 ``` これは「`192.168.1.100` を除く全ホストから sshd への接続を許可」を意味する。クライアントは `.example.com`(ドメイン後方一致)、`192.168.1.`(ネットワーク前方一致)といった指定も可能。 ::: warning TCP Wrappers が効くのは libwrap でビルドされたサービス(`sshd` など、ただしビルド構成依存)と xinetd 配下のサービスに限られる。すべての通信を止めるものではないため、ネットワーク全体の遮断には iptables / nftables などのファイアウォールを併用する。 ::: ## ログインと認証まわりの防御 {#login-defense} サービスを絞ったら、ログインと認証も締める。`/etc/nologin` とシャドウパスワードが代表的。 ### /etc/nologin で一般ログインを止める `/etc/nologin` ファイルが存在すると、root を除く一般ユーザーのログインが拒否される。ファイルの中身はログイン拒否時に表示されるメッセージになる。 ```bash echo "メンテナンス中です。後ほどお試しください。" > /etc/nologin ``` メンテナンス時に一時的にログインを封じる用途で使う。作業が終わったらファイルを削除すればログインが復活する。 ### シャドウパスワードの概要 シャドウパスワードは、暗号化済みパスワードを誰でも読める `/etc/passwd` から、root のみ読める `/etc/shadow` に分離する仕組み。現在のシステムでは標準で有効。 | コマンド | 動作 | | ----------- | ---------------------------------------------------- | | `pwconv` | `/etc/passwd` からシャドウ化し `/etc/shadow` を生成 | | `pwunconv` | `/etc/shadow` を `/etc/passwd` に戻し shadow を削除 | | `grpconv` | `/etc/group` から `/etc/gshadow` を生成 | | `grpunconv` | `/etc/gshadow` を `/etc/group` に戻し gshadow を削除 | `pwconv` はパスワードを `/etc/passwd` から取り出し、該当フィールドを `x` に置き換える。`pwunconv` はその逆で、平文構成(実際は暗号化済みパスワードを passwd に戻す)にする。 ::: tip レガシーな `/etc/inittab` は SysVinit 時代にデフォルトの実行レベル(runlevel)や各レベルで起動するプロセスを定義したファイル。systemd 環境では使われず、`systemctl get-default` / `set-default` でターゲット(旧 runlevel 相当)を管理する。試験では「inittab はレガシー」と位置づけを問われる。 ::: ## よくあるミスと対処 {#common-mistakes} ### ミス 1: stop だけで安心してしまう `systemctl stop` は今のプロセスを止めるだけ。再起動すると `enabled` のサービスは再び立ち上がる。永続停止には `disable`(または `disable --now`)が必要。 ### ミス 2: mask と disable を混同する `disable` は手動 `start` を許すが、`mask` は手動起動すら拒否する。「一時的に止めたいだけ」なのに `mask` すると、後で起動しようとして `Unit is masked` で詰まる。 ### ミス 3: hosts.allow と hosts.deny の評価順を逆に覚える allow が先、deny が後。`hosts.deny` に `ALL: ALL` を書いても、`hosts.allow` で許可した接続は通る。「deny で全部閉じたのに入れてしまう」と慌てない。順序が仕様通りの正常動作。 ### ミス 4: ルールに何も書かず「閉じた」と思い込む 両ファイルが空、またはマッチするルールがなければデフォルトは**許可**。明示的に拒否するルールを書かない限り、TCP Wrappers は何も止めない。 ### ミス 5: TCP Wrappers が全サービスに効くと誤解する 効くのは libwrap 対応サービスと xinetd 配下のみ。Web サーバなど libwrap を使わないサービスは hosts.deny を無視する。全体の遮断はファイアウォールで行う。 ## トラブルシューティング {#troubleshooting} ### 症状: disable したサービスが再起動後も動いている **原因**: `disable` は自動起動の無効化のみで、稼働中プロセスは止めない。また socket ユニットや他サービスの依存で起動している可能性がある。 **確認**: ```bash systemctl is-enabled svc systemctl status svc ``` **対処**: `systemctl disable --now svc` で即時停止+無効化。socket 経由なら対応する `svc.socket` も停止・無効化する。 ### 症状: hosts.allow に書いたのに接続が拒否される **原因**: 対象サービスが libwrap 非対応、またはデーモン名の綴り違い。 **確認**: ```bash ldd $(which sshd) | grep libwrap ``` **対処**: `libwrap` がリンクされていなければ TCP Wrappers は無効。ファイアウォール側で制御する。デーモン名はプロセス名(`sshd` 等)に正確に合わせる。 ### 症状: mask したサービスに依存する別サービスが起動失敗する **原因**: `mask` は依存解決を含めて起動を全拒否するため、依存元も連鎖的に失敗する。 **確認**: ```bash systemctl list-dependencies --reverse svc ``` **対処**: 本当に不要かを再判断し、依存があるなら `unmask` して `disable` に切り替える。 ## 作業完了チェックリスト {#checklist} - [ ] `ss -tulnp` でリッスン中サービスを洗い出した - [ ] 不要サービスを `disable --now` で停止・無効化した - [ ] 起動を完全に封じる対象は `mask` した - [ ] `/etc/hosts.allow` → `/etc/hosts.deny` の順序を理解した - [ ] 必要に応じてファイアウォールを併用した ## まとめ {#summary} | 目的 | コマンド / ファイル | ポイント | | ------------ | ---------------------------- | ------------------------------ | | リッスン確認 | `ss -tulnp` | 0.0.0.0 は外部公開 | | 即時停止 | `systemctl stop` | 再起動で復活 | | 永続無効化 | `systemctl disable --now` | 実務の標準 | | 起動ロック | `systemctl mask` | unmask まで起動不能 | | アクセス制御 | `/etc/hosts.allow` / `.deny` | allow→deny、最初のマッチで確定 | | ログイン停止 | `/etc/nologin` | root 以外を拒否 | ホストセキュリティは「攻撃面を減らす」と「接続元を絞る」の二本柱。LPIC-1 110 系のセキュリティ範囲を一通り押さえたら、暗号化や権限管理と合わせて運用知識が完成する。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [セキュリティ管理 - SUID/SGID とファイル権限](/articles/lpic/security-administration) - [データ暗号化 - SSH と GPG](/articles/lpic/data-encryption) - [systemd と起動プロセス](/articles/lpic/boot-and-systemd) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # インターネットプロトコルの基礎 - TCP/IP・ポート・IPv4/IPv6【LPIC-1 109.1】 Source: https://penguin-gym-linux.com/articles/lpic/internet-protocols ## この記事で達成できること {#intro} - TCP/IP 4階層モデルの各層と役割を説明できる - IPv4 アドレスをネットワーク部とホスト部に分け、サブネットマスク/CIDR で表せる - プライベートアドレス範囲とブロードキャストアドレスを判別できる - IPv6 のコロン16進表記と省略規則を正しく適用できる - ウェルノウンポートと `/etc/services` の対応を確認できる - TCP(コネクション型)と UDP(コネクションレス型)を使い分けられる LPIC-1 主題 109.1「インターネットプロトコルの基礎」の中核。アドレス計算とプロトコルの役割分担を押さえれば、ネットワーク設定やトラブル対応の土台が固まる。 ## TCP/IP はどんな階層で動くのか {#tcpip-model} TCP/IP は 4 つの階層に役割を分担する。下位層が上位層を運ぶ「カプセル化」の構造で、各層は隣接層とだけやり取りする。 | 層 | 名称 | 役割 | 代表プロトコル | | --- | ------------------------------------------ | -------------------------------------- | ------------------------------- | | 4 | アプリケーション層 | アプリ間のデータ表現・やり取り | HTTP, SSH, DNS, SMTP, FTP, DHCP | | 3 | トランスポート層 | エンドツーエンドの通信制御 | TCP, UDP | | 2 | インターネット層 | ホスト間のルーティング・アドレッシング | IP, ICMP | | 1 | リンク層(ネットワークインターフェース層) | 同一物理ネットワーク内の伝送 | Ethernet, ARP | OSI 参照モデルの 7 階層と対比されることが多いが、LPIC-1 では TCP/IP の 4 階層モデルで役割を理解しておけば十分。トランスポート層の「ポート番号」とインターネット層の「IP アドレス」を組み合わせて通信相手を特定する点が要となる。 ::: tip RFC 1122(Requirements for Internet Hosts)はこのモデルをリンク層/インターネット層/トランスポート層/アプリケーション層として定義している。層の境界はポート番号(トランスポート層)と IP アドレス(インターネット層)で分かれると覚えるとよい。 ::: ## IPv4 アドレスはどう読むのか {#ipv4} IPv4 アドレスは 32 ビットを 8 ビットずつ 4 つに区切り、各 8 ビットを 10 進数で表記する(ドット10進表記)。各オクテットは 0〜255 の範囲を取る。 アドレスは「ネットワーク部」と「ホスト部」に分かれ、その境界をサブネットマスクが示す。 ```bash ip addr show eth0 ``` ```output 2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff inet 192.168.1.10/24 brd 192.168.1.255 scope global eth0 inet6 fe80::5054:ff:fe12:3456/64 scope link ``` `inet 192.168.1.10/24` の `/24` がサブネットマスク長(CIDR 表記)。`/24` は先頭 24 ビットがネットワーク部、残り 8 ビットがホスト部であることを意味する。`brd 192.168.1.255` がブロードキャストアドレス。 ### サブネットマスクと CIDR の対応 サブネットマスクは「ネットワーク部のビットを 1、ホスト部のビットを 0」にした 32 ビット値。CIDR 表記(`/n`)は先頭から連続する 1 の個数を示す。 | CIDR | サブネットマスク | ホストビット | 利用可能ホスト数 | | ----- | ---------------- | ------------ | ---------------- | | `/8` | 255.0.0.0 | 24 | 16,777,214 | | `/16` | 255.255.0.0 | 16 | 65,534 | | `/24` | 255.255.255.0 | 8 | 254 | | `/30` | 255.255.255.252 | 2 | 2 | 利用可能ホスト数は「2 のホストビット乗 − 2」。差し引く 2 つは、ホスト部が全 0 の「ネットワークアドレス」と全 1 の「ブロードキャストアドレス」で、いずれも個別ホストには割り当てられない。 ### プライベートアドレス範囲 インターネット上で重複しないグローバルアドレスとは別に、組織内部で自由に使える範囲が RFC 1918 で予約されている。 | 範囲 | CIDR | クラス相当 | | ---------------------------------- | ---------------- | ---------- | | `10.0.0.0` 〜 `10.255.255.255` | `10.0.0.0/8` | クラス A | | `172.16.0.0` 〜 `172.31.255.255` | `172.16.0.0/12` | クラス B | | `192.168.0.0` 〜 `192.168.255.255` | `192.168.0.0/16` | クラス C | `172.16.0.0/12` は `172.16` 〜 `172.31` までで、`172.32` は含まない点に注意。試験ではこの範囲の境界がよく問われる。 ::: warning `127.0.0.0/8`(特に `127.0.0.1`)は IPv4 のループバックアドレスで、自ホストを指す。プライベートアドレスとは別枠の予約範囲。 ::: ## IPv6 の表記と省略規則 {#ipv6} IPv6 アドレスは 128 ビット。16 ビットずつ 8 つのブロックに区切り、各ブロックをコロンで区切った 16 進数で表記する(コロン16進表記)。 ```output 2001:0db8:0000:0000:0000:ff00:0042:8329 ``` 長くなるため、2 つの省略規則がある。 1. 各ブロック先頭の 0 は省略できる(`0db8` → `db8`、`0000` → `0`) 2. 連続する 0 のブロックは `::` で 1 回だけ省略できる 上記アドレスは次のように短縮される。 ```output 2001:db8::ff00:42:8329 ``` ::: danger `::` は 1 つのアドレス内で 1 回しか使えない。`2001:db8::1::1` のように 2 回使うと、省略されたブロック数が一意に決まらず無効なアドレスになる。これは試験頻出の誤りパターン。 ::: ### 主要な IPv6 アドレス | アドレス/範囲 | 意味 | | -------------- | ------------------------------------------------ | | `::1` | ループバックアドレス(IPv4 の `127.0.0.1` 相当) | | `::` | 未指定アドレス(全ビット 0) | | `fe80::/10` | リンクローカルアドレス(同一リンク内でのみ有効) | | `ff00::/8` | マルチキャストアドレス | `fe80::` で始まるリンクローカルアドレスは、ルーターを越えず同一セグメント内でのみ使える。`ip addr` の出力で `scope link` と表示されるのがこれにあたる。 ## ポート番号と /etc/services の関係 {#ports} IP アドレスがホストを特定するのに対し、ポート番号はホスト上のどのアプリケーションかを特定する 16 ビット(0〜65535)の番号。用途で 3 区分される。 | 区分 | 範囲 | 用途 | | ------------------------ | ------------ | ------------------------------- | | ウェルノウンポート | 0〜1023 | 主要サービス(root 権限が必要) | | 登録済みポート | 1024〜49151 | IANA に登録されたアプリ | | 動的/プライベートポート | 49152〜65535 | クライアント側の一時的な接続元 | サービス名とポート番号の対応は `/etc/services` に定義されている。 ```bash grep -E '^(ssh|http|https|domain|smtp|ftp) ' /etc/services ``` ```output ftp 21/tcp ssh 22/tcp smtp 25/tcp domain 53/tcp domain 53/udp http 80/tcp https 443/tcp ``` 各行は「サービス名 ポート番号/プロトコル」の形式。`domain`(DNS)のように TCP と UDP の両方を持つサービスもある。 ### 主要プロトコルとポート番号 | プロトコル | ポート | トランスポート | 役割 | | ---------- | -------------------------------- | -------------- | ---------------------------- | | FTP | 21(制御)/ 20(データ) | TCP | ファイル転送 | | SSH | 22 | TCP | 暗号化されたリモートログイン | | SMTP | 25 | TCP | メール送信 | | DNS | 53 | UDP / TCP | 名前解決 | | DHCP | 67(サーバ)/ 68(クライアント) | UDP | IP アドレス自動割り当て | | HTTP | 80 | TCP | Web 通信 | | HTTPS | 443 | TCP | 暗号化された Web 通信 | DNS は通常 UDP 53 を使い、応答サイズが大きい場合(ゾーン転送など)に TCP 53 を使う。この「DNS は UDP と TCP の両方」という点は頻出。 ## TCP と UDP はどう違うのか {#tcp-udp} トランスポート層の TCP と UDP は、信頼性と速度のトレードオフで使い分ける。 | 観点 | TCP | UDP | | -------------- | -------------------- | ------------------------- | | 接続 | コネクション型 | コネクションレス型 | | 信頼性 | 再送・順序保証あり | 保証なし | | オーバーヘッド | 大きい | 小さい | | 用途 | HTTP, SSH, SMTP, FTP | DNS, DHCP, ストリーミング | TCP は通信開始時に「3 ウェイハンドシェイク」で接続を確立する。 1. クライアント → サーバ: `SYN`(接続要求) 2. サーバ → クライアント: `SYN/ACK`(要求受理+応答) 3. クライアント → サーバ: `ACK`(確認) この 3 段階を経てからデータ転送が始まるため、パケットの到達と順序が保証される。一方 UDP はハンドシェイクを行わず、いきなりデータを送る。確認応答がない分、低遅延だがパケットロスは検知しない。 現在の待ち受けポートは `ss` で確認できる。 ```bash ss -tlnp ``` ```output State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 128 0.0.0.0:80 0.0.0.0:* ``` `-t` が TCP、`-l` が待ち受け(LISTEN)状態、`-n` が数値表示、`-p` がプロセス表示。UDP を見るには `-t` を `-u` に変える。 ::: tip ICMP(Internet Control Message Protocol)はインターネット層のプロトコルで、TCP/UDP のようなポート番号を持たない。`ping` の到達確認や `traceroute` の経路調査で使われるエラー・制御メッセージ用のプロトコル。 ::: ## よくあるミスと対処 {#common-mistakes} ### ミス1: サブネットの利用可能ホスト数を「2 のホストビット乗」と数える ネットワークアドレス(全 0)とブロードキャストアドレス(全 1)の 2 つを引き忘れるパターン。`/24` なら 256 ではなく **254** が正解。「2 のホストビット乗 − 2」を必ず適用する。 ### ミス2: TCP と UDP の役割を取り違える 「信頼性が必要 = TCP、速度・軽量が必要 = UDP」が原則。DNS の通常クエリや DHCP が UDP である点を、HTTP/SSH(TCP)と混同しやすい。 ### ミス3: IPv6 で `::` を 2 回使う `::` は 1 アドレスにつき 1 回まで。複数の 0 ブロックがあっても、省略は 1 か所に限られる。残りは `0` を明記する。 ### ミス4: プライベートアドレス範囲の境界を誤る 特に `172.16.0.0/12` は `172.16` 〜 `172.31` の範囲。`172.32.x.x` をプライベートと誤認しないこと。 ### ミス5: ループバックを忘れる IPv4 は `127.0.0.1`、IPv6 は `::1`。両方が「自ホスト」を指す予約アドレスである点を押さえる。 ## トラブルシューティング {#troubleshooting} ### 症状: ip addr で IPv4 アドレスが表示されない **原因**: インターフェースが down している、または DHCP からアドレスを取得できていない **確認**: ```bash ip addr show ``` **対処**: インターフェース状態が `DOWN` なら `ip link set eth0 up` で起動。DHCP 利用環境ならクライアントの再取得を行う。 ### 症状: サービス名からポート番号が引けない **原因**: `/etc/services` に該当サービス名のエントリが無い、または綴りが違う **確認**: ```bash grep -i ssh /etc/services ``` **対処**: 正しいサービス名で検索する。独自サービスは `/etc/services` に追記すれば名前解決できるが、ポート番号での指定でも通信は可能。 ### 症状: ループバック通信はできるが外部に到達しない **原因**: ループバック(`127.0.0.1` / `::1`)は自ホスト内で完結するため、外部疎通とは無関係 **確認**: ```bash ip addr show lo ping -c1 192.168.1.1 ``` **対処**: 外部疎通はループバックではなく、`eth0` 等の物理インターフェースのアドレスとデフォルトゲートウェイで確認する。 ## 作業完了チェックリスト {#checklist} - [ ] TCP/IP 4階層の各層と代表プロトコルを言える - [ ] `/24` 等の CIDR からホスト部ビットと利用可能ホスト数を計算した - [ ] プライベートアドレス 3 範囲の境界を確認した - [ ] IPv6 の `::` 省略を 1 回だけ適用した - [ ] `/etc/services` で主要サービスのポートを確認した - [ ] `ss` で TCP / UDP の待ち受けポートを確認した ## まとめ {#summary} | 項目 | 要点 | | ------------ | -------------------------------------------------------- | | 階層モデル | アプリ / トランスポート / インターネット / リンクの 4 層 | | IPv4 | 32 ビット・ドット10進、CIDR でネットワーク部を表す | | ホスト数 | 2 のホストビット乗 − 2 | | プライベート | `10/8`・`172.16/12`・`192.168/16` | | IPv6 | 128 ビット・コロン16進、`::` は 1 回のみ | | ポート | ウェルノウン 0-1023、`/etc/services` で確認 | | TCP / UDP | コネクション型(信頼性)vs コネクションレス型(軽量) | インターネットプロトコルの基礎は、ネットワーク設定・DNS・トラブルシューティングすべての前提となる。アドレス計算とプロトコルの役割分担を固めたら、実際の設定とトラブル対応へ進もう。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [ネットワーク設定の確認と変更](/articles/lpic/network-configuration) - [DNS クライアントの設定](/articles/lpic/client-dns) - [ネットワークトラブルシューティング](/articles/lpic/network-troubleshooting) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # ジョブスケジューリング - cron/at/systemd timer【LPIC-1 107.2】 Source: https://penguin-gym-linux.com/articles/lpic/job-scheduling ## この記事で達成できること {#intro} - `crontab` の書式(5 フィールド)を読み書きできる - ユーザー crontab とシステム crontab(`/etc/crontab`・`/etc/cron.d`)の違いを説明できる - `at` / `atq` / `atrm` で一度だけのジョブを管理できる - `anacron` と `systemd timer` の使いどころを判断できる - `cron.allow` / `cron.deny` による実行制御を理解できる - 「cron の PATH 問題」など試験頻出の落とし穴を回避できる LPIC-1 主題 107.2「ジョブスケジューリングによってシステム管理業務を自動化する」の中核。定期バックアップやログローテーションなど、定型作業を人手を介さず自動実行する技術。 ## どのスケジューラを使うべきか {#flow} 繰り返し実行なら `cron`、一度きりなら `at`、電源が常時入っていないマシンの定期実行なら `anacron`、systemd 環境での高機能な制御なら `systemd timer` を選ぶ。これが判断の起点になる。 | 要件 | ツール | 代表コマンド / ファイル | | ------------------------------------ | --------------- | ------------------------------------ | | 毎日・毎時など定期繰り返し | `cron` | `crontab -e`, `/etc/crontab` | | 指定時刻に一度だけ | `at` | `at`, `atq`, `atrm` | | 稼働していない時間帯の取りこぼし回避 | `anacron` | `/etc/anacrontab` | | systemd 統合・依存関係・ログ連携 | `systemd timer` | `.timer` + `.service`, `OnCalendar=` | `cron` は指定時刻にマシンが起動していなければそのジョブをスキップする。一方 `anacron` は「前回からの経過日数」で判定するため、ノート PC のように電源が落ちている時間がある環境でも実行を取りこぼさない。 ## crontab の書式 {#crontab-format} crontab の各行は「分 時 日 月 曜日 コマンド」の 6 要素で構成される。先頭 5 つが時刻フィールド、6 番目以降が実行するコマンド。 ```output # ┌───────────── 分 (0 - 59) # │ ┌───────────── 時 (0 - 23) # │ │ ┌───────────── 日 (1 - 31) # │ │ │ ┌───────────── 月 (1 - 12) # │ │ │ │ ┌───────────── 曜日 (0 - 7、0と7は日曜) # │ │ │ │ │ # * * * * * 実行するコマンド ``` 各フィールドで使える特殊記号は次のとおり。 | 記号 | 意味 | 例 | | ---- | ---------------- | ------------------------ | | `*` | すべての値 | 分が `*` なら毎分 | | `,` | 値の列挙 | `0,30` は 0 分と 30 分 | | `-` | 範囲 | `1-5`(曜日)は月〜金 | | `/` | 間隔(ステップ) | `*/15`(分)は 15 分ごと | 具体例を読んでみる。 ```output */15 * * * * /usr/local/bin/check.sh 毎15分ごと 0 3 * * * /usr/local/bin/backup.sh 毎日3:00 0 9 * * 1-5 /usr/local/bin/report.sh 平日(月-金)の9:00 0 0 1 * * /usr/local/bin/monthly.sh 毎月1日の0:00 ``` Vixie cron 系では `@reboot`・`@daily`・`@hourly`・`@weekly`・`@monthly`・`@yearly`(`@annually`)・`@midnight` といったニックネームも使える。`@reboot` は cron デーモン起動時に一度だけ実行される。 ::: tip 曜日フィールドの `0` と `7` はどちらも日曜日を指す。試験では「曜日の数字」がよく問われる。`0`=日曜、`1`=月曜… `6`=土曜、そして `7` も日曜、と覚える。 ::: ## 手順 {#steps} ### Step 1: ユーザー crontab を編集する ```bash crontab -e ``` ```output crontab: installing new crontab ``` `crontab -e` はエディタ(`$EDITOR` / `$VISUAL` で指定)を開き、現在のユーザーの crontab を編集する。保存すると `/var/spool/cron/`(ディストリビューションにより `crontabs/`)配下に格納され、cron デーモンが自動的に読み込む。次の行を追記すると、毎日 3:00 にバックアップが走る。 ```output 0 3 * * * /usr/local/bin/backup.sh ``` ### Step 2: 登録内容を一覧・削除する ```bash crontab -l crontab -r ``` ```output 0 3 * * * /usr/local/bin/backup.sh ``` `crontab -l` は登録済みエントリを表示し、`crontab -r` は crontab を**丸ごと削除**する。`-r` は確認なしで消えるため、`-l` で控えを取ってから実行するのが安全。root は `crontab -u user -e` / `-u user -l` で他ユーザーの crontab を操作できる。 ::: warning `crontab -r` と `crontab -e` はキーが隣接しており、誤って `-r` を打つと全エントリが消える。`crontab -l > ~/crontab.bak` でバックアップを取っておくとよい。 ::: ### Step 3: システム crontab を使う ```bash cat /etc/crontab ls /etc/cron.d/ ``` ```output SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # m h dom mon dow user command 17 * * * * root cd / && run-parts --report /etc/cron.hourly ``` `/etc/crontab` と `/etc/cron.d/` 配下のファイルは**システム crontab** で、ユーザー crontab と異なり時刻フィールドとコマンドの**間に「実行ユーザー」フィールドが入る**(6 フィールド構成)。これが試験頻出の差分。`/etc/cron.{hourly,daily,weekly,monthly}` にスクリプトを置くと、`run-parts` 経由でそれぞれの周期に実行される。 ### Step 4: at で一度だけ実行する ```bash at 22:00 tomorrow atq atrm 2 ``` ```output warning: commands will be executed using /bin/sh at> /usr/local/bin/deploy.sh at> job 2 at Sat May 31 22:00:00 2026 2 Sat May 31 22:00:00 2026 a user ``` `at` は時刻を指定して**一度だけ**ジョブを実行する。プロンプトでコマンドを入力し `Ctrl+D`(``)で確定する。`atq`(= `at -l`)で待ち行列を確認し、`atrm 2`(= `at -d 2`)でジョブ番号 2 を削除する。時刻は `10:00`・`now + 1 hour`・`midnight`・`teatime`(16:00)など柔軟に指定できる。`batch` はシステム負荷が下がったタイミングでジョブを実行する点が `at` と異なる。 ### Step 5: 実行可否を制御する ```bash cat /etc/cron.allow cat /etc/cron.deny ``` ```output alice bob ``` cron の利用は `/etc/cron.allow` と `/etc/cron.deny` で制御する。判定ルールは次のとおり。 | ファイルの状態 | 結果 | | ------------------------------------ | ---------------------------------- | | `cron.allow` が存在する | そこに記載されたユーザーのみ許可 | | `cron.allow` が無く `cron.deny` あり | `cron.deny` 記載ユーザー以外を許可 | | 両方とも存在しない | (実装依存)多くはroot のみ、等 | `at` も同様に `/etc/at.allow` / `/etc/at.deny` で制御する。`*.allow` が優先され、存在する場合は `*.deny` は参照されない。 ## anacron と systemd timer の使い分け {#anacron-systemd} cron は「電源が入っている前提」で動くが、anacron と systemd timer は電源が落ちていた間の取りこぼしを補える。常時稼働サーバーでないなら、この 2 つを検討する価値がある。 ### anacron `/etc/anacrontab` は cron と書式が異なり「日数間隔・遅延(分)・ジョブ識別子・コマンド」の 4 フィールドで記述する。 ```bash cat /etc/anacrontab ``` ```output # period delay job-identifier command 1 5 cron.daily run-parts --report /etc/cron.daily 7 25 cron.weekly run-parts --report /etc/cron.weekly @monthly 45 cron.monthly run-parts --report /etc/cron.monthly ``` 第 1 フィールドが「何日ごとに実行するか」、第 2 フィールドが「起動後に待つ遅延(分)」。anacron は分・時刻を指定できず、日単位の粒度しか持たない。前回実行日を `/var/spool/anacron/` に記録し、間隔を超えていれば起動時に実行する。 ### systemd timer systemd timer は `.timer` ユニットと、実際の処理を行う `.service` ユニットの 2 つで構成する。 ```bash systemctl list-timers ``` ```output NEXT LEFT LAST PASSED UNIT ACTIVATES Sat 2026-05-31 03:00:00 JST 8h left Fri 2026-05-30 03:00:00 JST 15h ago backup.timer backup.service ``` `.timer` 内の `OnCalendar=` で実行時刻を指定する。`OnCalendar=*-*-* 03:00:00` は毎日 3:00、`OnCalendar=daily` も同義。`systemctl list-timers` で次回実行・前回実行・対応サービスを一覧できる。systemd timer は依存関係制御・`journalctl` でのログ確認・`Persistent=true` による取りこぼし実行(anacron 相当)まで扱える点が強み。 ::: tip `OnCalendar=` の書式が正しいかは `systemd-analyze calendar "Mon *-*-* 09:00:00"` で検証できる。次回トリガー時刻が表示される。 ::: ## よくあるミスと対処 {#troubleshooting} ### 症状: cron では動くはずのスクリプトが実行されない(PATH 問題) **原因**: cron が起動するシェルの `PATH` は対話ログインシェルより短く、`/etc/profile` や `.bashrc` も読み込まれない。コマンドが見つからず失敗する **確認**: ```bash * * * * * env > /tmp/cron-env.txt ``` **対処**: コマンドは**絶対パス**で書く(`backup` ではなく `/usr/local/bin/backup`)。または crontab 冒頭で `PATH=...` を明示的に設定する。 ### 症状: スクリプト内の環境変数が未設定で誤動作する **原因**: cron は対話シェルの環境変数(`LANG`・`HOME` 周辺の独自設定など)を引き継がない。ログインシェル前提のスクリプトが想定外の挙動になる **確認**: ```bash crontab -l ``` **対処**: スクリプト側で必要な変数を明示設定するか、crontab に `LANG=ja_JP.UTF-8` などを書く。ログイン環境を再現したいなら `bash -lc 'コマンド'` で起動する。 ### 症状: crontab に書いた % 以降が無視される / コマンドが途中で切れる **原因**: crontab ではコマンド行の `%` が改行(標準入力の区切り)として特別扱いされる **確認**: ```bash crontab -l ``` **対処**: `%` をそのまま渡したい場合は `\%` とバックスラッシュでエスケープする。`date +\%Y\%m\%d` のように記述する。 ### 症状: 曜日と日付を両方指定したのに想定外の日に動く **原因**: cron では「日」と「曜日」を**両方とも `*` 以外**に指定すると、どちらか一方が一致した日に実行される(AND ではなく OR) **確認**: ```bash crontab -l ``` **対処**: 仕様として OR 動作になることを理解する。「特定曜日かつ特定日」のような AND 条件は、コマンド側で日付を判定して制御する。 ### 症状: cron の出力(エラー)が見えない **原因**: cron はジョブの標準出力・標準エラーをローカルメールでユーザーに送る。メールを確認していないと気づけない **確認**: ```bash grep CRON /var/log/syslog ``` **対処**: 出力をファイルへリダイレクトする(`>> /var/log/myjob.log 2>&1`)。`MAILTO=address` を crontab に書けば送信先を変更でき、`MAILTO=""` で抑止できる。 ## 作業完了チェックリスト {#checklist} - [ ] `crontab -e` でユーザー crontab を編集し `crontab -l` で確認した - [ ] システム crontab(`/etc/crontab`)に user フィールドがある点を確認した - [ ] `at` でジョブを登録し `atq` / `atrm` で管理した - [ ] `/etc/cron.allow` / `cron.deny` の優先順位を理解した - [ ] スクリプトを絶対パスで記述し PATH 問題を回避した - [ ] `systemctl list-timers` で systemd タイマーを確認した ## まとめ {#summary} | 場面 | コマンド / ファイル | 目的 | | -------------------- | ----------------------------- | --------------------------- | | 定期実行(ユーザー) | `crontab -e` | 自分の定期ジョブを登録 | | 定期実行(システム) | `/etc/crontab`, `/etc/cron.d` | user 指定付きの定期ジョブ | | 一度だけ | `at`, `atq`, `atrm` | 指定時刻に単発実行 | | 取りこぼし防止 | `/etc/anacrontab` | 非常時稼働マシンの日次処理 | | systemd 統合 | `.timer` + `OnCalendar=` | 依存・ログ・Persistent 実行 | | 実行制御 | `cron.allow` / `cron.deny` | ユーザー単位の許可・拒否 | ジョブスケジューリングはシステム運用の自動化基盤。ログ管理やプロセス優先度と組み合わせると、無人運用の信頼性が一段上がる。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [システムログ管理 - journald と rsyslog](/articles/lpic/system-logging) - [プロセス優先度の制御 - niceとreniceの仕組み](/articles/lpic/process-priorities-nice) - [シェル環境変数の設定](/articles/lpic/shell-environment) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # LPIC-1 取得後のキャリアガイド - 年収・求人・履歴書・未経験から転職まで Source: https://penguin-gym-linux.com/articles/lpic/lpic1-career-guide ## この記事で達成できること {#intro} - LPIC-1 取得後の主な職種と市場評価を把握できる - 公的データ・公式一次ソースに基づく求人傾向と年収レンジを確認できる - 履歴書・職務経歴書への正しい記載方法(英語表記・受験中の書き方を含む)を理解できる - 未経験からインフラエンジニアを目指す具体的なロードマップを描ける - LPIC-2 / LPIC-3 へのキャリアパスとクラウド資格との組み合わせを判断できる LPIC-1 は試験合格だけが目的ではない。取得後にどのようなキャリアが開けるかを正確に把握することで、学習のモチベーションと転職戦略の精度が上がる。本記事では誇大な表現を一切排除し、公的データと公式一次ソースに基づいて解説する。 ## キャリア機会の俯瞰 {#career-overview} LPIC-1 が評価される職種は主に以下の 4 領域だ。 | 職種カテゴリ | 具体的なポジション例 | LPIC-1 の位置づけ | | ---------------- | ------------------------------------------- | ------------------------------------ | | インフラ運用 | サーバエンジニア / インフラエンジニア | 応募要件または歓迎条件として頻出 | | クラウドインフラ | クラウドエンジニア / AWS / Azure / GCP 担当 | Linux 基礎知識の証明として機能 | | DevOps / SRE | SRE / DevOps エンジニア / CI-CD 担当 | シェル・プロセス管理の基礎として評価 | | セキュリティ | セキュリティエンジニア / SOC アナリスト | Linux システム理解の前提として機能 | LPI 公式が公開する調査データによると、採用担当者の 81% がオープンソース人材の採用を優先し、74% が新入社員に求めるスキルとして Linux を挙げている。(出典: LPI 公式サイト `https://www.lpi.org/ja/our-certifications/lpic-1-overview/`, 取得日: 2026-05-22) LPIC-1 は「Linux を扱える」ことを第三者が証明できる国際資格であり、ベンダーニュートラルな点が採用担当者から評価される。350,000 人以上の認定保有者を擁する LPI が認定する資格は、エントリーレベルの職種では特に有効に機能する。(出典: LPI 公式 FAQ `https://www.lpi.org/ja/about-lpi/frequently-asked-questions`, 取得日: 2026-05-22) ## 求人傾向 {#job-market} ### 主な求人カテゴリ LPIC-1 を要件・歓迎条件に挙げる求人は以下のカテゴリに集中している。 - **サーバ運用・保守**: Linux サーバの監視・障害対応・パッチ適用など運用業務 - **インフラ構築**: オンプレミスまたはクラウドでのサーバ構築・設定 - **クラウドインフラ管理**: AWS / Azure / GCP 上の EC2 / VM インスタンスの管理 - **DevOps / CI-CD**: コンテナ(Docker / Kubernetes)環境の構築・運用 ### LPIC-1 が「歓迎条件」になるケース LPIC-1 が「必須」ではなく「歓迎条件」として扱われる求人では、実務経験のない応募者が差別化要素として活用できる。一方、LPIC-2 以上や AWS 資格との組み合わせが「必須」とされる求人では、LPIC-1 は前提の一部として機能する。 ### 求人数の変動要因 求人数は景気・採用動向・技術トレンドにより変動する。クラウド移行の進展に伴い、オンプレミス専任よりもクラウドハイブリッド対応人材の需要が高まっている傾向が観測されている。(当サイト観察、2026-05 時点) ## 想定年収レンジ {#salary} ### 職種別の年収目安 以下は当サイトの観察と大手求人媒体の公開集計値を組み合わせた目安だ。個人のスキル・経験・企業規模・地域により大きく変動する。 | 経験・スキル段階 | 想定年収レンジ(目安) | 備考 | | -------------------------------- | ---------------------- | --------------------------------- | | 未経験 / 第二新卒・入社初年度 | 280〜380 万円 | LPIC-1 が歓迎条件の求人に応募可能 | | 実務 1〜2 年 + LPIC-1 | 370〜480 万円 | 運用業務をこなせる段階 | | 実務 3〜5 年 + LPIC-1 | 450〜600 万円 | 構築・設計まで担当できる段階 | | 実務 5 年超 + LPIC-2 以上 | 550〜750 万円 | マネジメントや上流設計を含む | | クラウド資格(AWS SAA など)追加 | +30〜80 万円程度の差 | 市場によって変動 | (参考: doda 公開集計値 `https://doda.jp/guide/heikin/it/`, 取得日: 2026-05-22。具体的な職種別データは doda サイト上で確認すること。当サイト観察による補足含む) ::: warning 年収は企業規模・地域・担当業務・交渉力で大幅に変動する。上記はあくまで参考レンジであり、特定の年収を保証するものではない。 ::: ### LPIC-1 単独では年収への直接的な影響は限定的 重要な注意点として、LPIC-1 を取得しただけで自動的に年収が上がるわけではない。年収への影響は以下の要素と組み合わさって初めて機能する。 - 実務経験(Linux サーバの実際の運用経験) - 上位資格の積み上げ(LPIC-2 / クラウド資格など) - 担当業務の幅(運用のみか設計・構築まで含むか) - 企業規模・業界・交渉力 ## 履歴書・職務経歴書への記載方法 {#resume} ### 職務経歴書への記載 職務経歴書の「保有資格」欄には以下の形式で記載する。 ``` Linux Professional Institute Certification Level 1(LPIC-1) 取得 XXXX 年 XX 月 取得 ``` 正式名称は「Linux Professional Institute Certification Level 1」だ。LPI 公式サイト(`https://www.lpi.org/ja/our-certifications/lpic-1-overview/`)で確認できる名称に合わせることが望ましい。(出典: LPI 公式, 2026-05-22 時点) ### 英語表記 英文 CV(レジュメ)や外資系企業への応募時は以下の形式が標準的だ。 ``` Linux Professional Institute Certification Level 1 (LPIC-1) Linux Professional Institute — Issued [Month YYYY] Credential ID: [LPI ID に紐づく認定番号] ``` ### 受験中・101 試験のみ合格時の書き方 LPIC-1 は 2 科目(101-500 / 102-500)の合格で取得となるため、片方のみ合格の段階では資格欄に「LPIC-1 取得」とは記載できない。以下のように記載する。 ``` 【資格取得予定】 LPIC-1(101-500 合格済 / 102-500 受験準備中、取得見込み: YYYY 年 MM 月) ``` または「自己 PR」や「その他特記事項」欄に記載し、取得への意欲を示す。 ### 101 単独合格時の記載例(実際の表記) | 記載箇所 | 記載例 | | -------------------- | ----------------------------------------------------------------------------- | | 資格欄(正式取得前) | 記載不可(LPIC-1 として成立しない) | | 特記事項 / 取得予定 | LPIC-1(101-500 合格、102-500 準備中)取得見込み YYYY/MM | | LPIC-1 正式取得後 | Linux Professional Institute Certification Level 1(LPIC-1)XXXX 年 XX 月取得 | ::: tip 101 試験と 102 試験はそれぞれ独立して合格する必要がある。LPIC-1 認定の有効期間や各試験スコアの有効期限については LPI 公式(`https://www.lpi.org/ja/our-certifications/lpic-1-overview/`)で最新情報を確認すること。記載する取得見込み日は計画的に設定することが重要だ。 ::: ## 未経験から LPIC-1 取得を目指すロードマップ {#roadmap} 未経験から IT インフラ職への転職を目指す場合、以下のフェーズを踏むことが現実的だ。 ### フェーズ 1: Linux 基礎(2〜4 週間) - シェル操作の基礎: `ls` / `cd` / `mkdir` / `cp` / `mv` / `rm` - ファイルパーミッション: `chmod` / `chown` / `umask` - パッケージ管理: `apt` / `yum` / `rpm` - テキスト処理: `grep` / `awk` / `sed` 当サイトの仮想ターミナルでブラウザ完結でコマンド実習が可能だ([LPIC-1 演習ターミナル](/terminal?category=lpic))。 ### フェーズ 2: LPIC-1 試験対策(1〜2 ヶ月) - 101 試験範囲: システムアーキテクチャ / ファイル管理 / シェル / パッケージ管理 - 102 試験範囲: シェルスクリプト / ネットワーク / セキュリティ基礎 / システム管理 - 勉強時間の目安は [LPIC-1 勉強時間・勉強方法ガイド](/articles/lpic/study-time-and-method) を参照 ### フェーズ 3: 実機環境の構築(並行推奨) - VirtualBox / Vagrant で自宅 Linux 環境を構築する - 試験範囲のコマンドを実際のシェルで動かす - GitHub に作業ログやシェルスクリプトを記録し、ポートフォリオ化する ::: tip 自宅環境が用意できない場合は、WSL2(Windows Subsystem for Linux 2)や無料のクラウドシェル(AWS CloudShell など)でもコマンド実習は可能だ。 ::: ### フェーズ 4: LPIC-1 取得・転職活動 - 2 科目合格で LPIC-1 認定取得 - 職務経歴書に資格を追記し、実機経験を「取り組みとして自宅環境で〜を実施」と具体的に記載する - 実務未経験でも「研修制度あり」の求人やエントリーレベルのポジションを中心に応募する ### 未経験転職時の現実的な期待値 | 項目 | 実態 | | ------------- | --------------------------------------------------------- | | LPIC-1 の評価 | 歓迎条件として評価される。必須要件は企業による | | 初年度年収 | 280〜380 万円が多い(当サイト観察、2026-05 時点) | | 実務習得期間 | 業務に慣れるまでおよそ 3〜6 ヶ月が目安 | | 次のステップ | 実務 1〜2 年後に LPIC-2 または AWS 資格へ進むケースが多い | ## LPIC-2 / LPIC-3 へのキャリアパス {#lpic-path} ### LPIC-2 LPIC-2 は LPIC-1 の上位資格で、有効な LPIC-1 認定が受験の前提条件となる。(出典: LPI 公式 `https://www.lpi.org/ja/our-certifications/lpic-2-overview/`, 取得日: 2026-05-22) | 項目 | 内容 | | -------------- | ------------------------------------------------------------------- | | 試験コード | 201-450(試験 1)/ 202-450(試験 2) | | 前提条件 | 有効な LPIC-1 認定(必須) | | 主な対象スキル | カーネル / ストレージ / ネットワーク / DNS / Webサーバ / メール配信 | | 有効期限 | 5 年(LPIC-1 の有効期限も自動更新) | LPI の認定調査では「認定取得者の 77% が 6 ヶ月以内に昇給した」というデータが示されている。(出典: LPI 公式 LPIC-2 概要ページ `https://www.lpi.org/ja/our-certifications/lpic-2-overview/`, 取得日: 2026-05-22) ::: warning 上記の昇給データは LPI が公開する統計値であり、LPIC-2 取得が昇給を保証するものではない。企業環境・役割・交渉力に依存する。 ::: ### LPIC-3 LPIC-3 は LPIC-2 の上位であり、有効な LPIC-2 認定が前提条件となる。4 つの専門トラックがある。(出典: LPI 公式 `https://www.lpi.org/ja/our-certifications/lpic-3-303-overview/`, 取得日: 2026-05-22) | トラック | 試験コード | 対象スキル | | ----------------------------------- | ---------- | ------------------------------------------------ | | Mixed Environments | 300-300 | Samba / Windows 統合 | | Security | 303-300 | 暗号化 / アクセス制御 / ネットワークセキュリティ | | High Availability | 304-300 | クラスタ / 高可用性 / ストレージ | | Virtualization and Containerization | 305-300 | 仮想化 / コンテナ / KVM | ### キャリアパスの選択基準 ``` LPIC-1 取得 │ ├── インフラ運用継続 → LPIC-2(サーバ管理の深化) │ └── 高可用性 / セキュリティ深化 → LPIC-3 │ ├── クラウド志向 → AWS/Azure/GCP 資格(後述) │ └── SRE / DevOps 志向 → LPIC-2 + クラウド資格の並行取得 ``` ## クラウド系資格(AWS / Azure / GCP)との組み合わせ {#cloud} クラウドインフラ職では LPIC-1 単独よりも、クラウドベンダー資格との組み合わせが評価される傾向がある。 ### 組み合わせパターン | 組み合わせ | 主な対象ポジション | 特徴 | | ------------------------------------- | --------------------------------------------- | ------------------------ | | LPIC-1 + AWS SAA-C03 | AWS インフラエンジニア / クラウドアーキテクト | 最もメジャーな組み合わせ | | LPIC-1 + AWS CLF-C02 | クラウド入門者 / 開発者 | AWS の基礎確認に有効 | | LPIC-1 + Azure AZ-104 | Azure 環境のインフラ担当 | Microsoft 系企業向け | | LPIC-1 + GCP Associate Cloud Engineer | GCP 基盤エンジニア | Google Cloud 志向向け | AWS 認定の概要は AWS 公式(`https://aws.amazon.com/jp/certification/`)を参照。現行の主要資格は以下のとおり。(取得日: 2026-05-22) - **基礎レベル**: AWS Certified Cloud Practitioner(CLF-C02) - **アソシエイトレベル**: Solutions Architect Associate(SAA-C03)/ Developer Associate / SysOps Administrator Associate - **プロフェッショナルレベル**: Solutions Architect Professional / DevOps Engineer Professional ::: tip AWS SAA-C03 の試験は EC2 / Linux 操作の基礎知識を前提としている部分があるため、LPIC-1 の学習経験は AWS 学習の足場として機能する。 ::: ### 組み合わせを取得する順序 1. LPIC-1 取得(Linux 基礎の確立) 2. クラウド基礎資格(AWS CLF-C02 または SAA-C03)で実務クラウド環境の知識を追加 3. 実務経験を積みながら上位資格(LPIC-2 または AWS 上位資格)を検討 ## 落とし穴・注意点 + 転職タイミング判断 {#pitfalls} ### よくある落とし穴 1. **資格取得だけで転職できると過信する**: LPIC-1 は「Linux を学んだ証拠」であり、「実務ができる証拠」ではない。自宅環境での実機経験やポートフォリオを並行して準備することが重要だ。 2. **有効期限を意識せず放置する**: LPIC-1 の認定有効期限は 5 年。更新を怠ると失効する。LPIC-2 取得で自動更新されるため、キャリアアップ計画と合わせて管理することが望ましい。(出典: LPI 公式 `https://www.lpi.org/ja/our-certifications/lpic-1-overview/`, 取得日: 2026-05-22) 3. **101 試験のみで「LPIC-1 取得」と記載する**: 2 科目合格が LPIC-1 の取得条件。101 のみ合格した状態では LPIC-1 の取得にはならない。履歴書への誤記載は信頼を損なう。 4. **LinuC と LPIC-1 を混同する**: LinuC は LPI Japan が独自に運営する別の資格制度。学習テキストや試験範囲が異なる。(詳細は [LPIC-1 とは記事](/articles/lpic/what-is-lpic1) を参照) ### 転職タイミング判断基準 | タイミング | 状況 | 判断基準 | | --------------- | ----------------------------- | -------------------------------------------------------------------------------------------- | | LPIC-1 合格直後 | 資格のみ・実務未経験 | 未経験可・研修制度ありのエントリー求人に絞って応募可。年収より成長環境を優先する時期 | | 実務 1 年後 | 運用経験あり + LPIC-1 | 運用業務を一通り経験済みであれば転職活動の本格開始が現実的。構築も担当できる求人を狙える | | LPIC-2 取得後 | LPIC-1 + LPIC-2 + 実務 3 年超 | 年収交渉の余地が広がる。設計・構築のシニアポジションや SRE / DevOps への転換を検討できる時期 | ::: warning 転職の判断はキャリアゴール・生活状況・市場環境によって異なる。上記はあくまで参考基準であり、個別の状況に応じて判断することが重要だ。 ::: ## キャリア設計チェックリスト {#checklist} LPIC-1 取得後のキャリア設計に使えるチェックリストだ。 **資格・スキル面** - [ ] LPIC-1(101-500 + 102-500)両科目合格済み - [ ] 自宅または業務での Linux 実機操作経験あり - [ ] 履歴書・職務経歴書に正式名称で記載済み - [ ] 次の目標資格(LPIC-2 / クラウド資格)を決定済み - [ ] LPIC-1 の認定有効期限(取得日 + 5 年)を把握済み **転職活動面** - [ ] 目標職種(インフラ / クラウド / SRE など)を決定済み - [ ] 希望年収レンジと現実的なレンジのギャップを把握済み - [ ] 職務経歴書に実務・自習経験を具体的に記載済み - [ ] ポートフォリオ(GitHub / 構築手順書など)を準備済み **長期キャリア面** - [ ] 3〜5 年後の目標ポジション(シニアエンジニア / SRE / アーキテクトなど)を設定済み - [ ] LPIC-2 または上位クラウド資格の取得計画を立てた - [ ] 認定更新を計画に組み込んでいる ## まとめ {#conclusion} - LPIC-1 はインフラエンジニア・クラウドエンジニア・SRE 職への転職で「Linux を扱える」ことを証明する国際資格 - 未経験転職では「歓迎条件」として機能することが多く、実機経験との組み合わせが重要 - 年収は経験・スキル・企業規模によって大きく変動する。未経験入社時 280〜380 万円、実務 3 年超で 450〜600 万円が当サイト観察による目安 - 履歴書の正式名称は「Linux Professional Institute Certification Level 1(LPIC-1)」。101 のみ合格の段階では「取得予定」として記載する - 上位資格(LPIC-2 / クラウド資格)との組み合わせがキャリアの幅を広げる - 認定有効期限(5 年)を意識し、LPIC-2 取得で自動更新する計画を立てることが望ましい LPIC-1 の学習・受験対策は [LPIC-1 学習ハブ](/lpic1) からスタートできる。 # LPIC-1 vs LPIC-2 / Linux+ / Linux Essentials / LinuC 比較 - 101/102 順番と同日受験 Source: https://penguin-gym-linux.com/articles/lpic/lpic1-comparison ## この記事で達成できること {#intro} - LPIC-1 と LPIC-2・CompTIA Linux+・Linux Essentials・LinuC レベル 1 の違いを試験形式・費用・有効期限で比較できる - 自分のキャリア状況に合った資格を 30 秒で判定できる - LPIC-1 の 101 試験と 102 試験の配点・受験順番・同日受験戦略を理解できる - 公式一次ソースに基づく数値で受験計画を立てられる - LinuC レベル 1 と LPIC-1 の関係を正確に把握し、誤選択を避けられる Linux 資格は「種類が多すぎて選べない」のが学習着手前の最大の壁。本記事は受験戦略決定支援に特化し、迷いを 1 本で解消する。 ## どれを受けるか 30 秒で判定 {#flow} 迷ったらまず下表で該当行を 1 つ選ぶ。詳細比較は各セクションで深掘りする。 | あなたの状況 | 推奨資格 | 次の一歩 | | ------------------------------------- | ---------------- | ------------------------- | | Linux 学習が初めて | Linux Essentials | 入門書 + 公式試験範囲確認 | | 実務 1-2 年・体系基礎を確立したい | LPIC-1 | 101 → 102 順で受験計画 | | 英語圏転職・北米市場を狙う | CompTIA Linux+ | XK0-006(V8)範囲確認 | | 国内エンタープライズ・公的セクター | LinuC レベル 1 | LinuC-1 範囲確認 | | LPIC-1 取得済・運用エンジニア上位狙い | LPIC-2 | 201 → 202 順で受験計画 | 判定の根拠は各比較セクションで提示する。出典は各表直下脚注を参照。 ## LPIC-1 vs LPIC-2 {#vs-lpic2} LPIC-1 と LPIC-2 はどちらも LPI(Linux Professional Institute)が運営する国際資格。LPIC-2 は LPIC-1 の上位で、サーバ運用・ネットワーク・セキュリティの応用領域に踏み込む。 | 比較軸 | LPIC-1 | LPIC-2 | | -------------- | ------------------------------ | -------------------------------------- | | 運営団体 | LPI | LPI | | 必要試験 | 101 + 102 | 201 + 202 | | 各試験問題数 | 60 問 | 60 問 | | 各試験時間 | 90 分 | 90 分 | | 出題形式 | 多肢選択 + 穴埋め | 多肢選択 + 穴埋め | | 前提条件 | なし | 有効な LPIC-1 認定が必須 | | 有効期限 | 5 年 | 5 年 | | 主な対象スキル | コマンドライン・ファイル・基礎 | カーネル・ネットワーク・サーバサービス | | 想定実務年数 | 1-2 年 | 3-5 年 | 出典: LPI 公式 [LPIC-1 Overview](https://www.lpi.org/our-certifications/lpic-1-overview/) / [LPIC-2 Overview](https://www.lpi.org/our-certifications/lpic-2-overview/)(最終確認: 2026-05-22) ### 判断の要点 - LPIC-1 は実務 1-2 年層が「Linux の基礎を体系化した」と示すための国際標準。コマンドライン・ファイル・パッケージ管理が中心。 - LPIC-2 は LPIC-1 取得が前提。201 で容量計画・カーネル・ストレージ・ネットワーク設定、202 で DNS・Web・メール・ファイル共有・セキュリティを問う。サーバ運用エンジニアの上位指標。 - 「LPIC-1 を飛ばして LPIC-2」は不可。LPIC-2 認定取得には有効な LPIC-1 が必須要件。 ## LPIC-1 vs CompTIA Linux+ {#vs-linuxplus} CompTIA Linux+ は CompTIA(米国)が運営するベンダーニュートラル資格。V8(XK0-006)が 2025 年 7 月 15 日にローンチした現行版。 | 比較軸 | LPIC-1 | CompTIA Linux+ (XK0-006) | | ------------- | -------------------------------- | ------------------------------------------------------- | | 運営団体 | LPI(国際) | CompTIA(米国) | | 必要試験 | 101 + 102(2 試験) | XK0-006 1 試験 | | 問題数 | 各 60 問 | 最大 90 問 | | 試験時間 | 各 90 分 | 90 分 | | 出題形式 | 多肢選択 + 穴埋め | 多肢選択 + パフォーマンスベース | | 合格点 | 500 / 800(200〜800 点スケール) | 720 / 900 | | 前提条件 | なし | Linux サーバ実務 12 か月 + A+/Network+/Server+ 相当推奨 | | 有効期限 | 5 年 | 概ね 3 年 | | 試験言語 | 多言語(日本語含む) | 英語 | | ISO/ANSI 認定 | なし | あり | 出典: LPI 公式 [LPIC-1 Overview](https://www.lpi.org/our-certifications/lpic-1-overview/) / CompTIA 公式 [Linux+](https://www.comptia.org/certifications/linux)(最終確認: 2026-05-22) ### 判断の要点 - 北米・英語圏転職や米国系企業(特に政府調達系)を狙うなら ISO/ANSI 認定のある CompTIA Linux+ が有利。 - 日本国内・アジア圏で実務基礎を示すなら LPIC-1。日本語受験が可能で、教材・コミュニティも豊富。 - Linux+ はパフォーマンスベース問題(実機操作シミュレーション)を含み、コマンドの「使える」レベルを直接問う。LPIC-1 は知識を問う比率が高い。 - 受験料は両資格とも国別・時期で変動。Pearson VUE / 各公式販売チャネルで最新価格を確認する。 ## LPIC-1 vs Linux Essentials {#vs-essentials} Linux Essentials は LPI が提供する入門資格。LPIC-1 への前段として位置付けられる。 | 比較軸 | LPIC-1 | Linux Essentials | | ---------- | ---------------------- | ------------------------------ | | 運営団体 | LPI | LPI | | 試験コード | 101-500 + 102-500 | 010-160 | | 必要試験 | 2 試験 | 1 試験 | | 問題数 | 各 60 問 | 40 問 | | 試験時間 | 各 90 分 | 60 分 | | 前提条件 | なし | なし | | 有効期限 | 5 年 | 生涯有効(Lifetime) | | 対象者 | 実務 1-2 年層 | 学生・ジョブチェンジ志望・入門 | | 出題深度 | コマンド実行・設定理解 | 用語・概念・基本操作 | 出典: LPI 公式 [LPIC-1 Overview](https://www.lpi.org/our-certifications/lpic-1-overview/) / [Linux Essentials Overview](https://www.lpi.org/our-certifications/linux-essentials-overview/)(最終確認: 2026-05-22) ### 判断の要点 - Linux Essentials は LPI 公式が「LPIC-1 へのゲートウェイ」と位置付ける入門資格。生涯有効で更新不要。 - 用語・概念・OSS の基本理解を問うため、実機操作の比重は低い。Linux に触れたことがない学生・他職種からのジョブチェンジ志望者向け。 - LPIC-1 受験前の「自分は本当に Linux に向いているか」確認用としても妥当。Essentials → LPIC-1 のステップアップ学習者は実在する。 - 既に Linux 実務経験がある層は Essentials を飛ばして LPIC-1 から開始するのが効率的。 ## LPIC-1 vs LinuC レベル 1 {#vs-linuc} LinuC は LPI-Japan が日本市場向けに独自展開する Linux 技術者認定。LPI 本部の LPIC とは別運営の資格である点に注意。 | 比較軸 | LPIC-1 | LinuC レベル 1 | | -------------------- | ---------------- | ---------------------- | | 運営団体 | LPI(国際) | LPI-Japan(日本独自) | | 必要試験 | 101 + 102 | 101 + 102 | | 各試験問題数 | 60 問 | 約 60 問 | | 各試験時間 | 90 分 | 90 分(実試験 85 分) | | 受験料(税込) | 国別変動 | 16,500 円 / 試験 | | 前提条件 | なし | なし | | 認定の有効期限 | 5 年 | 5 年以内に再認定が必要 | | 国際通用性 | あり(国際資格) | 日本市場中心 | | 試験言語 | 日本語 / 英語他 | 日本語 / 英語 | | 試験範囲のバージョン | v5.0 | バージョン 10.0 | 出典: LPI Japan [LinuC レベル 1](https://linuc.org/linuc1/) / LPI 公式 [LPIC-1 Overview](https://www.lpi.org/our-certifications/lpic-1-overview/)(最終確認: 2026-05-22) ### 判断の要点 - LinuC は 2018 年に LPI-Japan が独自展開を開始した日本市場特化の資格。**LPIC-1 と LinuC レベル 1 は別資格**であり、相互認定はない。 - 国内エンタープライズ・公共セクター・国内 SIer での評価は LinuC が高い場合がある(採用要件として LinuC を指定する求人実例あり)。 - グローバル企業・外資・海外転職を視野に入れるなら LPIC-1 の国際通用性が有利。 - 試験範囲は LinuC が「クラウド時代の Linux 技術」を強化、LPIC-1 は伝統的なオンプレ Linux スキル中心。学習内容は重複部分が多いが完全一致ではない。 - 「LPIC-1 と LinuC レベル 1 は同じもの」という誤解は採用要件確認時に致命的な手戻りを生む。求人票の表記を確認する。 ## 101 と 102 の難易度・配点比較 {#101-102-weight} LPIC-1 v5.0(101-500 / 102-500)の試験主題と配点(weight)を主題群単位で集約した。配点合計は **101 試験 60 点 / 102 試験 56 点**。 ### 101 試験の配点(主題群別) | 主題群 | 内容 | 主題数 | 配点合計 | | ------ | ------------------------------------- | ------ | -------- | | 101 | システムアーキテクチャ | 3 | 8 | | 102 | Linux のインストールとパッケージ管理 | 6 | 12 | | 103 | GNU と Unix のコマンド | 8 | 26 | | 104 | デバイス・Linux ファイルシステム・FHS | 6 | 14 | | **計** | | **23** | **60** | ### 102 試験の配点(主題群別) | 主題群 | 内容 | 主題数 | 配点合計 | | ------ | -------------------------------------- | ------ | -------- | | 105 | シェル・シェルスクリプト | 2 | 8 | | 106 | ユーザーインターフェースとデスクトップ | 3 | 4 | | 107 | 管理タスク | 3 | 12 | | 108 | 必須システムサービス | 4 | 12 | | 109 | ネットワークの基礎 | 4 | 14 | | 110 | セキュリティ | 3 | 10 | | **計** | | **19** | **56** | 出典: LPI Wiki [LPIC-1 Objectives V5.0](https://wiki.lpi.org/wiki/LPIC-1_Objectives_V5.0)(最終確認: 2026-05-22) ### 判断の要点 - 101 試験は **主題群 103(GNU と Unix のコマンド)26 点** が突出して重い。コマンドライン操作・パイプ・リダイレクト・プロセス管理・正規表現が得点源。 - 102 試験は **主題群 109(ネットワーク基礎)14 点 + 主題群 108(システムサービス)12 点 + 主題群 107(管理タスク)12 点** に配点が分散。ネットワーク設定・cron・systemd・ログ管理・MTA 基礎が中心。 - 受験者体感では「101 のほうがコマンド暗記量が多い」「102 のほうが概念理解と設定ファイル形式の暗記が多い」という声が一般的。ただし学習開始時のスキル背景による個人差が大きい。 - 配点が高い主題に時間を多く割り当てるのが基本戦略。主題 103.x(101)と主題 107.x / 109.x(102)から着手する。 ## 101 と 102 の受験順番 {#order} LPIC-1 認定取得には 101 と 102 両方の合格が必要だが、**受験順序は規定されていない**。とはいえ推奨ルートは明確にある。 | 受験順序 | メリット | デメリット | | ----------------- | ------------------------------------------------------------------- | ------------------------------------ | | 101 → 102(推奨) | コマンドライン基礎 → 応用の自然な学習順序。102 学習が楽になる | 特になし | | 102 → 101 | 既にシェルスクリプト・ネットワーク実務経験がある層は 102 が早く済む | 101 のコマンド基礎が後付けで非効率 | | 同日受験 | 学習期間短縮・受験往復コスト削減・モチベ持続 | 1 日あたりの集中力消耗・落ちると痛い | ### 推奨ルート: 101 → 102 ほとんどの学習者にとって **101 → 102 の順** が推奨される。理由は以下。 1. 101 で学ぶコマンドライン基礎・パイプ・リダイレクト・正規表現・テキスト処理は、102 のシェルスクリプト(105.2)で必須前提となる。 2. 101 のパッケージ管理(102.4 / 102.5)と Linux ファイルシステム理解は、102 のユーザー管理(107.1)・cron 自動化(107.2)・システムログ(108.2)の前提知識として効く。 3. 102 → 101 で進めるとシェルスクリプトの学習中に「awk / sed の使い方が分からない」状態になりやすく、結局 101 範囲を先取り学習する羽目になる。 例外: 既に Linux サーバ運用 3 年以上の実務経験があり、シェルスクリプト・ネットワーク設定を日常的に扱っている層は **102 から先に受験するのも妥当**。実務で日々触れている範囲のほうが暗記コストが低いため。 ## 同日 / 同時受験の可否と戦略 {#same-day} 「101 と 102 を同日に連続受験できるか」は受験者の頻出疑問。結論から述べる。 ### 同日受験は可能 - Pearson VUE / OnVUE では **同日に複数試験を予約可能**。LPI 側でも 101 と 102 の同日受験を禁じる規定はない。 - 試験センター予約画面で午前 101 / 午後 102 のように 2 試験を同日に枠取りする運用が実例として存在する。 - ただし試験センターによっては「同日に複数試験を受ける場合は事前連絡が必要」とする場合がある。予約時に必ず確認する。 ### 同日受験のメリット・デメリット | 観点 | 同日受験のメリット | 同日受験のデメリット | | ---------------- | ------------------------------------------- | -------------------------------------- | | 学習期間 | 試験日 1 日に集約、追い込み学習が一回で済む | 学習範囲が広く準備期間が長期化しやすい | | 受験コスト | 試験センター往復が 1 回 | 同日 2 試験で集中力消耗、ミスリスク増 | | モチベーション | 一気に取得完了の達成感 | 1 つ目落ちると 2 つ目の集中力に響く | | スケジュール調整 | 1 日休暇で完了 | 受験料を一度に支払う必要あり | ### 同日受験を選ぶ判断基準 以下に **すべて当てはまる** なら同日受験を検討する価値がある。 1. 101 と 102 の模試で安定して 70% 以上得点できている 2. 連続 3 時間(90 分 + 休憩 + 90 分)の集中力を維持できる 3. 試験予約の枠取りに同日 2 枠が確保できる 4. 万一どちらか落ちても再受験コストを許容できる ### 推奨パターン 不安があれば **101 受験 → 合格確認 → 1-2 週間後に 102 受験** が最も安全。LPIC-1 の合否はオンライン CBT であれば試験直後にスコアレポートで判定可能なため、間隔を空けるロスは最小限。 ## 結論: あなたに合うのはどれか {#conclusion} ここまでの比較を統合し、シナリオ別の最終推奨を示す。 | シナリオ | 推奨資格 | 受験順序 | 想定学習期間 | | --------------------------------------- | ---------------- | ------------ | ------------ | | Linux 完全初心者・職種転換志望 | Linux Essentials | 010-160 単独 | 1-2 か月 | | 実務 1-2 年・国内 SIer / Web 系企業 | LPIC-1 | 101 → 102 | 2-4 か月 | | 実務 1-2 年・国内エンタープライズ志望 | LinuC レベル 1 | 101 → 102 | 2-4 か月 | | 北米転職・外資系・英語圏キャリア | CompTIA Linux+ | XK0-006 単独 | 3-5 か月 | | LPIC-1 既取得・サーバ運用エンジニア上位 | LPIC-2 | 201 → 202 | 3-6 か月 | | 国際通用性 + 日本国内通用性の両取り | LPIC-1 + LinuC-1 | 各 101 → 102 | 3-6 か月 | ### 迷ったらこの順で判定する 1. Linux に触れたことがない → **Linux Essentials** 2. Linux 実務 1-2 年あり、国内中心 → **LPIC-1 または LinuC レベル 1** 3. 海外・英語圏転職を視野 → **CompTIA Linux+** 4. LPIC-1 取得済 → **LPIC-2** 5. どちらの市場も狙う → **LPIC-1 を先に取得し、必要に応じて LinuC-1 を追加** ### 採用要件確認の重要性 求人票で「LPIC 取得者歓迎」と書かれていても、実際の要求が LPIC-1 か LinuC レベル 1 かで意味が異なる。応募前に企業の採用ページまたは人事に確認する。日本国内の採用市場では両者を混同する記載も少なくない。 ## チェックリスト・まとめ {#checklist} 受験前最終チェック。 - [ ] 自分の状況(実務年数・志望市場・キャリア方向)に合った資格を 1 つに絞った - [ ] 公式試験範囲(v5.0 / XK0-006 / v10.0 等)を確認した - [ ] 受験料を公式チャネル(LPI Marketplace / Pearson VUE / LinuC 公式)で最新値確認した - [ ] 試験言語(日本語可否)を確認した - [ ] 試験会場 / OnVUE(在宅オンライン受験)の選択を決めた - [ ] 受験順序を決定した(LPIC-1 / LinuC-1 / LPIC-2 の場合) - [ ] 同日受験を希望する場合は試験センターに事前確認した - [ ] 認定有効期限と更新方針を理解した ### 本記事の要点 - **受験戦略は自分の市場(国内 / 海外)と実務年数で決まる**。資格名から逆引きせず、キャリア状況から順引きする。 - **LPIC-1 と LinuC レベル 1 は別資格**。「同じ」と誤解しない。求人要件の表記を必ず確認する。 - **101 → 102 順が推奨**だが、規定はない。実務経験次第で 102 から先に受験する選択肢も妥当。 - **同日受験は可能**だが、不安があれば 1-2 週間空けるのが安全策。 - **数値情報は変動する**。受験前に必ず公式一次ソースで最新値を確認する。 --- ## 最終確認 本記事の比較数値・試験仕様・公式情報は **2026-05-22** 時点で各公式サイトから取得したもの。試験範囲・受験料・有効期限・現行バージョンは年内に改定される可能性があるため、受験申し込み前に下記公式サイトで最新情報を必ず確認すること。 - LPI 公式(LPIC-1 / LPIC-2 / Linux Essentials): https://www.lpi.org/our-certifications/ - CompTIA 公式(Linux+): https://www.comptia.org/certifications/linux - LPI Japan / LinuC 公式: https://linuc.org/linuc1/ - LPI Wiki(試験主題詳細): https://wiki.lpi.org/wiki/LPIC-1_Objectives_V5.0 # ファイルシステムのマウント - mount/umount/fstab【LPIC-1 104.3】 Source: https://penguin-gym-linux.com/articles/lpic/mounting-filesystems ## この記事で達成できること {#intro} - `mount` でデバイスを任意のマウントポイントに接続できる - `umount` で安全にアンマウントし、使用中エラーに対処できる - `/etc/fstab` の 6 フィールドを正確に読み書きできる - UUID / LABEL でデバイスを不変に指定し、`blkid` / `findmnt` で確認できる - `nofail` の有無による起動可否の違いを根拠付きで説明できる LPIC-1 主題 104.3「ファイルシステムをマウント・アンマウントする」の中核。デバイスとディレクトリツリーを接続する仕組みを、試験頻出の `fstab` 設定ミスまで含めて押さえる。 ## マウントとは何か {#what} マウントとは、ブロックデバイス上のファイルシステムを既存のディレクトリツリーの一点(マウントポイント)に接続する操作。Linux ではドライブにドライブレターを割り当てず、すべてを単一のツリーに統合する。 `mount` を引数なしで実行すると、現在マウント済みのすべてのファイルシステムが表示される。デバイス・マウントポイント・タイプ・オプションがこの順で並ぶ。 ```bash mount | grep sdb ``` ```output /dev/sdb1 on /mnt type ext4 (rw,relatime) ``` ファイルやディレクトリではなく「デバイスとディレクトリの対応関係」を扱う点が重要。マウントポイントに元々あったファイルは、マウント中は隠れて見えなくなる(消えるわけではなく、アンマウントで再び見える)。 ## mount でどうマウントするか {#mount} `mount デバイス マウントポイント` が基本形。`/etc/fstab` に該当エントリがあれば、どちらか一方の指定だけでマウントできる。 ### Step 1: マウント先のデバイスを確認する ```bash lsblk blkid ``` ```output NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 50G 0 disk └─sda1 8:1 0 50G 0 part / sdb 8:16 0 10G 0 disk └─sdb1 8:17 0 10G 0 part /dev/sda1: UUID="a1b2c3d4-..." TYPE="ext4" /dev/sdb1: UUID="e5f6a7b8-..." TYPE="ext4" LABEL="data" ``` `lsblk` でブロックデバイスの階層を、`blkid` で各パーティションの UUID・LABEL・ファイルシステムタイプを確認する。マウント前にタイプを把握しておく。 ### Step 2: デバイスをマウントする ```bash mount /dev/sdb1 /mnt findmnt /mnt ``` ```output TARGET SOURCE FSTYPE OPTIONS /mnt /dev/sdb1 ext4 rw,relatime ``` `mount /dev/sdb1 /mnt` で `/dev/sdb1` を `/mnt` に接続する。多くの環境ではタイプは自動判別されるが、明示するなら `-t ext4` を付ける。`findmnt` で接続結果をツリー表示して確認できる。 ### Step 3: オプションを指定してマウントする ```bash mount -t ext4 -o ro,noexec /dev/sdb1 /mnt findmnt -o TARGET,OPTIONS /mnt ``` ```output TARGET OPTIONS /mnt ro,noexec,relatime ``` `-o` でマウントオプションをカンマ区切りで渡す。`ro` は読み取り専用、`noexec` はそのファイルシステム上のバイナリ実行を禁止する。複数指定は `-o ro,noexec` のように連結する。 ### Step 4: ISO イメージをループバックマウントする ```bash mount -o loop ubuntu.iso /mnt findmnt /mnt ``` ```output TARGET SOURCE FSTYPE OPTIONS /mnt /dev/loop0[/ubuntu.iso] iso9660 ro,relatime ``` `-o loop` はファイル(ISO イメージ等)をループバックデバイス経由でマウントする。物理デバイスがなくてもイメージの中身をディレクトリとして参照できる。 ### Step 5: マウント済みファイルシステムを再マウントする ```bash mount -o remount,ro /mnt findmnt -o TARGET,OPTIONS /mnt ``` ```output TARGET OPTIONS /mnt ro,relatime ``` `-o remount` はアンマウントせずにオプションだけを変更する。`rw` で動作中のファイルシステムを `ro` に切り替える、といった用途に使う。`/` のような常時使用中のファイルシステムにも適用できる。 ## umount でどう安全に外すか {#umount} `umount` はマウントポイントまたはデバイス名のどちらでも指定できる。使用中のファイルシステムは外せないため、原因プロセスの特定が要点になる。 ### Step 1: 通常のアンマウント ```bash umount /mnt findmnt /mnt ``` ```output (no output) ``` `umount /mnt`(または `umount /dev/sdb1`)でアンマウントする。コマンド名は `unmount` ではなく `umount`(n なし)である点に注意。出力がなければ成功。 ### Step 2: 使用中で外せない場合 ```bash umount /mnt ``` ```output umount: /mnt: target is busy. ``` そのファイルシステム上に開いているファイルやカレントディレクトリを持つプロセスがあると `target is busy` で失敗する。原因プロセスは `lsof` や `fuser` で特定する。 ```bash lsof /mnt fuser -m /mnt ``` ```output COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME bash 4210 user cwd DIR 8,17 4096 2 /mnt /mnt: 4210c ``` `lsof /mnt` は対象を使用中のプロセス一覧を、`fuser -m /mnt` は PID を表示する。多くの場合、自分のシェルが `cd /mnt` した状態が原因なので、別ディレクトリへ移動してから再度 `umount` する。 ### Step 3: 最終手段としての遅延アンマウント ```bash umount -l /mnt ``` ```output (no output) ``` `-l`(lazy)はファイルシステムをツリーから即座に切り離し、参照がなくなった時点で実際の解放を行う。busy を回避できるが、書き込み中データの扱いに注意が必要なため、原因プロセスを止められない場合の最終手段とする。 ## /etc/fstab の 6 フィールドをどう書くか {#fstab} `/etc/fstab` は起動時や `mount -a` で参照される静的なマウント定義ファイル。各行は空白またはタブ区切りの 6 フィールドで構成される。 ```output UUID=e5f6a7b8-... /data ext4 defaults,nofail 0 2 ``` | # | フィールド | 内容 | 例 | | --- | ---------------------- | ------------------------------------------------ | ----------------- | | 1 | デバイス | マウント対象。`UUID=` / `LABEL=` / デバイスパス | `UUID=e5f6...` | | 2 | マウントポイント | 接続先ディレクトリ。swap は `none` または `swap` | `/data` | | 3 | ファイルシステムタイプ | `ext4` / `xfs` / `vfat` / `swap` / `auto` 等 | `ext4` | | 4 | マウントオプション | カンマ区切り。`defaults` 等 | `defaults,nofail` | | 5 | dump | `dump` 用のバックアップ要否(通常 `0`) | `0` | | 6 | fsck passno | 起動時 `fsck` の順序(`0`=検査しない) | `2` | 第 5 フィールドは `dump` コマンド用で、通常 `0`。第 6 フィールド(passno)は起動時のファイルシステムチェック順序で、ルート `/` は `1`、その他のチェック対象は `2`、検査不要なら `0` とする。passno が同じドライブのものは順次、異なるドライブのものは並行して検査される。 ::: tip 第 4 フィールドの `defaults` は `rw,suid,dev,exec,auto,nouser,async` をまとめて指定するショートカット。標準的なローカルディスクはこれで足りる。 ::: ## UUID / LABEL でなぜ指定するのか {#uuid} `/dev/sdb1` のようなデバイス名は、ディスクの増設や接続順の変化で `/dev/sdc1` 等に変わりうる。`fstab` でデバイス名を直書きすると、名前がずれた瞬間に誤ったデバイスをマウントするか、マウント失敗で起動に支障が出る。 UUID(ファイルシステム生成時に割り当てられる一意な識別子)や LABEL(任意のラベル名)はデバイスの接続位置に依存しないため、`fstab` ではこれらの指定が推奨される。値は `blkid` で確認する。 ```bash blkid /dev/sdb1 ``` ```output /dev/sdb1: UUID="e5f6a7b8-1234-..." TYPE="ext4" LABEL="data" ``` ```output # /etc/fstab の記述例 UUID=e5f6a7b8-1234-... /data ext4 defaults,nofail 0 2 LABEL=data /data ext4 defaults,nofail 0 2 ``` UUID 指定なら `blkid` で得た値をそのまま第 1 フィールドに書く。LABEL 指定はラベルを付けたファイルシステムにのみ有効で、`e2label` 等でラベルを設定しておく必要がある。 ## fstab を編集したらどう検証するか {#verify} fstab の記述ミスは起動不能に直結する。再起動する前に、現在のシステムで記述が正しいかを必ず検証する。 ### mount -a で一括テストする ```bash mount -a ``` ```output (no output = 成功) ``` `mount -a` は fstab 内の `auto` 対象エントリ(`noauto` 以外)をすべてマウントする。すでにマウント済みのものはスキップされる。エラーが出なければ fstab の記述は妥当。出力にエラーが出た行は記述ミスの可能性が高い。 ### findmnt で fstab エントリを照合する ```bash findmnt --verify ``` ```output /data [ ] target exists [ ] FS options: defaults,nofail 0 parsing errors, 0 errors, 0 warnings ``` `findmnt --verify` は fstab を解析し、マウントポイントの存在やオプションの妥当性を実際にマウントせずに点検する。`0 errors` を確認してから再起動するのが安全。 ::: warning fstab の編集後に検証せず再起動すると、記述ミスがある場合に通常ブートが止まり緊急シェルに落ちる。必ず `mount -a` と `findmnt --verify` で確認してから再起動すること。 ::: ## 主要マウントオプションの早見表 {#options} 第 4 フィールドや `mount -o` で使う代表的なオプション。組み合わせはカンマ区切り。 | オプション | 意味 | | ----------------- | ------------------------------------------------------------- | | `defaults` | `rw,suid,dev,exec,auto,nouser,async` の標準セット | | `ro` / `rw` | 読み取り専用 / 読み書き可能 | | `noexec` | そのファイルシステム上のバイナリ実行を禁止 | | `nosuid` | setuid / setgid ビットを無効化 | | `nodev` | デバイスファイルの解釈を禁止 | | `auto` / `noauto` | `mount -a`・起動時に自動マウントする / しない | | `user` / `users` | 一般ユーザーにマウントを許可(`user` は実行者のみ umount 可) | | `nofail` | デバイスが存在しなくても起動を継続する | ::: tip `noexec,nosuid,nodev` は USB メモリや一時データ領域のセキュリティ強化の定番。実行ファイルや setuid バイナリを置かない領域に付けておくと安全性が上がる。 ::: ## systemd による自動マウントの概要 {#systemd} systemd を採用する現代のディストリビューションでは、`/etc/fstab` は起動時に `systemd-fstab-generator` によって `.mount` ユニットへ自動変換され、systemd 管理下でマウントされる。fstab を書けば従来どおり動作する点は変わらない。 第 4 フィールドのオプションで systemd 固有の挙動を指定できる。例えば `x-systemd.automount` を付けると、対象ディレクトリに初めてアクセスした時点でオンデマンドにマウントされる(オートマウント)。ネットワークファイルシステムでは `_netdev` を付けてネットワーク到達後にマウントさせる。 ```output # アクセス時にオンデマンドマウントする例 UUID=e5f6... /data ext4 noauto,x-systemd.automount 0 2 ``` LPIC-1 の範囲では「fstab が systemd により .mount ユニットへ変換される」という関係と、`nofail` / `_netdev` 程度のオプションを押さえておけば十分。 ## よくあるミスと対処法 {#mistakes} ### ミス 1: fstab の記述ミスで起動不能になる 存在しないデバイスや誤った UUID を fstab に書くと、デフォルトでは起動が停止して緊急シェルに落ちる。必須でないデータ領域には `nofail` を付け、デバイスが無くても起動を継続させる。編集後は必ず `mount -a` と `findmnt --verify` で検証する。 ### ミス 2: 使用中で umount できない `target is busy` は、そのファイルシステム上に開いているファイルやカレントディレクトリを持つプロセスがあるサイン。`lsof /mnt` や `fuser -m /mnt` で原因プロセスを特定し、そのプロセスを終了するか作業ディレクトリを移動してから `umount` する。 ### ミス 3: UUID 変更でマウントに失敗する `mkfs` でファイルシステムを作り直すと UUID が変わるため、古い UUID を書いた fstab エントリはマウントに失敗する。再フォーマット後は `blkid` で新しい UUID を取得し、fstab を更新する。 ### ミス 4: コマンド名を `unmount` と打ってしまう アンマウントのコマンドは `umount`(n なし)。`unmount` は存在せず `command not found` になる。試験でもコマンド名の綴りが問われる。 ### ミス 5: マウントポイントのファイルが消えたと誤認する マウントポイントに元々あったファイルは、マウント中は隠れて見えなくなるだけで削除はされていない。アンマウントすれば元のファイルが再び見える。 ## トラブルシューティング {#troubleshooting} ### 症状: mount で「wrong fs type」エラーになる **原因**: ファイルシステムタイプの自動判別に失敗、または `-t` で誤ったタイプを指定している **確認**: ```bash blkid /dev/sdb1 ``` **対処**: `blkid` で実際のタイプを確認し、`mount -t 正しいタイプ` で明示する。タイプ対応のカーネルモジュールやツール(`xfsprogs` 等)が無い場合はそれを導入する。 ### 症状: 再起動後にデータ領域がマウントされていない **原因**: fstab エントリに `noauto` が付いている、または UUID がずれている **確認**: ```bash findmnt --verify blkid ``` **対処**: `findmnt --verify` で fstab の妥当性を点検し、`auto`(または `noauto` の削除)と正しい UUID に修正する。手動確認は `mount -a` で行う。 ### 症状: umount -l 後もディスクが取り外せない **原因**: 遅延アンマウントはツリーから切り離すだけで、参照中プロセスが残っていると実解放されない **確認**: ```bash fuser -m /dev/sdb1 ``` **対処**: `fuser -m` で残存プロセスを特定して終了させる。すべての参照が消えた時点で実際の解放が完了する。 ## 作業完了チェックリスト {#checklist} - [ ] `lsblk` / `blkid` でデバイスとタイプを確認した - [ ] `mount` でマウントし `findmnt` で結果を確認した - [ ] `umount` で安全にアンマウントできた(busy 時は `lsof` / `fuser` で対処) - [ ] `/etc/fstab` を UUID / LABEL で記述した - [ ] `mount -a` / `findmnt --verify` で再起動前に検証した - [ ] 任意領域に `nofail` を付けて起動不能リスクを回避した ## まとめ {#summary} | 場面 | コマンド | 目的 | | ------------ | ------------------------------------ | ---------------------------- | | マウント | `mount -t ext4 -o ro /dev/sdb1 /mnt` | タイプ・オプション指定で接続 | | アンマウント | `umount /mnt` | 安全に切り離す | | 使用中対処 | `lsof /mnt` / `fuser -m /mnt` | busy の原因特定 | | 識別子確認 | `blkid` | UUID / LABEL / タイプ取得 | | 状態確認 | `findmnt` | マウントツリー表示 | | fstab 検証 | `mount -a` / `findmnt --verify` | 再起動前の妥当性確認 | マウントはストレージ運用の基礎であり、パーティション作成・FHS と一体で理解すると効果が高い。fstab の記述ミスは起動不能に直結するため、検証手順まで含めて身につけることが LPIC-1 合格と実務の両方で重要になる。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [パーティションとファイルシステムの作成](/articles/lpic/partitions-and-filesystems) - [FHS とファイル検索](/articles/lpic/fhs-and-finding-files) - [基本的なファイル管理](/articles/lpic/basic-file-management) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # ネットワーク設定の永続化 - ip/nmcli/NetworkManager【LPIC-1 109.2】 Source: https://penguin-gym-linux.com/articles/lpic/network-configuration ## この記事で達成できること {#intro} - `ip` コマンドで一時的にIP・ルートを設定し、その設定が再起動で消える理由を説明できる - NetworkManager 環境で `nmcli` を使い、静的IPとデフォルトゲートウェイを**永続的に**設定できる - Ubuntu の netplan、Red Hat 系の ifcfg ファイルで永続設定を記述できる - `hostnamectl` と `/etc/hostname` でホスト名を永続化できる - 「一時設定」と「永続設定」を混同して起きる典型的な事故を回避できる LPIC-1 主題 109.2「基本的なネットワーク構成」(102 試験、重み 4)の中核。設定が再起動後も残るかどうかを意識して構成する力が問われる。 ## 一時設定と永続設定はどう違うのか {#temp-vs-persistent} `ip` コマンドで入れた設定はカーネルの実行中状態を直接書き換えるだけで、再起動すると消える。永続化するには設定ファイルか NetworkManager 等の管理ツールに登録する。 両者を取り違えると「設定したのに再起動で消えた」「ファイルに書いたのに反映されない」という事故につながる。役割を整理する。 | 種別 | 手段 | 反映タイミング | 再起動後 | | ---------------------- | --------------------------------------- | -------------- | -------- | | 一時設定 | `ip addr` / `ip route` / `ip link` | 即時 | 消える | | 永続設定(NM) | `nmcli connection modify` + `up` | `up` で反映 | 残る | | 永続設定(Ubuntu) | `/etc/netplan/*.yaml` + `netplan apply` | `apply` で反映 | 残る | | 永続設定(Red Hat 系) | `ifcfg-*` + `ifup` / 再起動 | `ifup` で反映 | 残る | ::: tip 本番作業の鉄則: まず `ip` で一時設定して疎通を確かめ、問題なければ同じ内容を永続設定に書き写す。いきなり永続ファイルだけ編集すると、設定ミス時に再起動で接続不能になり復旧が難しくなる。 ::: ## ip コマンドで一時設定する {#ip-command} `ip` は iproute2 が提供する現行の標準ツール。`ip OBJECT COMMAND` の形で、`address`(IP)・`link`(インターフェース)・`route`(経路)を操作する。 旧来の `ifconfig` / `route` はメンテナンスされておらず、現在は `ip` に置き換わっている。試験でも実務でも `ip` を基準に覚える。 ### 現在の状態を確認する ```bash ip addr show ip link show ip route show ``` ```output 2: eth0: mtu 1500 ... inet 192.168.1.10/24 brd 192.168.1.255 scope global eth0 default via 192.168.1.1 dev eth0 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.10 ``` `ip addr show` でIPアドレス、`ip route show` でルーティングテーブルを確認する。`addr` は `address`、`a` まで短縮できる。 ### IPアドレスを付与・削除する ```bash ip address add 192.168.1.20/24 dev eth0 ip address del 192.168.1.20/24 dev eth0 ``` 公式の構文は `ip address add IFADDR dev IFNAME`、削除は `ip address del IFADDR dev IFNAME`。`IFADDR` は `192.168.1.20/24` のように「アドレス/プレフィックス長」で指定する。 ### インターフェースを up / down する ```bash ip link set eth0 up ip link set eth0 down ``` `ip link set IFNAME up` で有効化、`down` で無効化。IPを付与してもリンクが down のままだと通信できない。 ### デフォルトゲートウェイを設定する ```bash ip route add default via 192.168.1.1 dev eth0 ip route del default via 192.168.1.1 dev eth0 ``` `default`(= 0.0.0.0/0)宛ての経路を `via` で指定したゲートウェイに向ける。これがデフォルトゲートウェイの設定。`ip route del` は add と同じ引数で削除する。 ::: warning ここまでの `ip` 設定はすべて一時的。再起動すると消える。本番では次節以降の永続化が必須。 ::: ## nmcli で永続設定する {#nmcli} NetworkManager 環境では `nmcli connection modify` で接続プロファイルを書き換えると、その内容がディスクに保存され永続化される。`nmcli connection up` で再アクティブ化して反映する。 NetworkManager は多くのデスクトップ/サーバーディストリビューションの標準。プロファイル単位で設定を管理し、`ip` の一時設定とは別レイヤーで動く。 ### 接続とデバイスの状態を確認する ```bash nmcli connection show nmcli device status ``` ```output NAME UUID TYPE DEVICE Wired connection 1 abcd1234-... ethernet eth0 DEVICE TYPE STATE CONNECTION eth0 ethernet connected Wired connection 1 ``` `nmcli connection show` で接続プロファイル一覧、`nmcli device status` でデバイスとの紐付けを確認する。`connection` は `con`、`device` は `dev` まで短縮できる。 ### 静的IPを永続設定する ```bash nmcli connection modify "Wired connection 1" \ ipv4.method manual \ ipv4.addresses 192.168.1.10/24 \ ipv4.gateway 192.168.1.1 \ ipv4.dns 8.8.8.8 nmcli connection up "Wired connection 1" ``` ```output Connection successfully activated ... ``` `ipv4.method manual` で静的(手動)に切り替え、`ipv4.addresses`・`ipv4.gateway`・`ipv4.dns` を指定する。`modify` の時点でプロファイルに保存され、`up` で実際の設定が反映される。DHCP に戻すには `ipv4.method auto` を指定する。 ### 新規プロファイルを作成する ```bash nmcli connection add type ethernet ifname eth0 \ ipv4.method manual ipv4.addresses 192.168.1.10/24 \ ipv4.gateway 192.168.1.1 ipv4.dns 8.8.8.8 ``` `nmcli connection add` で接続を新規作成する。`connection add` / `modify` による変更はデフォルトで永続(ディスク保存)であり、`--temporary` を付けたときだけ一時的になる。 ## netplan で永続設定する(Ubuntu) {#netplan} Ubuntu は `/etc/netplan/*.yaml` に YAML で設定を書き、`netplan apply` で適用する。netplan はバックエンド(NetworkManager または systemd-networkd)へ設定を橋渡しするレンダラー。 YAML はインデントに厳格で、タブは使えない(スペースのみ)。設定ファイルの構造を覚える。 ### 静的IPの YAML 例 ```bash ls /etc/netplan/ ``` ```output 01-netcfg.yaml ``` ```yaml network: version: 2 ethernets: eth0: addresses: - 192.168.1.10/24 routes: - to: default via: 192.168.1.1 nameservers: addresses: - 8.8.8.8 - 8.8.4.4 ``` デフォルトゲートウェイは `routes` の `to: default` + `via` で表現する。`addresses` は「アドレス/プレフィックス長」のリスト。 ### 設定を適用する ```bash netplan try netplan apply ``` `netplan apply` で設定を反映する。`netplan try` は適用後に一定時間で自動ロールバックするため、リモート作業での切断事故を防げる。 ::: danger YAML を編集しただけでは反映されない。`netplan apply` の実行を忘れると「ファイルは正しいのに設定が効かない」状態になる。これは典型的なミス。 ::: ## Red Hat 系の ifcfg ファイル {#ifcfg} Red Hat 系の従来方式では `/etc/sysconfig/network-scripts/ifcfg-<デバイス名>` に設定を記述し、`ifup` / `ifdown` または再起動で反映する。 NetworkManager を併用する環境では、`nmcli` 経由の変更がこの ifcfg ファイルへ書き込まれる構成もある。どちらで管理しているかを把握しておく。 ### 静的IPの ifcfg 例 ```bash cat /etc/sysconfig/network-scripts/ifcfg-eth0 ``` ```output DEVICE=eth0 BOOTPROTO=none ONBOOT=yes IPADDR=192.168.1.10 PREFIX=24 GATEWAY=192.168.1.1 DNS1=8.8.8.8 ``` 主なディレクティブ: `BOOTPROTO=none`(または `static`)で静的、`dhcp` で DHCP。`ONBOOT=yes` で起動時に自動有効化。`IPADDR` / `PREFIX`(または `NETMASK`)/ `GATEWAY` / `DNS1` でアドレス系を指定する。 ### ifup / ifdown で反映する ```bash ifdown eth0 ifup eth0 ``` `ifup <デバイス名>` でインターフェースを有効化、`ifdown` で無効化する。ifcfg ファイルを編集したら `ifdown` → `ifup` で再読み込みする。 ## ホスト名を永続化する {#hostname} `hostnamectl hostname <名前>` で静的ホスト名を設定すると `/etc/hostname` に書き込まれ、再起動後も残る。`hostnamectl status` で現在の各種ホスト名を確認できる。 ホスト名には静的(static)・一時的(transient)・装飾(pretty)の3種類がある。永続化の対象は static で、`/etc/hostname` が実体。 ```bash hostnamectl status hostnamectl hostname host01 hostnamectl status ``` ```output Static hostname: localhost Static hostname: host01 ``` `hostnamectl hostname host01` が現行 systemd の書式。古い表記の `hostnamectl set-hostname host01` も同じ意味で、`/etc/hostname` に永続化される。 ::: tip ホスト名を変えたら `/etc/hosts` の `127.0.0.1`(または `127.0.1.1`)の行にも新しい名前を追加しておく。これを忘れると一部アプリで名前解決の警告や遅延が出る。 ::: ## よくあるミスと回避策 {#pitfalls} ネットワーク設定の失敗は接続断に直結する。頻出する5つの落とし穴を押さえる。 - **`ip` 設定が再起動で消える**: `ip addr` / `ip route` は一時設定。永続化するには nmcli・netplan・ifcfg のいずれかに書き写す - **nmcli と ifcfg の二重管理**: NetworkManager 管理下のインターフェースを ifcfg で直接編集すると競合する。どちらか一方に統一する - **netplan apply 忘れ**: YAML を編集しただけでは反映されない。必ず `netplan apply`(またはリモートなら `netplan try`)を実行する - **`ifconfig` / `route` を使い続ける**: 非推奨の旧ツール。現行は `ip addr` / `ip route`。試験も実務も `ip` 基準 - **nmcli modify 後に up し忘れる**: `modify` はプロファイル保存のみ。`nmcli connection up NAME` で再アクティブ化しないと現行設定に反映されない ## トラブルシューティング {#troubleshooting} ### 症状: 再起動したらIP設定が消えた **原因**: `ip addr add` など一時設定だけで、永続設定に書いていない **確認**: ```bash ip addr show nmcli connection show ``` **対処**: NetworkManager 環境なら `nmcli connection modify` で永続化、Ubuntu なら `/etc/netplan/*.yaml` に記述。一時設定の内容を永続側へ書き写す。 ### 症状: netplan の YAML を編集したが反映されない **原因**: `netplan apply` を実行していない、または YAML のインデント(タブ混入)エラー **確認**: ```bash netplan try ``` **対処**: `netplan apply` で適用する。`netplan try` がエラーを返すなら YAML を見直す(タブ禁止、スペースインデント)。 ### 症状: nmcli で設定したのに現在のIPが変わらない **原因**: `nmcli connection modify` で保存しただけで、接続を再アクティブ化していない **確認**: ```bash nmcli device status ``` **対処**: `nmcli connection up "接続名"` で再アクティブ化する。これで保存済みプロファイルが現行設定に反映される。 ## 作業完了チェックリスト {#checklist} - [ ] `ip addr show` / `ip route show` で現在の設定を確認した - [ ] 一時設定(`ip`)と永続設定の違いを理解した - [ ] nmcli・netplan・ifcfg のいずれかで静的IPを永続化した - [ ] デフォルトゲートウェイを永続設定した - [ ] `hostnamectl hostname` でホスト名を永続化した - [ ] 再起動またはサービス再起動後も設定が残ることを確認した ## まとめ {#summary} | 環境 | 永続設定の手段 | 反映コマンド | | ------------------ | ------------------------- | ---------------------- | | 一時設定(全環境) | `ip addr` / `ip route` | 即時(再起動で消える) | | NetworkManager | `nmcli connection modify` | `nmcli connection up` | | Ubuntu | `/etc/netplan/*.yaml` | `netplan apply` | | Red Hat 系 | `ifcfg-*` | `ifup` / 再起動 | | ホスト名 | `/etc/hostname` | `hostnamectl hostname` | ネットワーク設定は「一時か永続か」を常に意識するのが要点。`ip` で素早く試し、動いたら永続側へ書き写す型を身につければ、再起動で消える事故も反映漏れも防げる。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [TCP/IPの基礎とIPアドレス・ポート](/articles/lpic/internet-protocols) - [ネットワークの疎通確認とトラブルシュート](/articles/lpic/network-troubleshooting) - [DNSクライアント設定と名前解決](/articles/lpic/client-dns) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # ネットワーク障害の基本対応 - ping/traceroute/ss/dig【LPIC-1 109.3】 Source: https://penguin-gym-linux.com/articles/lpic/network-troubleshooting ## この記事で達成できること {#intro} - `ping` でホストとの疎通を確認し、`-c` で回数を制御できる - `traceroute` / `tracepath` で経路上のどこで止まっているか特定できる - `ss` で待ち受けポートと接続状態を確認し、`netstat` との対応を説明できる - `ip addr` / `ip route` で IP 設定とルーティングを確認できる - `dig` / `host` で DNS 名前解決の成否を切り分けられる - 物理層から DNS・アプリ層まで、層を追った障害切り分けができる LPIC-1 主題 109.3「基本的なネットワークの問題を解決する」の中核。「つながらない」を、思い込みではなく層ごとの確認で原因に絞り込む技術。 ## 障害切り分けはどの順で進めるか {#flow} ネットワーク障害は「下の層から上の層へ」順に確認すると最短で原因に到達する。下が壊れていれば上は必ず失敗するため、下から潰すのが鉄則。 | 層 | 確認内容 | 主なコマンド | | ------------- | ---------------------- | ------------------------------ | | 物理 / リンク | NIC が上がっているか | `ip link show` | | IP | アドレスが付いているか | `ip addr show` | | ルーティング | ゲートウェイ・経路 | `ip route show` / `traceroute` | | 疎通 | 相手まで届くか | `ping` | | DNS | 名前を解決できるか | `dig` / `host` | | アプリ | ポートが開いているか | `ss -tulnp` | 「Web サイトが見られない」なら、まず `ping 8.8.8.8` のように IP 直打ちで疎通を見て、次に `ping example.com` で名前解決込みを見る。IP では通るのにドメインで通らなければ DNS の問題、と一発で切り分けられる。 ## 手順 {#steps} ### Step 1: インターフェースと IP を確認する ```bash ip link show ip addr show ``` ```output 2: enp0s3: mtu 1500 ... link/ether 08:00:27:ab:cd:ef brd ff:ff:ff:ff:ff:ff 2: enp0s3: mtu 1500 ... inet 192.168.1.20/24 brd 192.168.1.255 scope global enp0s3 ``` `ip link` はネットワークデバイス(リンク層)の状態を表示する。`UP` と `LOWER_UP` があればリンクは生きている。`ip addr` で IP アドレス(`inet 192.168.1.20/24`)が付いているかを確認する。アドレスが付いていなければ、ここから先は確認しても無駄。 ::: tip `ip` は `iproute2` パッケージのコマンドで、`ip addr` が従来の `ifconfig`、`ip route` が `route` に相当する役割を担う。LPIC-1 v5.0 では `ip` コマンド体系を中心に学習する。 ::: ### Step 2: ルーティングとゲートウェイを確認する ```bash ip route show ``` ```output default via 192.168.1.1 dev enp0s3 proto dhcp metric 100 192.168.1.0/24 dev enp0s3 proto kernel scope link src 192.168.1.20 ``` `ip route` はルーティングテーブルを表示する。`default via 192.168.1.1` がデフォルトゲートウェイ。この行が無いとローカルネットワーク外(インターネット)へ出られない。「LAN 内は通るが外に出られない」障害の大半はここが原因。 ### Step 3: ping で疎通を確認する ```bash ping -c 4 192.168.1.1 ping -c 4 8.8.8.8 ``` ```output PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=115 time=12.3 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=115 time=11.8 ms --- 8.8.8.8 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3004ms ``` `ping` は ICMP ECHO パケットで疎通を確認する。`-c 4` は「4 回送信したら終了」の意味(man ping: "Stop after sending count ECHO_REQUEST packets")。これを付けないと `Ctrl+C` まで送り続ける。送信間隔は `-i` で秒指定でき、デフォルトは 1 秒。まずゲートウェイ、次に外部 IP の順で確認する。 ::: warning `ping` が通らない=ネットワーク不通とは限らない。セキュリティ上 ICMP をブロックしているホストやファイアウォールは珍しくない。`ping` 失敗は「ICMP 応答が無い」事実に過ぎず、TCP ポートへの接続(後述の `ss` や別手段)と合わせて判断する。 ::: ### Step 4: traceroute で経路を追う ```bash traceroute 8.8.8.8 ``` ```output traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets 1 192.168.1.1 (192.168.1.1) 0.8 ms 0.7 ms 0.7 ms 2 10.0.0.1 (10.0.0.1) 8.2 ms 8.1 ms 8.0 ms 3 * * * 4 8.8.8.8 (8.8.8.8) 12.1 ms 12.0 ms 11.9 ms ``` `traceroute` は宛先までの経路上の各ホップ(ルーター)を順に表示する(man: "tracks the route packets taken ... on their way to a given host")。各ホップで止まっていれば、その手前まではつながっていると分かる。`* * *` はそのホップが「タイムアウト内に応答しなかった」表示で、必ずしも障害ではなく ICMP を返さないルーターでも出る。 非特権ユーザーで実行したい、または経路の MTU も知りたい場合は `tracepath` を使う。man tracepath によれば「tracepath は traceroute と違い特権プログラムではない」。 ```bash tracepath example.com ``` ### Step 5: dig / host で DNS を確認する ```bash dig example.com dig +short example.com ``` ```output ;; ANSWER SECTION: example.com. 3600 IN A 93.184.216.34 93.184.216.34 ``` `dig` は DNS 問い合わせの詳細を表示する。基本構文は `dig @server name type`(BIND 9 公式)。`ANSWER SECTION` に解決結果(リソースレコード)が出れば名前解決は成功。`+short` は簡潔表示で、結果の値だけを返す。 ```bash dig MX example.com dig -x 93.184.216.34 host example.com ``` レコード種別を指定するには type を付ける(`dig MX example.com`)。`-x` は IP からホスト名への逆引き(PTR)。`host` はより簡潔な DNS ルックアップユーティリティで、`host name` だけで A・AAAA・MX をまとめて確認できる。 ::: tip `ping 8.8.8.8` は通るのに `ping example.com` が `Name or service not known` で失敗する場合、ネットワークは正常で DNS だけが壊れている。`dig` で名前解決の可否を直接確認し、`/etc/resolv.conf` の `nameserver` 設定を見る。 ::: ### Step 6: ss で待ち受けポートと接続を確認する ```bash ss -tulnp ``` ```output Netid State Local Address:Port Peer Address:Port Process tcp LISTEN 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=812,fd=3)) tcp LISTEN 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=940,fd=6)) udp UNCONN 0.0.0.0:68 0.0.0.0:* ``` `ss` はソケット統計を表示する。よく使う組み合わせ `-tulnp` の意味は man ss に従い以下のとおり。 | オプション | 意味(man ss) | | ---------- | ------------------------------------------------ | | `-t` | TCP ソケットを表示 | | `-u` | UDP ソケットを表示 | | `-l` | LISTEN(待ち受け)状態のソケットのみ表示 | | `-n` | サービス名を解決しない(ポート番号を数値で表示) | | `-p` | ソケットを使用しているプロセスを表示 | 「サービスを起動したのに接続できない」場合、`ss -tlnp` で対象ポートが `LISTEN` になっているか、どのプロセスが握っているかを確認する。 ## なぜ netstat ではなく ss を使うのか {#why} `netstat` の man ページ NOTES 節には次のように明記されている。「This program is mostly obsolete.(このプログラムはほぼ廃止予定)Replacement for netstat is ss.」。つまり公式に `ss` が後継と位置づけられている。同様に `netstat -r` の代替は `ip route`、`netstat -i` の代替は `ip -s link` と man に書かれている。 実務上も `ss` は `netstat` より高速で、`/proc/net` を直接読まずカーネルから直接ソケット情報を取得する。オプション体系は互換性が高く、`netstat -tulnp` はほぼそのまま `ss -tulnp` に置き換えられる。古い手順書には `netstat` が残っているが、新規環境では `ss` を第一選択にするのが定石。LPIC-1 でも両者の対応関係(`ss` が後継、`ip route` が `route`/`netstat -r` の後継)は頻出ポイント。 | レガシー | 後継(推奨) | 用途 | | ---------------- | -------------- | ------------------------- | | `netstat -tulnp` | `ss -tulnp` | ソケット・ポート一覧 | | `netstat -r` | `ip route` | ルーティングテーブル | | `ifconfig` | `ip addr` | インターフェース・IP 設定 | | `route add` | `ip route add` | 経路の追加 | ## よくあるミス {#pitfalls} - **`netstat` 前提で手を止める**: 最小構成のコンテナや新しいディストリには `netstat`(`net-tools`)が入っていないことがある。`ss` を覚えておけば追加インストール不要で動く。 - **`ping` が通らない=不通と決めつける**: ICMP をブロックした環境では正常でも `ping` は失敗する。TCP ポートへの接続可否(`ss` / アプリ層の確認)と合わせて判断する。 - **DNS 障害を疎通障害と取り違える**: `ping ドメイン名` の失敗だけで「ネットワークが切れた」と判断しない。IP 直打ち(`ping 8.8.8.8`)が通れば物理・IP・経路は正常で、原因は DNS。 - **デフォルトゲートウェイ未設定を見落とす**: LAN 内は通るのに外に出られないとき、`ip route` に `default via ...` が無いケースが多い。アドレスだけ見て満足しない。 - **`ss` のポート番号をホスト名解決で読み違える**: `-n` を付けないと `:22` が `:ssh` と表示され、ポート番号の特定に手間取る。確認時は `-n` を付ける。 ## トラブルシューティング {#troubleshooting} ### 症状: ping example.com が Name or service not known で失敗する **原因**: ネットワークは正常だが DNS 名前解決が失敗している **確認**: ```bash ping -c 2 8.8.8.8 dig +short example.com ``` **対処**: IP 直打ちの `ping` が通れば物理・IP・経路は正常。`dig` で解決できなければ `/etc/resolv.conf` の `nameserver` 行を確認し、到達可能な DNS サーバーを設定する。 ### 症状: LAN 内は通るがインターネットに出られない **原因**: デフォルトゲートウェイが未設定、または誤っている **確認**: ```bash ip route show ping -c 2 192.168.1.1 ``` **対処**: `ip route` に `default via <ゲートウェイIP>` の行があるか確認する。無ければ `ip route add default via <ゲートウェイIP>` で追加(恒久化はディストリの設定ファイルで行う)。 ### 症状: サービスを起動したのにクライアントから接続できない **原因**: プロセスが意図したアドレス/ポートで待ち受けていない、またはファイアウォールでブロックされている **確認**: ```bash ss -tlnp ``` **対処**: 対象ポートが `LISTEN` か、待ち受けアドレスが `127.0.0.1`(ローカルのみ)でなく `0.0.0.0` 等になっているかを確認する。`LISTEN` していればアプリは正常で、原因はファイアウォール側の可能性が高い。 ### 症状: traceroute の途中が \* \* \* のまま進まない **原因**: 途中のルーターが ICMP を返さない、または実際にそこで経路が途切れている **確認**: ```bash traceroute 8.8.8.8 tracepath 8.8.8.8 ``` **対処**: `* * *` でも宛先まで到達し最終行に応答があれば経路は生きている(中継ルーターが ICMP を返さないだけ)。最終ホップまで届かない場合は、最後に応答したホップの先で障害が起きていると絞り込める。 ## 作業完了チェックリスト {#checklist} - [ ] `ip addr` / `ip link` でインターフェースと IP を確認した - [ ] `ip route` でデフォルトゲートウェイを確認した - [ ] `ping -c` でゲートウェイと外部 IP の疎通を確認した - [ ] `traceroute` / `tracepath` で経路上の停止点を確認した - [ ] `dig` / `host` で DNS 名前解決を確認した - [ ] `ss -tulnp` で待ち受けポートとプロセスを確認した ## まとめ {#summary} | 層 | コマンド | 確認すること | | ------------ | --------------------- | ---------------------- | | リンク / IP | `ip addr` / `ip link` | NIC・IP アドレス | | ルーティング | `ip route` | デフォルトゲートウェイ | | 疎通 | `ping -c 4` | 相手まで届くか | | 経路 | `traceroute` | どのホップで止まるか | | DNS | `dig` / `host` | 名前を解決できるか | | アプリ | `ss -tulnp` | ポートが開いているか | ネットワーク障害対応の本質は「層を順に潰す」こと。下から確認すれば、勘に頼らず原因にたどり着ける。`netstat` は `ss` に、`ifconfig`/`route` は `ip` に置き換わった点も合わせて押さえれば、109.3 の出題範囲を一通りカバーできる。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [インターネットプロトコルの基礎](/articles/lpic/internet-protocols) - [ネットワーク設定の基本](/articles/lpic/network-configuration) - [DNSクライアントの設定](/articles/lpic/client-dns) - [システムログの管理](/articles/lpic/system-logging) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) # パッケージ管理 - dpkg/apt と rpm/yum/dnf【LPIC-1 102.4/102.5】 Source: https://penguin-gym-linux.com/articles/lpic/package-management ## この記事で達成できること {#intro} - Debian 系(`dpkg` / `apt`)と RPM 系(`rpm` / `yum` / `dnf`)の役割分担を説明できる - 低レベルツール(`dpkg` / `rpm`)と高レベルツール(`apt` / `dnf`)の**依存解決の違い**を区別できる - インストール・削除・更新・検索・問い合わせの各操作を両系統で実行できる - ファイルがどのパッケージに属するかを `dpkg -S` / `rpm -qf` で逆引きできる - 試験頻出の「`dpkg -i` は依存を解決しない」を根拠付きで答えられる LPIC-1 主題 102.4「Debian パッケージ管理を使用する」と 102.5「RPM および YUM パッケージ管理を使用する」の両方を 1 本でカバーする。 ## 2 系統 4 ツールをどう使い分けるか {#flow} パッケージ管理ツールは「低レベル(単一パッケージ操作・依存解決なし)」と「高レベル(リポジトリ連携・依存自動解決)」の 2 層に分かれる。原則は**高レベルツールを日常使いし、低レベルツールは問い合わせや手元の .deb/.rpm 直接導入に使う**。 | 系統 | 低レベル(依存解決なし) | 高レベル(依存解決あり) | リポジトリ設定 | | --------- | ------------------------ | ------------------------------- | ---------------------------- | | Debian 系 | `dpkg` | `apt` / `apt-get` / `apt-cache` | `/etc/apt/sources.list(.d/)` | | RPM 系 | `rpm` | `yum` / `dnf` | `/etc/yum.repos.d/` | ::: tip 覚え方: 末尾が `kg` / `pm` の `dpkg` / `rpm` は「個別パッケージを直接いじる低レベル」。`apt` / `yum` / `dnf` は「リポジトリと依存を面倒見る高レベル」。試験では「依存を自動解決するのはどれか」が頻出。 ::: ## Debian 系: dpkg の操作 {#dpkg} `dpkg` は Debian 系の低レベルツール。手元の `.deb` ファイルを直接インストールし、インストール済みパッケージの問い合わせを行う。**依存関係は解決しない**点が最大の特徴。 ### dpkg -i: .deb を直接インストールする ```bash dpkg -i package_1.0_amd64.deb ``` ```output Selecting previously unselected package package. (Reading database ... 180000 files and directories currently installed.) Preparing to unpack package_1.0_amd64.deb ... Unpacking package (1.0) ... Setting up package (1.0) ... ``` `-i`(`--install`)は `.deb` を展開して設定まで行う。依存パッケージが不足していると `dependency problems` で止まる(後述のトラブルシューティング参照)。 ### dpkg -r / -P: 削除する ```bash dpkg -r package dpkg -P package ``` `-r`(`--remove`)は設定ファイルを残して削除、`-P`(`--purge`)は設定ファイルも含めて完全削除する。「設定を残す/消す」の違いが試験で問われる。 ### dpkg -l / -L / -S: 問い合わせる ```bash dpkg -l # インストール済みパッケージを一覧 dpkg -L package # パッケージが配置したファイルの一覧 dpkg -S /usr/bin/which # ファイルが属するパッケージを逆引き ``` ```output debianutils: /usr/bin/which ``` `-l`(list)はインストール済み一覧、`-L`(list files)は「そのパッケージが置いたファイル」、`-S`(search)は「このファイルはどのパッケージのものか」の逆引き。`-L` と `-S` は向きが逆である点に注意する。 ### dpkg --configure: 未設定パッケージを設定する ```bash dpkg --configure -a ``` 展開済みだが設定が完了していないパッケージを設定する。`-a` で対象を全パッケージにする。インストールが途中で中断したときの復旧に使う。 ## Debian 系: apt / apt-get / apt-cache の操作 {#apt} `apt` 系はリポジトリと連携し、**依存関係を自動解決**する高レベルツール。`apt` は対話的な日常利用向け、`apt-get` / `apt-cache` はスクリプト向けの安定したインターフェース。 ### apt update / install / upgrade ```bash apt update # パッケージ情報(インデックス)を更新 apt install package # 依存解決つきでインストール apt upgrade # 更新可能なパッケージを一括更新 ``` ```output Reading package lists... Done Building dependency tree... Done The following additional packages will be installed: libfoo1 libbar2 ... ``` `apt update` はリポジトリのインデックスを取得するだけで、パッケージ本体は更新しない。実際の更新は `apt upgrade`。この 2 つの役割分担が試験頻出ポイント。`apt install` は不足する依存(上記 `libfoo1` 等)を自動で引き込む。 ### apt remove / purge ```bash apt remove package # 設定を残して削除 apt purge package # 設定も含めて完全削除 ``` `apt remove` は `dpkg -r` 相当(設定保持)、`apt purge` は `dpkg -P` 相当(設定削除)。違いは dpkg と同じ対応関係になる。 ### apt-cache search / show(apt search / show) ```bash apt-cache search keyword # キーワードでパッケージを検索 apt-cache show package # パッケージの詳細情報を表示 ``` ```output nginx - small, powerful, scalable web/proxy server nginx-core - nginx web/proxy server (standard version) ``` `search` はインストール前に名前や説明から目的のパッケージを探す。`show` はバージョン・依存・説明などのメタ情報を表示する。`apt search` / `apt show` でも同じことができる。 ::: tip `aptitude` も apt と同等の高レベルツールで、対話的 TUI と高度な依存解決を持つ。LPIC-1 では名前と位置づけ(apt 系の代替フロントエンド)を押さえておけばよい。 ::: ## RPM 系: rpm の操作 {#rpm} `rpm` は RPM 系(Red Hat / Fedora / openSUSE 系)の低レベルツール。`dpkg` と同じく**単一パッケージの導入と問い合わせ**を担い、依存解決はしない。 ### rpm -i / -U / -e: インストール・更新・削除 ```bash rpm -ivh package-1.0.x86_64.rpm # 新規インストール rpm -Uvh package-2.0.x86_64.rpm # アップグレード(なければ新規導入) rpm -e package # 削除(erase) ``` `-i`(install)は新規導入、`-U`(upgrade)は既存があれば更新・なければ新規導入。`-e`(erase)は削除。`-v`(詳細表示)と `-h`(進捗をハッシュ記号で表示)はよく組み合わせる。 ### rpm -q 系: 問い合わせる ```bash rpm -qa # インストール済み全パッケージ rpm -qi bash # 指定パッケージの詳細情報 rpm -ql bash # パッケージが配置したファイル一覧 rpm -qf /bin/bash # ファイルが属するパッケージを逆引き ``` ```output bash-5.1.8-9.el9.x86_64 ``` 問い合わせは必ず `-q`(query)を付ける。`-qa`(all 一覧)、`-qi`(info)、`-ql`(list files)、`-qf`(file 逆引き)。`-ql` と `-qf` は dpkg の `-L` / `-S` に対応し、向きが逆になる点も同じ。 ### rpm -V: 検証する ```bash rpm -V bash ``` インストール後にファイルが改変・破損していないか(サイズ・パーミッション・MD5 など)を検証する。出力がなければ変更なし、`S`(サイズ)や `M`(モード)などの文字が出れば該当項目に差異がある。 ::: tip `rpm2cpio package.rpm | cpio -idmv` で `.rpm` をインストールせずにファイルだけ取り出せる。単一ファイルを救出したいときの定番。LPIC-1 では存在と用途を知っていれば十分。 ::: ## RPM 系: yum / dnf の操作 {#yum} `yum` とその後継 `dnf` は RPM 系の高レベルツール。リポジトリ(`/etc/yum.repos.d/`)と連携し、**依存関係を自動解決**する。コマンド体系はほぼ共通で、新しいディストリでは `dnf` が標準。 ### dnf install / remove / update ```bash dnf install package # 依存解決つきでインストール dnf remove package # 削除 dnf update # 更新可能パッケージを一括更新 ``` ```output Dependencies resolved. ================================================================ Package Arch Version Repository Size ================================================================ Installing: package x86_64 1.0-1.el9 appstream 120 k Installing dependencies: libfoo x86_64 2.3-4.el9 baseos 45 k ... ``` `dnf install` は不足する依存(上記 `libfoo`)を自動で引き込む。`yum install` と書いても同じ(多くのディストリで `yum` は `dnf` へのリンク)。 ### dnf search / info / list / repolist ```bash dnf search keyword # キーワードでパッケージを検索 dnf info package # パッケージの詳細情報 dnf list installed # インストール済み一覧 dnf repolist # 有効なリポジトリ一覧 ``` ```output repo id repo name baseos Rocky Linux 9 - BaseOS appstream Rocky Linux 9 - AppStream ``` `search` で探し、`info` で詳細を確認、`list installed` で導入済みを確認、`repolist` でどのリポジトリが有効かを確認する。リポジトリ設定そのものは `/etc/yum.repos.d/` 配下の `.repo` ファイルで管理される。 ## 両系統の対応表 {#mapping} 操作ごとに Debian 系と RPM 系のコマンドを対応づけると、片方を覚えればもう片方は類推できる。**依存解決の有無が低レベル/高レベルの境界**であることを軸に整理する。 | 操作 | Debian 低レベル | Debian 高レベル | RPM 低レベル | RPM 高レベル | | --------------------------- | --------------- | ------------------ | ------------ | ------------- | | インストール | `dpkg -i` | `apt install` | `rpm -i` | `dnf install` | | アップグレード | `dpkg -i` | `apt upgrade` | `rpm -U` | `dnf update` | | 削除 | `dpkg -r` | `apt remove` | `rpm -e` | `dnf remove` | | 完全削除(設定も) | `dpkg -P` | `apt purge` | `rpm -e` | `dnf remove` | | 検索 | — | `apt-cache search` | — | `dnf search` | | 詳細情報 | — | `apt-cache show` | `rpm -qi` | `dnf info` | | 全パッケージ一覧 | `dpkg -l` | — | `rpm -qa` | `dnf list` | | パッケージのファイル一覧 | `dpkg -L` | — | `rpm -ql` | — | | ファイル → パッケージ逆引き | `dpkg -S` | — | `rpm -qf` | — | | 依存自動解決 | しない | する | しない | する | ::: warning 低レベルツール(`dpkg` / `rpm`)は依存を解決しない。依存を含めて正しく導入したいなら、必ず高レベルツール(`apt` / `dnf`)を使う。手元の `.deb` / `.rpm` を直接入れて依存エラーになったときも、リポジトリ経由(`apt install ./pkg.deb` 等)に切り替えると解決できることが多い。 ::: ## よくあるミス {#mistakes} 実務と試験の両方で繰り返し見かける誤りを 5 つ挙げる。いずれも「低レベルと高レベルの違い」「更新の 2 段階」を理解していれば回避できる。 1. **`dpkg -i` で依存解決を期待する**: `.deb` を `dpkg -i` で入れて `dependency problems` で止まる。依存を解決したいなら `apt install ./pkg.deb` を使う。 2. **`rpm -e` で依存エラーを無視しようとする**: 他パッケージが依存しているものを `rpm -e` すると `Failed dependencies` で拒否される。むやみに `--nodeps` で強制せず、`dnf remove` に任せるのが安全。 3. **`sources.list` 編集後の `apt update` 忘れ**: リポジトリを追加・変更しても `apt update` でインデックスを更新しないと、新しいパッケージは見つからない。 4. **`apt update` と `apt upgrade` の混同**: `update` は情報更新だけ、実際にパッケージを新しくするのは `upgrade`。`update` しただけで最新になったと誤解しない。 5. **`-q` なしの rpm クエリ**: 問い合わせのつもりで `rpm bash` のように `-q` を付け忘れると意図した結果にならない。問い合わせは必ず `rpm -q...` の形にする。 ## トラブルシューティング {#troubleshooting} ### 症状: dpkg -i で dependency problems と出て止まる **原因**: `dpkg` は依存を解決しないため、必要なパッケージが未導入だと設定段階で失敗する **確認**: ```bash dpkg -i package.deb ``` **対処**: 依存を自動で引き込む高レベルツールに任せる。 ```bash apt install ./package.deb apt -f install ``` `apt install ./package.deb` でローカルの `.deb` を依存解決つきで導入できる。すでに壊れた状態なら `apt -f install`(`--fix-broken`)で依存の不足を補う。 ### 症状: rpm -e で Failed dependencies と出る **原因**: 削除しようとしたパッケージに他のパッケージが依存している **確認**: ```bash rpm -e package rpm -q --whatrequires package ``` **対処**: 依存関係ごと安全に処理する高レベルツールを使う。 ```bash dnf remove package ``` `dnf remove` は依存関係を考慮して安全に削除する。`rpm -e --nodeps` での強制削除はシステムを壊す恐れがあるため避ける。 ### 症状: apt install で対象パッケージが見つからない **原因**: リポジトリのインデックスが古い、または `sources.list` 編集後に更新していない **確認**: ```bash cat /etc/apt/sources.list ls /etc/apt/sources.list.d/ ``` **対処**: ```bash apt update apt install package ``` `apt update` でインデックスを取得し直してから再度インストールする。RPM 系で同様の症状なら `dnf repolist` で有効リポジトリを確認する。 ## 作業完了チェックリスト {#checklist} - [ ] 低レベル(`dpkg` / `rpm`)と高レベル(`apt` / `dnf`)の役割を説明できる - [ ] `dpkg -i` / `rpm -i` が依存を解決しないことを理解した - [ ] `apt update` と `apt upgrade` の違いを区別できる - [ ] `dpkg -S` / `rpm -qf` でファイルからパッケージを逆引きできる - [ ] リポジトリ設定の場所(`/etc/apt/sources.list(.d/)` / `/etc/yum.repos.d/`)を把握した ## まとめ {#summary} | 役割 | Debian 系 | RPM 系 | | ------------ | ------------------------------- | ------------------- | | 低レベル | `dpkg` | `rpm` | | 高レベル | `apt` / `apt-get` / `apt-cache` | `yum` / `dnf` | | インストール | `apt install` | `dnf install` | | 削除 | `apt remove` / `apt purge` | `dnf remove` | | 逆引き | `dpkg -S` | `rpm -qf` | | リポジトリ | `/etc/apt/sources.list(.d/)` | `/etc/yum.repos.d/` | パッケージ管理は「低レベルは単一操作・問い合わせ、高レベルは依存解決つきの日常運用」という対比で押さえれば、102.4 と 102.5 を横断的に答えられる。次はファイルシステムやファイル検索と組み合わせて、システム管理の全体像を固めるとよい。 ## 次に読む {#next} - [ファイル管理の基本コマンド](/articles/lpic/basic-file-management) - [FHS とファイル検索](/articles/lpic/fhs-and-finding-files) - [パーティションとファイルシステム](/articles/lpic/partitions-and-filesystems) - [LPIC-1 出題範囲完全ガイド](/articles/lpic/exam-scope-101-102) - [LPIC-1 学習ハブ](/lpic1) # パーティションとファイルシステム作成 - fdisk/mkfs【LPIC-1 104.1】 Source: https://penguin-gym-linux.com/articles/lpic/partitions-and-filesystems ## この記事で達成できること {#intro} - MBR と GPT の違いと、選択基準を説明できる - `fdisk` / `gdisk` / `parted` でパーティションを作成できる - `mkfs` 系コマンドで ext4 / xfs / vfat ファイルシステムを作成できる - `mkswap` と `swapon` でスワップ領域を構築できる - `lsblk` / `blkid` / `partprobe` でディスク状態を正確に把握できる LPIC-1 主題 104.1「パーティションとファイルシステムを作成する」の中核。ディスクを区切り、使える状態にするまでの一連の流れを扱う。 ## パーティション作成は何から決めるのか {#flow} 最初に決めるのは「MBR か GPT か」。これでパーティション編集ツールが変わる。2TB を超えるディスクや 4 個を超える基本パーティションが必要なら GPT を選ぶ。 ディスクを使える状態にするまでは、原則として次の 3 段階を踏む。順序を飛ばすとマウントできない。 | 段階 | 操作 | 代表コマンド | | ----------------------- | ------------------------------ | ----------------------------------- | | 1. パーティション分割 | ディスクを区画に分ける | `fdisk` / `gdisk` / `parted` | | 2. ファイルシステム作成 | 区画をフォーマットする | `mkfs.ext4` / `mkfs.xfs` / `mkswap` | | 3. マウント | 区画をディレクトリに割り当てる | `mount`(本記事の範囲外) | ::: warning パーティションを作っただけでは、まだデータを書き込めません。`mkfs` でファイルシステムを作成する手順を忘れると、`mount` の段階で `wrong fs type` エラーになります。 ::: ## MBR と GPT はどう違うのか {#mbr-gpt} MBR は古い方式で、基本パーティション 4 個までの制限がある。GPT は新しい方式で、大容量ディスクと多数のパーティションに対応する。 | 項目 | MBR(msdos) | GPT | | -------------------- | -------------------------- | ------------------ | | 最大ディスク容量 | 2TB まで | 2TB 超対応 | | 基本パーティション数 | 4 個まで | 実用上 128 個 | | 拡張パーティション | 必要(基本3+拡張1の構成) | 不要 | | 編集ツール | `fdisk` | `gdisk` | | 共通ツール | `parted`(両対応) | `parted`(両対応) | MBR で 5 個以上の区画が必要な場合、基本パーティション 3 個+拡張パーティション 1 個を作り、拡張パーティションの中に論理パーティションを置く。GPT ではこの制約がなく、すべて対等なパーティションとして扱える。 ## 手順 {#steps} ### Step 1: lsblk でディスク構成を確認する ```bash lsblk ``` ```output NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 50G 0 disk ├─sda1 8:1 0 49G 0 part / └─sda2 8:2 0 1G 0 part [SWAP] sdb 8:16 0 20G 0 disk ``` `lsblk` はブロックデバイスとパーティションを階層表示する。ここでは `sdb` がまだ未分割(子の `sdb1` 等がない)と分かる。作業対象のデバイス名を取り違えると既存データを破壊するため、必ず最初に確認する。 ### Step 2: fdisk で MBR パーティションを作成する ```bash fdisk /dev/sdb ``` ```output Command (m for help): n Partition type p primary (0 primary, 0 extended, 4 free) e extended Select (default p): p Partition number (1-4, default 1): 1 First sector (2048-41943039, default 2048): Last sector, +/-sectors or +/-size{K,M,G,T,P} (default 41943039): +10G Created a new partition 1 of type 'Linux' and of size 10 GiB. Command (m for help): w The partition table has been altered. ``` `fdisk` の主要サブコマンドは `n`(新規作成)、`p`(テーブル表示)、`d`(削除)、`t`(タイプ変更)、`w`(書き込んで終了)、`q`(保存せず終了)。`n` でパーティション種別・番号・サイズを指定し、最後に `w` で確定する。 ::: danger `w` を実行するまで変更はディスクに書き込まれません。逆に `w` を押すと既存のパーティションテーブルが上書きされます。誤ったデバイスを開いた場合は `q` で保存せず終了してください。 ::: ### Step 3: GPT は gdisk、汎用なら parted を使う ```bash gdisk /dev/sdb parted /dev/sdb mklabel gpt parted /dev/sdb mkpart primary ext4 1MiB 10GiB ``` ```output Command (? for help): n Partition number (1-128, default 1): 1 First sector (...): Last sector (...): +10G Current type is 8300 (Linux filesystem) Command (? for help): w ``` GPT のディスクは `gdisk` で対話編集する。操作体系は `fdisk` に似ており、`n`(作成)/`d`(削除)/`w`(書き込み)を使う。`parted` は MBR・GPT 両対応で、`mklabel`(テーブル形式の作成)と `mkpart`(パーティション作成)を一行で実行できるため、スクリプト化に向く。 ### Step 4: partprobe でカーネルにテーブルを再読込させる ```bash partprobe /dev/sdb lsblk /dev/sdb ``` ```output NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sdb 8:16 0 20G 0 disk └─sdb1 8:17 0 10G 0 part ``` パーティションテーブルを書き換えても、使用中のディスクではカーネルが古い情報を保持していることがある。`partprobe` はカーネルにパーティションテーブルの再読込を要求する。新しい `sdb1` が認識されたことを `lsblk` で確認する。 ### Step 5: mkfs でファイルシステムを作成する ```bash mkfs.ext4 /dev/sdb1 blkid /dev/sdb1 ``` ```output mke2fs 1.46.5 (30-Dec-2021) Creating filesystem with 2621440 4k blocks and 655360 inodes Filesystem UUID: 3f29c0a1-...-9b2e4d Writing superblocks and filesystem accounting information: done /dev/sdb1: UUID="3f29c0a1-...-9b2e4d" TYPE="ext4" ``` `mkfs.ext4` は ext4 ファイルシステムを作成する。`mkfs.xfs`(XFS)、`mkfs.vfat`(FAT、`-F 32` で FAT32)も同じ要領で使える。`mkfs.ext4` は内部的に `mke2fs` を呼び出す。作成後は `blkid` で UUID とファイルシステム種別を確認する。UUID は `/etc/fstab` での永続マウントに使う。 ### Step 6: mkswap でスワップ領域を作成する ```bash mkswap /dev/sdb2 swapon /dev/sdb2 swapon --show ``` ```output Setting up swapspace version 1, size = 2 GiB no label, UUID=8a1f...e07c NAME TYPE SIZE USED PRIO /dev/sdb2 partition 2G 0B -2 ``` スワップ用パーティションは `mkfs` ではなく `mkswap` で初期化する。`swapon` で有効化、`swapoff` で無効化する。`swapon --show` または `free -h` で有効なスワップを確認できる。スワップ領域も `mkswap` を実行しないと `swapon` が失敗する点に注意する。 ## ext / xfs / vfat / btrfs はどう使い分けるのか {#fs-types} 汎用の Linux ファイルシステムなら ext4 が無難。大容量・高並列なら xfs、Windows との共有や EFI システムパーティションには vfat を使う。 | 種別 | 作成コマンド | 主な用途 | | ----- | ------------ | ----------------------------------------------- | | ext2 | `mkfs.ext2` | ジャーナルなし。小容量・ブートパーティション等 | | ext3 | `mkfs.ext3` | ext2 にジャーナリングを追加 | | ext4 | `mkfs.ext4` | 標準的な Linux 用。大容量・高信頼 | | xfs | `mkfs.xfs` | 大容量・高スループット向け | | vfat | `mkfs.vfat` | FAT。Windows 共有・EFI システムパーティション | | btrfs | `mkfs.btrfs` | スナップショット・サブボリューム対応の新しい FS | ext2/3/4 は系統的に互換性があり、`mke2fs` が共通の実体。xfs は一度作成すると縮小できない(拡張のみ)点が運用上の注意点。btrfs はスナップショットや RAID 機能を内蔵する比較的新しいファイルシステムで、LPIC-1 では概要レベルの理解で足りる。 ## トラブルシューティング {#troubleshooting} ### 症状: mount すると wrong fs type と言われる **原因**: パーティションを作成したが `mkfs` でファイルシステムを作っていない **確認**: ```bash blkid /dev/sdb1 ``` **対処**: `blkid` で `TYPE=` が表示されなければ未フォーマット。`mkfs.ext4 /dev/sdb1` などでファイルシステムを作成する。 ### 症状: fdisk で 5 個目の基本パーティションが作れない **原因**: MBR は基本パーティション 4 個までの制限がある **確認**: ```bash fdisk -l /dev/sdb ``` **対処**: 拡張パーティションを作り、その中に論理パーティションを配置する。制約を受けたくなければ `parted ... mklabel gpt` で GPT に変更する(既存データは消える)。 ### 症状: パーティションを作ったのに lsblk に出てこない **原因**: カーネルが古いパーティションテーブルを保持している **確認**: ```bash lsblk /dev/sdb ``` **対処**: `partprobe /dev/sdb` でカーネルにテーブルを再読込させる。それでも反映されない場合は再起動する。 ### 症状: swapon が Invalid argument で失敗する **原因**: 対象パーティションに `mkswap` を実行していない **確認**: ```bash blkid /dev/sdb2 ``` **対処**: `mkswap /dev/sdb2` でスワップ領域として初期化してから `swapon /dev/sdb2` を実行する。 ## 作業完了チェックリスト {#checklist} - [ ] `lsblk` で作業対象デバイスを取り違えていないか確認した - [ ] `fdisk` / `gdisk` / `parted` でパーティションを作成し `w` で書き込んだ - [ ] `partprobe` でカーネルにテーブルを再読込させた - [ ] `mkfs.ext4`(または用途に応じた mkfs)でフォーマットした - [ ] スワップは `mkswap` → `swapon` で有効化した - [ ] `blkid` で UUID とファイルシステム種別を確認した ## まとめ {#summary} | 場面 | コマンド | 目的 | | ------------ | --------------------------- | ------------------------------ | | 確認 | `lsblk` / `fdisk -l` | デバイスとパーティションの把握 | | MBR 編集 | `fdisk /dev/sdb` | n/p/d/t/w/q で対話編集 | | GPT 編集 | `gdisk /dev/sdb` | GPT パーティション作成 | | 汎用編集 | `parted ... mklabel/mkpart` | スクリプト向き・両形式対応 | | 反映 | `partprobe /dev/sdb` | テーブル再読込 | | フォーマット | `mkfs.ext4 /dev/sdb1` | ファイルシステム作成 | | スワップ | `mkswap` / `swapon` | スワップ領域の構築 | | 確認 | `blkid /dev/sdb1` | UUID・FS 種別の確認 | パーティションとファイルシステムの作成は、ディスクを使える状態にする入口。次はこのファイルシステムを実際にマウントし、`/etc/fstab` で永続化する手順に進むと理解が完成する。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [ファイルシステムのマウントと管理](/articles/lpic/mounting-filesystems) - [FHS とファイル検索](/articles/lpic/fhs-and-finding-files) - [基本的なファイル管理](/articles/lpic/basic-file-management) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # プロセス優先度の制御 - niceとreniceの仕組み Source: https://penguin-gym-linux.com/articles/lpic/process-priorities-nice ## この記事で達成できること {#intro} - nice 値とカーネルの優先度(PRI)の関係を説明できる - `nice` でコマンドを低優先度で起動できる - `renice` で実行中プロセスの優先度を変更できる - `top` / `ps` で優先度を正確に確認できる - 試験頻出の「一般ユーザーは nice 値を下げられない」を根拠付きで答えられる LPIC-1 主題 103.6「プロセスの実行優先度を変更する」の中核。CPU を多く使うバックグラウンド処理を、対話作業を妨げずに走らせる技術。 ## nice 値の判断 {#flow} nice 値は `-20`(最高優先度)から `19`(最低優先度)の整数。値が小さいほど CPU を優先的に得る。 | nice 値 | 優先度 | 典型用途 | | ------------- | --------------------- | ---------------------------------------- | | `-20` 〜 `-1` | 高(root のみ設定可) | リアルタイム性が必要な処理 | | `0` | 標準(デフォルト) | 通常のコマンド | | `1` 〜 `19` | 低 | バックアップ・バッチ・ビルド等の重い処理 | 「バックアップが対話作業を重くする」なら `nice -n 19` で低優先度起動。一般ユーザーは nice 値を**上げる(数値を大きく=優先度を下げる)方向にしか変更できない**。これが試験頻出ポイント。 ## 手順 {#steps} ### Step 1: 現在の nice 値を確認する ```bash ps -o pid,ni,comm -p $$ nice ``` ```output PID NI COMMAND 2451 0 bash 0 ``` `NI` 列が nice 値。引数なしの `nice` は現在のシェルの nice 値を表示する。デフォルトは `0`。 ### Step 2: nice でコマンドを低優先度起動する ```bash nice -n 19 tar czf backup.tar.gz /var/www & ps -o pid,ni,comm -C tar ``` ```output PID NI COMMAND 3120 19 tar ``` `nice -n 19 cmd` は nice 値 19(最低優先度)でコマンドを起動する。CPU が空いているときだけ処理が進むため、対話作業への影響を最小化できる。 ### Step 3: renice で実行中プロセスを変更する ```bash renice -n 10 -p 3120 ps -o pid,ni,comm -p 3120 ``` ```output 3120 (process ID) old priority 19, new priority 10 PID NI COMMAND 3120 10 tar ``` `renice -n 10 -p PID` は実行中プロセスの nice 値を変更する。`-u user` でユーザーの全プロセス、`-g group` でグループ単位の変更も可能。 ### Step 4: top で優先度を監視する ```bash top -o NI ``` ```output PID USER PR NI VIRT RES %CPU COMMAND 3120 user 30 10 118000 4200 2.3 tar 2451 user 20 0 12000 3800 0.1 bash ``` `top` の `NI` 列が nice 値、`PR` がカーネル内部の優先度。`PR = 20 + NI`(通常プロセス)の関係。`top` 内で `r` キーを押すと対話的に renice もできる。 ### Step 5: 一般ユーザーの制約を確認する ```bash renice -n -5 -p 3120 sudo renice -n -5 -p 3120 ``` ```output renice: failed to set priority for 3120 (process ID): Permission denied 3120 (process ID) old priority 10, new priority -5 ``` 一般ユーザーは nice 値を負(高優先度)にできず `Permission denied` になる。優先度を上げるには root 権限(`sudo`)が必要。 ## なぜ一般ユーザーは優先度を上げられないのか {#why} nice 値を負にすると、そのプロセスが他ユーザーのプロセスより CPU を優先的に奪う。もし誰でも自由に優先度を上げられれば、悪意あるユーザーやバグのあるプログラムがシステム全体を占有でき、マルチユーザー環境の公平性が崩壊する。そのため Linux は「優先度を下げる(譲る)操作は誰でも可、優先度を上げる(奪う)操作は root のみ」という非対称な権限設計を採用している。 `nice` の語源は「他プロセスに親切(nice)に CPU を譲る」こと。デフォルトの 0 から数値を上げるほど「より親切に譲る」=低優先度になる。この方向性を把握すれば nice 値の符号で混乱しなくなる。なお nice はあくまでスケジューラへのヒントであり、CPU が空いていれば低 nice 値のプロセスも処理は進む。 ## トラブルシューティング {#troubleshooting} ### 症状: renice で Permission denied になる **原因**: 一般ユーザーが nice 値を下げよう(負方向)としている、または他人のプロセスを変更しようとしている **確認**: ```bash ps -o pid,user,ni -p PID ``` **対処**: 優先度を上げるには `sudo renice` を使う。他ユーザーのプロセス変更も root 権限が必要。 ### 症状: nice -n 19 にしても処理が遅くならない **原因**: nice は相対的なヒントで、競合するプロセスがなければフル CPU を使える **確認**: ```bash top -o %CPU ``` **対処**: nice は CPU 競合時にのみ効果が出る正常な挙動。I/O 優先度を下げたい場合は別途 `ionice` を使う。 ### 症状: バックグラウンドのビルドが対話作業を重くする **原因**: ビルドプロセスがデフォルト nice 値 0 で CPU を対等に奪っている **確認**: ```bash ps -o pid,ni,comm -C make ``` **対処**: `renice -n 19 -p PID` で実行中プロセスを最低優先度に下げる。今後は `nice -n 19 make` で起動する。 ## 作業完了チェックリスト {#checklist} - [ ] `ps -o pid,ni,comm` で現在の nice 値を確認した - [ ] `nice -n 19` で低優先度起動した - [ ] `renice -n -p PID` で実行中プロセスを変更した - [ ] `top` の NI 列で優先度を監視した - [ ] 一般ユーザーが優先度を上げられない制約を確認した ## まとめ {#summary} | 場面 | コマンド | 目的 | | ------------ | --------------------- | -------------------------- | | 起動時設定 | `nice -n 19 cmd` | 重い処理を低優先度で開始 | | 実行中変更 | `renice -n 10 -p PID` | 動作中プロセスの優先度調整 | | ユーザー単位 | `renice -n 5 -u user` | 全プロセス一括変更 | | 確認 | `ps -el` / `top` | NI・PRI の監視 | | 優先度上げ | `sudo renice -n -5` | root のみ可能 | プロセス優先度は CPU リソース管理の基礎。LPIC-1 のプロセス管理範囲を一通り押さえたら、シェル環境やリンク機構と合わせて運用知識が完成する。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全12記事と出題範囲マップ)](/lpic1) - [ps・top・killの使い方](/articles/tutorials/process-management-basics) - [Linuxプロセス管理の実践](/articles/tutorials/process-management-practical) - [シェル環境変数の設定](/articles/lpic/shell-environment) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # 正規表現 - 基本・拡張正規表現とgrep応用 Source: https://penguin-gym-linux.com/articles/lpic/regular-expressions ## この記事で達成できること {#intro} - 基本正規表現(BRE)と拡張正規表現(ERE)の違いを説明できる - アンカー・文字クラス・量指定子を使ったパターンを正確に書ける - `grep` / `egrep` / `sed` で正規表現を使い分けられる - ログから特定パターンの行を抽出・除外できる - 試験で頻出の「BRE のバックスラッシュ要否」を間違えなくなる LPIC-1 主題 103.7「正規表現を使用してテキストファイルを検索する」の中核。正規表現は文字列のパターンを記述する言語で、grep / sed / awk すべての基盤になる。 ## BRE と ERE の判断 {#flow} 正規表現には方言がある。LPIC で問われるのは POSIX の 2 種。 | 種類 | 使うコマンド | `+ ? { } ( ) \|` の扱い | | ---------------- | ------------------------------ | ---------------------------------------- | | 基本正規表現 BRE | `grep` / `sed` | バックスラッシュが必要(`\+` `\{` `\(`) | | 拡張正規表現 ERE | `grep -E` / `egrep` / `sed -E` | そのまま使える(`+` `{` `(`) | 「`grep` で `\{3\}` と書くのに `grep -E` では `{3}`」という差は試験頻出。どちらの方言で書いているかを常に意識する。 ## メタ文字一覧 {#meta} | メタ文字 | 意味 | 例 | | --------------------------- | -------------------------------------------------------- | ------------------ | | `.` | 任意の1文字 | `a.c` → abc, axc | | `*` | 直前要素の0回以上 | `ab*` → a, ab, abb | | `^` | 行頭アンカー | `^root` | | `$` | 行末アンカー | `bash$` | | `[ ]` | 文字クラス | `[0-9]` 数字1文字 | | `[^ ]` | 否定文字クラス | `[^0-9]` 数字以外 | | `\+`(ERE は `+`) | 直前要素の1回以上 | `a\+` | | `\?`(ERE は `?`) | 直前要素の0または1回 | `colou\?r` | | `\{n,m\}`(ERE は `{n,m}`) | n回以上m回以下 | `[0-9]\{1,3\}` | | `\|`(OR / 選択) | いずれかにマッチ。BRE はバックスラッシュ必須、ERE は不要 | `cat\|dog` | ## 手順 {#steps} ### Step 1: アンカーで位置を固定する ```bash grep '^#' /etc/ssh/sshd_config grep 'bash$' /etc/passwd grep -x 'root' users.txt ``` ```output # $OpenBSD: sshd_config #Port 22 root:x:0:0:root:/root:/bin/bash root ``` `^` は行頭、`$` は行末を表すゼロ幅アンカー。`grep -x` は行全体一致(`^pattern$` と等価)。コメント行抽出や完全一致検索の基本。 ### Step 2: 文字クラスで範囲を指定する ```bash grep '[0-9]\{1,3\}\.[0-9]\{1,3\}' access.log grep -E '[0-9]{1,3}(\.[0-9]{1,3}){3}' access.log grep '[[:space:]]' config.txt ``` ```output 192.168.1.10 - - [17/May/2026] 10.0.0.5 - - [17/May/2026] ``` `[0-9]` は数字 1 文字、`\{1,3\}` は 1〜3 回の繰り返し(BRE はバックスラッシュ必須)。`[[:space:]]` は POSIX 文字クラスで空白類にマッチする。 ### Step 3: BRE と ERE を使い分ける ```bash grep 'colou\?r' notes.txt grep -E 'colou?r' notes.txt grep -E 'error|warning|fatal' app.log ``` ```output color theme favourite colour [ERROR] disk full [WARNING] high load ``` `?` `+` `|` `{}` `()` を使うときは `grep -E`(ERE)が読みやすい。BRE で同じことをするにはバックスラッシュエスケープが必要になり可読性が落ちる。 ### Step 4: 抽出と除外を組み合わせる ```bash grep -v '^#' /etc/fstab | grep -v '^$' grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}' access.log | sort -u grep -ci 'timeout' app.log ``` ```output UUID=xxxx / ext4 defaults 0 1 192.168.1.10 10.0.0.5 7 ``` `-v` でマッチしない行(コメント・空行除去)、`-o` でマッチ部分のみ抽出、`-c` で件数、`-i` で大文字小文字無視。設定ファイルの実効行抽出に頻出。 ### Step 5: sed で正規表現置換する ```bash echo "2026-05-17" | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/' sed -n '/^ERROR/p' app.log ``` ```output 17/05/2026 ERROR connection refused ``` `sed -E` は ERE モード。`\1` `\2` は後方参照で、`( )` でキャプチャしたグループを置換側で再利用する。日付フォーマット変換などに使う。 ## なぜ BRE にバックスラッシュが要るのか {#why} BRE は歴史的に古い grep / ed の構文を保つために、`+` `?` `{` `(` `|` を「特殊な意味なし(リテラル)」として扱う。これらを量指定子・グループ・OR として使うにはバックスラッシュで「これはメタ文字」と明示する必要がある。ERE はこの煩雑さを解消し、特殊文字を最初からメタ文字として扱う新しい構文。`grep -E` / `egrep` が ERE を有効化する。 この設計差を理解していないと「`grep 'a+'` が `a` の 1 回以上にマッチしない(リテラルの `a+` を探す)」という挙動を説明できない。LPIC ではこの落とし穴がそのまま出題される。 ## トラブルシューティング {#troubleshooting} ### 症状: grep 'a+' が1回以上にマッチしない **原因**: BRE では `+` がリテラル文字扱い **確認**: ```bash grep 'a\+' file grep -E 'a+' file ``` **対処**: ERE(`grep -E`)を使うか、BRE なら `\+` とエスケープする。 ### 症状: ドットが任意の文字にマッチして誤検出する **原因**: `.` は正規表現で任意の1文字を意味する **確認**: ```bash grep '192\.168' access.log ``` **対処**: リテラルのドットを探すときは `\.` とエスケープする。固定文字列検索なら `grep -F`(fgrep)が安全。 ### 症状: 特殊文字を含む文字列を検索できない **原因**: `[` `*` `$` などがメタ文字として解釈される **確認**: ```bash grep -F '[error]' app.log ``` **対処**: パターンに正規表現が不要なら `grep -F` で固定文字列検索する。必要な箇所だけ `\` でエスケープする。 ## 作業完了チェックリスト {#checklist} - [ ] `^` `$` アンカーで行頭・行末一致を確認した - [ ] `[0-9]` 等の文字クラスと量指定子を組み合わせた - [ ] BRE(`\{3\}`)と ERE(`grep -E '{3}'`)の差を実機で確認した - [ ] `-v` `-o` `-c` `-i` の各オプションを試した - [ ] `sed -E` の後方参照(`\1`)で置換した ## まとめ {#summary} | 場面 | 書き方 | 目的 | | -------- | --------------------- | ------------------- | | 行頭一致 | `grep '^pattern'` | コメント/特定行抽出 | | 完全一致 | `grep -x` / `^...$` | 行全体一致 | | 1回以上 | `grep -E 'a+'` | 連続文字検出 | | 抽出のみ | `grep -o` | マッチ部分だけ取得 | | 置換 | `sed -E 's/.../.../'` | 後方参照で整形 | 正規表現は grep / sed / awk すべての基盤。次はリンク機構やプロセス優先度など他の試験範囲に進むと知識が連結する。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全12記事と出題範囲マップ)](/lpic1) - [テキストストリームフィルタ](/articles/lpic/text-stream-filters) - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [ハードリンクとシンボリックリンク](/articles/lpic/hard-symbolic-links) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # セキュリティ管理タスク - SUID 探索/lsof/ulimit/sudo【LPIC-1 110.1】 Source: https://penguin-gym-linux.com/articles/lpic/security-administration ## この記事で達成できること {#intro} - `find` で SUID / SGID が設定された危険なファイルを洗い出せる - `lsof` でオープン中のファイル・ネットワークポートを特定できる - `fuser` でファイルやファイルシステムを使用中のプロセスを確認・停止できる - `ulimit` でソフト/ハードリミットの違いを理解しリソースを制限できる - `sudo` / `sudoers` を `visudo` で安全に設定できる - `who` / `w` / `last` でログイン状況を監査できる LPIC-1 主題 110.1「セキュリティ管理業務を実施する」の中核。権限昇格の温床となる SUID ファイルの監査、リソース枯渇攻撃を防ぐ `ulimit`、最小権限を実現する `sudo` を体系的に押さえる。 ## なぜ SUID ファイルの監査が重要なのか {#flow} SUID が設定された実行ファイルは、実行者ではなく**ファイル所有者(多くは root)の権限**で動く。脆弱性があれば権限昇格に直結するため、定期的な洗い出しが不可欠だ。 | ビット | 8 進数 | 意味 | 探索コマンド | | ------ | ------ | ------------------------------------- | -------------------- | | SUID | `4000` | 所有者権限で実行(`passwd` 等) | `find / -perm -4000` | | SGID | `2000` | グループ権限で実行 / ディレクトリ継承 | `find / -perm -2000` | | Sticky | `1000` | `/tmp` 等で他人のファイル削除を防止 | `find / -perm -1000` | `-perm` の先頭の `-`(マイナス)は「指定したビットが**すべて**立っている」を意味する。`-perm 4000`(マイナスなし)は「パーミッションが完全一致」、`-perm /4000` は「いずれかが立っている」となり挙動が異なる点に注意。 ::: warning 身に覚えのない SUID ファイル(特に `/tmp` や一般ユーザーのホーム配下)は権限昇格の侵入経路になり得る。基準値を `find` で取得し、差分を定期監査する運用が望ましい。 ::: ## 手順 {#steps} ### Step 1: SUID / SGID ファイルを探索する ```bash find / -perm -4000 -type f 2>/dev/null find / -perm -2000 -type f 2>/dev/null find / -perm -u=s -o -perm -g=s -type f 2>/dev/null ``` ```output /usr/bin/passwd /usr/bin/sudo /usr/bin/su /usr/bin/mount /usr/bin/chsh ``` `-perm -4000` は SUID、`-perm -2000` は SGID を持つファイルを抽出する。シンボル表記 `-perm -u=s`(SUID)・`-perm -g=s`(SGID)でも同じ探索ができる。`2>/dev/null` で権限のないディレクトリのエラーを抑制する。 ### Step 2: lsof でオープンファイルとポートを調べる ```bash lsof -i :22 lsof -p 1234 lsof -u alice ``` ```output COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sshd 812 root 3u IPv4 18293 0t0 TCP *:ssh (LISTEN) ``` `lsof -i :22` はポート 22 を使うプロセス、`-p PID` は特定プロセスが開くファイル、`-u user` はユーザーのオープンファイルを一覧する。`-i TCP` や `-i @host` のようにプロトコルやホストでも絞り込める。 ### Step 3: fuser で使用中プロセスを特定・停止する ```bash fuser -v /var/log/syslog fuser -m /mnt/data fuser -k -m /mnt/data ``` ```output USER PID ACCESS COMMAND /var/log/syslog: root 812 F.... rsyslogd ``` `fuser -v file` はファイルを開いているプロセスを ps 風に表示する。`-m` は引数をマウントされたファイルシステムとして扱い、そのファイルシステム上のファイルを使う全プロセスを対象にする。`-k` はそれらに SIGKILL を送る。アンマウント前に「device is busy」を解消する用途で使う。 ::: danger `fuser -k -m` はファイルシステムを使う全プロセスを強制終了する。アンマウントしたい対象を誤ると無関係なプロセスまで巻き込む。実行前に必ず `-k` なしで対象を確認すること。 ::: ### Step 4: ulimit でリソース制限を確認・設定する ```bash ulimit -a ulimit -Sn ulimit -Hn ulimit -n 2048 ``` ```output open files (-n) 1024 max user processes (-u) 7860 ... ``` `ulimit -a` で全制限を一覧表示。`-n` はオープンできるファイル数、`-u` はユーザーのプロセス数。`-S` がソフトリミット(現在値)、`-H` がハードリミット(上限)。一般ユーザーはソフトをハード以下に変更できるが、ハードを引き上げられるのは root のみ。 恒久的に設定するには `/etc/security/limits.conf` を編集する。 ```bash # /etc/security/limits.conf # alice soft nofile 4096 alice hard nofile 8192 @developers hard nproc 100 ``` `type` に `soft` / `hard`、`item` に `nofile`(ファイル数)・`nproc`(プロセス数)等を指定。`@グループ名` でグループ単位の制限も可能。 ### Step 5: visudo で sudo 権限を設定する ```bash visudo ``` ```output # /etc/sudoers alice ALL=(ALL:ALL) ALL %admin ALL=(ALL) ALL %developers ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx ``` `visudo` は `/etc/sudoers` を文法チェック付きで編集する専用コマンド。`%グループ名` でグループに権限を付与、`NOPASSWD:` でパスワード入力を省略できる。設定後は対象ユーザーで `sudo -l` で確認する。 ```bash sudo -l ``` ```output User alice may run the following commands on host: (ALL : ALL) ALL ``` ::: danger `/etc/sudoers` を `vi` 等で直接編集すると、文法エラーがあっても保存できてしまい sudo 全体が機能停止する(自分も root になれなくなる)。必ず `visudo` を使うこと。`visudo` は保存時に文法を検証し、エラーがあれば再編集を促す。 ::: ## su と su - の違い、ログイン監査 {#why} `su` と `su -` の差は **環境変数とカレントディレクトリの引き継ぎ** にある。`su user`(オプションなし)は元ユーザーの環境変数の多くを保持したまま切り替えるが、`su - user`(ハイフン付き、`su -l` と同義)はログインシェルを起動し、対象ユーザーの `PATH`・`HOME`・初期化ファイルを完全に読み込む。root に切り替えて作業するときは `su -` が安全で、`PATH` 由来のコマンド誤実行を避けられる。 ログイン状況の監査には次のコマンドを使う。 | コマンド | 表示内容 | データソース | | --------- | ----------------------------------------- | ------------------ | | `who` | 現在ログイン中のユーザー | `/var/run/utmp` | | `w` | ログイン中ユーザー + 実行中プロセス・負荷 | `/var/run/utmp` | | `last` | ログイン履歴(再起動含む) | `/var/log/wtmp` | | `lastlog` | 各ユーザーの最終ログイン日時 | `/var/log/lastlog` | 不審なログインの追跡は `last` と `lastlog` が基本。あわせて `chage -l user` でパスワード有効期限を確認すれば、放置アカウントの棚卸しができる。 ```bash chage -l alice ``` ```output Last password change : May 01, 2026 Password expires : Jul 30, 2026 Account expires : never ``` ネットワーク側の確認では `nmap` でホストの開いているポートをスキャンできる(`nmap localhost` 等)。自組織以外への無断スキャンは不正アクセス行為に該当し得るため、許可された対象のみに使うこと。 ## よくあるミスと対処 {#mistakes} - **`sudoers` を直接編集して破損**: `vi /etc/sudoers` で誤った文法を保存すると sudo が完全に使えなくなる。必ず `visudo` を使う。破損した場合は `pkexec` やレスキューモードで修復する。 - **ソフト/ハードリミットの混同**: 一般ユーザーがハードリミットを超えて `ulimit -n` を上げようとして `Operation not permitted` になる。ハード引き上げは root + `/etc/security/limits.conf`。 - **SUID の危険性を軽視**: 自作スクリプトに安易に SUID を付けると権限昇格の穴になる。シェルスクリプトの SUID は多くの環境で無視されるが、依存コマンドの脆弱性は残る。 - **`su` と `su -` の取り違え**: `su` のみで root 化すると元ユーザーの環境が残り、`PATH` 上の意図しないコマンドを root 実行してしまう恐れ。管理作業は `su -` を使う。 - **`find -perm 4000` と `-perm -4000` の混同**: マイナスなしは「完全一致」のため、`rwsr-xr-x` のような実際の SUID ファイルがヒットしない。監査では先頭マイナス必須。 ## トラブルシューティング {#troubleshooting} ### 症状: アンマウントしようとして「device is busy」 **原因**: そのファイルシステム上のファイルを開いているプロセスが残っている **確認**: ```bash fuser -vm /mnt/data lsof +D /mnt/data ``` **対処**: プロセスを特定して正常終了させる。やむを得ない場合のみ `fuser -k -m /mnt/data` で強制終了するが、対象を必ず事前確認する。 ### 症状: アプリが「Too many open files」で落ちる **原因**: オープンファイル数がソフトリミット(`nofile`)に達している **確認**: ```bash ulimit -Sn lsof -p PID | wc -l ``` **対処**: `ulimit -n` で当該シェルのソフトリミットを引き上げる。恒久対応は `/etc/security/limits.conf` に `nofile` を追記し、再ログインで反映する。 ### 症状: sudo 実行時に毎回パスワードを求められて自動化できない **原因**: 対象ユーザー/コマンドに `NOPASSWD:` が設定されていない **確認**: ```bash sudo -l ``` **対処**: `visudo` で特定コマンドに限定して `NOPASSWD:` を付与する。`ALL` への無条件 `NOPASSWD` はセキュリティ上避け、必要なコマンドのみに絞る。 ## 作業完了チェックリスト {#checklist} - [ ] `find / -perm -4000 -type f` で SUID ファイルを洗い出した - [ ] `lsof -i` でリッスン中のポートを確認した - [ ] `fuser -m` で使用中プロセスを特定できることを確認した - [ ] `ulimit -a` でソフト/ハードリミットの違いを把握した - [ ] `visudo` で sudoers を安全に編集する手順を確認した - [ ] `who` / `w` / `last` でログイン監査ができることを確認した ## まとめ {#summary} | 場面 | コマンド | 目的 | | -------------- | ---------------------------- | -------------------------- | | SUID/SGID 監査 | `find / -perm -4000 -type f` | 権限昇格リスクの洗い出し | | ポート確認 | `lsof -i :PORT` | リッスン中プロセスの特定 | | 使用中プロセス | `fuser -vm /mount` | アンマウント阻害要因の特定 | | リソース制限 | `ulimit -Sn` / `-Hn` | ソフト/ハードの確認・設定 | | sudo 設定 | `visudo` | 文法検証付き sudoers 編集 | | ログイン監査 | `who` / `w` / `last` | ログイン状況・履歴の確認 | セキュリティ管理は「権限の最小化」と「異常の可視化」が両輪。SUID 監査・`sudo` の最小権限・`ulimit` のリソース保護を押さえたら、暗号化やホスト堅牢化と組み合わせて防御を完成させる。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [ユーザーとグループの管理](/articles/lpic/user-group-administration) - [ホストのセキュリティ強化](/articles/lpic/host-security) - [データの暗号化](/articles/lpic/data-encryption) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # シェル環境変数 - export・env・.bashrc・.bash_profile Source: https://penguin-gym-linux.com/articles/lpic/shell-environment ## この記事で達成できること {#intro} - シェル変数と環境変数の違いを子プロセス継承の観点で説明できる - `export` がなぜ必要かを正確に理解できる - ログインシェルと非ログインシェルの起動ファイル読み込み順序を説明できる - `.bashrc` と `.bash_profile` を用途に応じて正しく使い分けられる - 設定変更が反映されない原因を特定できる LPIC-1 主題 105.1「シェル環境のカスタマイズと使用」の中核。環境変数の継承モデルと起動ファイルの読み込み順序が要点。 ## シェル変数と環境変数の判断 {#flow} | 観点 | シェル変数 | 環境変数 | | ------------------ | ---------------------- | --------------------------------- | | 設定方法 | `VAR=value` | `export VAR=value` | | 子プロセスへの継承 | されない | される | | 一覧表示 | `set` | `env` / `printenv` | | 用途 | 現在のシェル内の一時値 | 子プロセスに渡す設定(`PATH` 等) | 子プロセス(実行したコマンド・スクリプト)に値を渡す必要があるなら `export` で環境変数にする。シェル内だけの一時計算ならシェル変数で十分。 ## 起動ファイルの読み込み順序 {#startup} | シェル種別 | 読み込まれるファイル | | -------------------------- | ----------------------------------------------------------------------------- | | ログインシェル | `/etc/profile` → `~/.bash_profile`(なければ `~/.bash_login` → `~/.profile`) | | 非ログイン対話シェル | `/etc/bash.bashrc` → `~/.bashrc` | | 非対話シェル(スクリプト) | `$BASH_ENV` が指すファイル(通常なし) | 慣例として `~/.bash_profile` 内で `~/.bashrc` を `source` し、設定を `~/.bashrc` に集約するのが定石。これを知らないと「SSH ログインでは反映されるが端末を開くと反映されない(またはその逆)」を説明できない。 ## 手順 {#steps} ### Step 1: シェル変数を設定し継承を確認する ```bash MYVAR=hello echo $MYVAR bash -c 'echo "child sees: $MYVAR"' ``` ```output hello child sees: ``` `MYVAR` はシェル変数なので子プロセス(`bash -c`)には継承されず空になる。これが環境変数との決定的な違い。 ### Step 2: export で環境変数に昇格する ```bash export MYVAR bash -c 'echo "child sees: $MYVAR"' export GREETING=hi env | grep GREETING ``` ```output child sees: hello GREETING=hi ``` `export` でシェル変数を環境変数に昇格すると子プロセスに継承される。`export VAR=value` で設定と昇格を同時に行える。 ### Step 3: 変数一覧を確認する ```bash set | grep MYVAR env | grep MYVAR printenv PATH ``` ```output MYVAR=hello MYVAR=hello /usr/local/bin:/usr/bin:/bin ``` `set` はシェル変数を含む全変数、`env`(`printenv`)は環境変数のみを表示する。両方に出るなら export 済み、`set` にだけ出るならシェル変数のまま。 ### Step 4: 起動ファイルを編集して反映する ```bash echo 'export EDITOR=vim' >> ~/.bashrc echo "alias ll='ls -alF'" >> ~/.bashrc source ~/.bashrc echo $EDITOR ``` ```output vim ``` `.bashrc` への追記は新規シェルから有効になる。現在のシェルに即座に反映するには `source ~/.bashrc`(`. ~/.bashrc` と等価)を実行する。 ### Step 5: 変数を削除する ```bash unset MYVAR echo "[$MYVAR]" unalias ll ``` ```output [] ``` `unset` は変数を削除する。エイリアスは `unalias` で解除する。一時的に環境変数なしでコマンドを実行するには `env -u VAR command` も使える。 ## なぜ export が必要なのか {#why} Linux のプロセスは `fork` + `exec` で子プロセスを生成する。このとき親プロセスの「環境(environment)」だけが子にコピーされ、シェル変数は子のメモリ空間に渡されない。`export` は変数に「環境としてエクスポートする」属性を付与する操作であり、これにより `exec` 時に子プロセスの環境に含まれるようになる。 `PATH` や `LANG` が子プロセスでも有効なのは、それらがログイン時に `export` 済みの環境変数だから。逆に `export` していないシェル変数をスクリプトから参照できないのは、スクリプトが子プロセスとして起動され環境を継承するこのモデルが原因。継承は親→子の一方向で、子で変更した環境変数が親に戻ることはない。 ## トラブルシューティング {#troubleshooting} ### 症状: .bashrc の変更が SSH ログインで反映されない **原因**: SSH ログインはログインシェルで、`~/.bashrc` ではなく `~/.bash_profile` が読まれる **確認**: ```bash grep bashrc ~/.bash_profile ``` **対処**: `~/.bash_profile` に `[ -f ~/.bashrc ] && . ~/.bashrc` を追加し、設定を `~/.bashrc` に集約する。 ### 症状: スクリプトから変数が見えない **原因**: シェル変数のまま export していない **確認**: ```bash export -p | grep VAR ``` **対処**: 子プロセス(スクリプト)に渡す変数は `export VAR` で環境変数に昇格する。 ### 症状: source せず変数が反映されない **原因**: 起動ファイルへの追記は新規シェルからしか有効にならない **確認**: ```bash echo $EDITOR ``` **対処**: 現在のシェルに即時反映するには `source ~/.bashrc` を実行する。新しい端末を開いても反映される。 ## 作業完了チェックリスト {#checklist} - [ ] シェル変数が子プロセスに継承されないことを確認した - [ ] `export` 後に子プロセスへ継承されることを確認した - [ ] `set` と `env` の表示差を確認した - [ ] `.bashrc` を編集し `source` で即時反映した - [ ] `unset` で変数を削除した ## まとめ {#summary} | 場面 | コマンド | 目的 | | ------------ | ------------------ | ---------------------- | | シェル変数 | `VAR=value` | 現在のシェル内の一時値 | | 環境変数昇格 | `export VAR` | 子プロセスへ継承 | | 一覧(環境) | `env` / `printenv` | 環境変数の確認 | | 一覧(全体) | `set` | シェル変数も含む | | 即時反映 | `source ~/.bashrc` | 起動ファイル再読込 | 環境変数モデルはプロセス継承の理解に直結する。次はプロセス優先度の制御に進むと運用知識が連結する。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全12記事と出題範囲マップ)](/lpic1) - [コマンドライン基礎](/articles/lpic/command-line-basics) - [プロセス優先度の制御](/articles/lpic/process-priorities-nice) - [ps・top・killの使い方](/articles/tutorials/process-management-basics) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # シェルスクリプト入門 - 変数/条件分岐/ループ/test【LPIC-1 105.2】 Source: https://penguin-gym-linux.com/articles/lpic/shell-scripting-basics ## この記事で達成できること {#intro} - シバン(`#!/bin/bash`)の役割を説明し、スクリプトを実行可能にできる - 変数・位置パラメータ・特殊変数・コマンド置換を正しく使える - `test` / `[ ]` / `[[ ]]` で文字列・数値・ファイルを判定できる - `if` / `case` / `for` / `while` / `until` で制御構造を書ける - 終了ステータスと `exit`・`&&`・`||`、シェル関数を使い分けられる LPIC-1 主題 105.2「簡単なスクリプトをカスタマイズ・記述する」(LPIC102)の中核。日々の運用を自動化する最小単位の技術。 ## シェルスクリプトの基本構造とは {#flow} シェルスクリプトは「シバン → 変数定義 → 処理本体」で構成する。先頭行のシバンが使用するインタプリタを決め、`chmod +x` で実行権限を与えると `./script.sh` で起動できる。 | 要素 | 記法 | 役割 | | ------------ | ------------------- | ------------------------------------ | | シバン | `#!/bin/bash` | 実行するインタプリタを指定 | | 変数 | `name=value` | 値の保持(`=` の前後にスペース不可) | | 参照 | `$name` / `${name}` | 変数の値を展開 | | コマンド置換 | `$(command)` | コマンド出力を値として取得 | | 終了 | `exit N` | 終了ステータス N で終了 | シバンはスクリプト最初の 2 バイトが `#!` の場合にカーネルが解釈する。`#!/bin/bash` なら bash、`#!/bin/sh` なら POSIX シェルで実行される。man bash の「INVOCATION」が定義する標準的な起動方式。 ::: tip シバンは必ず**1 行目の先頭**に置く。前に空行やスペースがあると通常のコメント扱いとなり無効になる。 ::: ## 手順 {#steps} ### Step 1: シバンを書いて実行可能にする ```bash cat > hello.sh <<'EOF' #!/bin/bash echo "Hello, LPIC" EOF chmod +x hello.sh ./hello.sh ``` ```output Hello, LPIC ``` 1 行目の `#!/bin/bash` がインタプリタ指定。`chmod +x` で実行権限を付与し、`./hello.sh` で起動する。`bash hello.sh` のように明示的にインタプリタへ渡す場合は実行権限もシバンも不要だが、運用上は両方つけておくのが定石。 ### Step 2: 変数とクォートを使う ```bash #!/bin/bash name="Penguin Gym" count=3 echo "${name} : ${count}" echo "${name}_log" ``` ```output Penguin Gym : 3 Penguin Gym_log ``` 代入は `name="値"` の形で、`=` の前後にスペースを入れてはならない。参照時は `${name}` のように波括弧で囲むと、`${name}_log` のように直後に文字が続く場合でも変数名の境界が明確になる。値にスペースを含むため `"..."` で囲んでいる点に注意。 ### Step 3: 位置パラメータと特殊変数を読む ```bash #!/bin/bash echo "スクリプト名: $0" echo "第1引数: $1" echo "引数の数: $#" echo "全引数: $@" ``` ```output スクリプト名: ./args.sh 第1引数: alpha 引数の数: 2 全引数: alpha beta ``` `$0` はスクリプト名、`$1`〜`$9` は順に引数。`$#` は引数の個数、`$@` と `$*` は全引数を表す。`"$@"` は各引数を個別の文字列として、`"$*"` は全体を 1 つの文字列として展開する点が異なる(man bash「Special Parameters」)。引数をループ処理するなら `"$@"` を使う。 | 変数 | 意味 | | ------------ | ----------------------------------------- | | `$0` | スクリプト名 | | `$1` 〜 `$9` | 位置パラメータ(n 番目の引数) | | `$#` | 引数の個数 | | `$@` / `$*` | 全引数 | | `$?` | 直前のコマンドの終了ステータス | | `$$` | 現在のシェルのプロセス ID | | `$!` | 直近にバックグラウンド実行したプロセス ID | ### Step 4: コマンド置換で出力を変数に取り込む ```bash #!/bin/bash today=$(date +%F) files=$(ls /etc | wc -l) echo "${today} : /etc に ${files} 項目" ``` ```output 2026-05-30 : /etc に 152 項目 ``` `$(command)` はコマンドの標準出力を文字列として展開する。これがコマンド置換。古い `` `command` ``(バッククォート)記法と等価だが、ネストや可読性の点で `$(...)` が推奨される(man bash「Command Substitution」)。 ### Step 5: test / [ ] / [[]] で条件を判定する ```bash #!/bin/bash a="abc"; n=5 [ "$a" = "abc" ] && echo "文字列一致" [ "$n" -gt 3 ] && echo "数値: 3より大きい" [ -f /etc/passwd ] && echo "ファイルが存在する" ``` ```output 文字列一致 数値: 3より大きい ファイルが存在する ``` `[ ... ]` は `test` コマンドの別名で、**`[` の直後と `]` の直前には必ずスペースが必要**。文字列比較は `=`・`!=`、数値比較は `-eq`・`-ne`・`-lt`・`-gt`、ファイルテストは `-f`(通常ファイル)・`-d`(ディレクトリ)・`-e`(存在)・`-r`・`-w`・`-x`(読み・書き・実行権限)を使う(man test)。bash 拡張の `[[ ... ]]` は単語分割やパス名展開を抑止し、`&&`・`||` や `<`・`>` をそのまま書けるため、bash 限定なら扱いやすい。 | 種類 | 演算子 | 例 | | -------- | ----------------------------------- | ----------------- | | 文字列 | `=` / `!=` | `[ "$a" = "$b" ]` | | 数値 | `-eq` `-ne` `-lt` `-gt` `-le` `-ge` | `[ "$n" -eq 0 ]` | | ファイル | `-f` `-d` `-e` `-r` `-w` `-x` | `[ -d /tmp ]` | ::: warning `[ ]` の中で数値比較に `>` や `<` を使うと「リダイレクト」や「文字列の辞書順比較」と解釈され、意図しない結果になる。数値比較は必ず `-gt` / `-lt` などを使う。 ::: ### Step 6: if / case で分岐する ```bash #!/bin/bash score=$1 if [ "$score" -ge 80 ]; then echo "合格" elif [ "$score" -ge 60 ]; then echo "再評価" else echo "不合格" fi ``` ```output 合格 ``` `if 条件; then ... elif 条件; then ... else ... fi` が基本形。`if` は条件コマンドの**終了ステータスが 0(真)**かどうかで分岐する点が重要。値の集合で分けるなら `case` が読みやすい。 ```bash #!/bin/bash case "$1" in start) echo "開始" ;; stop) echo "停止" ;; *) echo "使い方: $0 {start|stop}" ;; esac ``` ```output 開始 ``` `case 値 in パターン) 処理 ;; esac` の形。各分岐は `;;` で終え、`*)` が「いずれにも該当しない場合」を受ける。サービス起動スクリプトの定番パターン。 ### Step 7: for / while / until で繰り返す ```bash #!/bin/bash for f in *.log; do echo "処理中: $f" done n=1 while [ "$n" -le 3 ]; do echo "回数: $n" n=$((n + 1)) done ``` ```output 処理中: access.log 処理中: error.log 回数: 1 回数: 2 回数: 3 ``` `for 変数 in 値リスト; do ... done` はリストを順に処理する。`while 条件; do ... done` は条件が真の間繰り返し、`until 条件; do ... done` は逆に条件が真になるまで繰り返す。`$((...))` は算術展開で、整数計算に使う(man bash「Arithmetic Expansion」)。 ### Step 8: read で入力を受け取り exit で終了する ```bash #!/bin/bash read -p "名前を入力: " who if [ -z "$who" ]; then echo "名前が空です" >&2 exit 1 fi echo "ようこそ ${who}" exit 0 ``` ```output 名前を入力: rina ようこそ rina ``` `read 変数名` は標準入力を 1 行読み取り変数へ格納する(`-p` でプロンプト表示)。`exit N` は終了ステータス N でスクリプトを終了する。慣習として **0 が成功、1 以上が失敗**。`[ -z "$who" ]` は文字列が空かどうかの判定。 ## 終了ステータスと && / || はどう使うか {#exit-status} 直前のコマンドの終了ステータスは `$?` に入る。0 が成功、非 0 が失敗。この値を使って `&&`(前が成功したら次を実行)と `||`(前が失敗したら次を実行)でコマンドを連結できる。 ```bash mkdir -p /tmp/work && echo "作成成功" || echo "作成失敗" echo "$?" ``` ```output 作成成功 0 ``` `A && B` は A の終了ステータスが 0 のときだけ B を実行する。`A || B` は A が非 0 のときだけ B を実行する。`if` の判定もこの終了ステータスに基づいており、「真 = 終了ステータス 0」というシェル独自の真偽の定義を理解すると、条件分岐の挙動が一貫して見える。 シェル関数で処理をまとめることもできる。関数内の `return N` は関数の終了ステータスを、`exit N` はスクリプト全体を終了させる。 ```bash #!/bin/bash greet() { echo "Hello, $1" return 0 } greet "world" echo "関数の戻り値: $?" ``` ```output Hello, world 関数の戻り値: 0 ``` 関数は `名前() { ... }` で定義し、`名前 引数` で呼び出す。引数は関数内でも `$1`・`$2`・`$#` で参照でき、これは位置パラメータが呼び出しごとに切り替わるため。`return` 省略時は関数内で最後に実行したコマンドの終了ステータスが返る。 ## よくあるミスと対処 {#mistakes} | ミス | 症状 | 正しい書き方 | | --------------------------- | ---------------------------------- | -------------------------------------------------- | | 代入の `=` 前後にスペース | `var: command not found` | `var=value`(スペース無し) | | クォート不足 | スペースを含む値で引数が分割される | `[ "$var" = "x" ]` のように二重引用符で囲む | | `[ ]` のスペース欠落 | `[: missing ']'` | `[ "$a" = "$b" ]`(`[` 直後と `]` 直前にスペース) | | 数値比較に `=` / `>` を使用 | 文字列比較やリダイレクトと誤認 | 数値は `-eq` / `-gt` などを使う | | 終了ステータスの取り違え | `if` が逆に動く | 真は「終了ステータス 0」と理解する | 特に多いのが代入時のスペースだ。`var = value` と書くと、シェルは `var` を**コマンド名**、`=` と `value` を**引数**と解釈して実行しようとし、`command not found` になる。 ## トラブルシューティング {#troubleshooting} ### 症状: スクリプトが「Permission denied」で起動しない **原因**: 実行権限がない、またはシバンが無効 **確認**: ```bash ls -l script.sh head -n 1 script.sh ``` **対処**: `chmod +x script.sh` で実行権限を付与する。シバンが 1 行目にあるか、前に空行が無いかも確認する。応急的には `bash script.sh` で直接実行できる。 ### 症状: 変数代入で「command not found」になる **原因**: `=` の前後にスペースが入っている **確認**: ```bash bash -n script.sh ``` **対処**: `var=value` のようにスペースを除く。`bash -n` は構文チェック(実行せず文法のみ検査)に使える。 ### 症状: 値にスペースが含まれると条件分岐が壊れる **原因**: 変数参照をクォートしておらず、単語分割が起きている **確認**: ```bash bash -x script.sh ``` **対処**: `[ "$var" = "値" ]` のように変数を二重引用符で囲む。`bash -x` は各コマンドの展開結果を表示するため、分割の発生箇所を特定できる。 ## 作業完了チェックリスト {#checklist} - [ ] 1 行目にシバン(`#!/bin/bash`)を書いた - [ ] `chmod +x` で実行権限を付与した - [ ] 変数代入の `=` 前後にスペースが無いことを確認した - [ ] 変数参照を `"$var"` でクォートした - [ ] `[ ]` 内のスペースと比較演算子(文字列・数値)を確認した - [ ] `exit 0` / `exit 1` で終了ステータスを返した ## まとめ {#summary} | 場面 | 記法 | 目的 | | ---------- | ----------------------- | ---------------------- | | 先頭行 | `#!/bin/bash` | インタプリタ指定 | | 値の取得 | `$(command)` | コマンド出力を変数化 | | 文字列判定 | `[ "$a" = "$b" ]` | 一致 / 不一致 | | 数値判定 | `[ "$n" -gt 3 ]` | 大小比較 | | 分岐 | `if`/`case` | 条件・値による分岐 | | 繰り返し | `for`/`while`/`until` | ループ処理 | | 連結 | `A && B` / `A \|\| B` | 終了ステータスでの連結 | シェルスクリプトは Linux 運用自動化の基礎。`105.2` を押さえたら、環境変数(`105.1`)やテキスト処理コマンド・正規表現と組み合わせると、実務的な自動化スクリプトが書けるようになる。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [シェル環境変数の設定](/articles/lpic/shell-environment) - [コマンドライン操作の基本](/articles/lpic/command-line-basics) - [テキストストリーム処理コマンド](/articles/lpic/text-stream-filters) - [正規表現の基本](/articles/lpic/regular-expressions) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # LPIC-1 参考書比較ガイド - あずき本・スピードマスター・白本の選び方 Source: https://penguin-gym-linux.com/articles/lpic/study-materials-comparison ## この記事で達成できること {#intro} - あずき本・スピードマスター問題集・白本(3 候補)の特徴を公式書誌情報で正確に把握できる - 「白本」と呼ばれる書籍が複数存在する理由と、候補ごとの違いを理解できる - Ping-t と Udemy の位置づけを書籍と比較して整理できる - 自分の学習スタイル・予算・残り期間に合った教材の組み合わせを選択できる - 本記事は「教材選び」に特化する(資格制度・難易度・合格点は別記事で解説) LPIC-1 の教材選びに正解の組み合わせはない。ただし、書籍の特性を誤解して購入すると時間と費用を無駄にする。本記事は公式書誌情報のみを根拠に、選択の判断基準を提示する。 ## 教材選びの判断フロー {#flow} まず自分の状況を 3 つの軸で確認してから教材を選ぶ。 | 確認軸 | 選択肢 | 推奨方向 | | ------------------ | --------------------------------- | ---------------------------------------------------- | | Linux 実務経験 | あり(半年以上)/ なし | あり → 問題集から着手。なし → 参考書から体系的に学ぶ | | 確保できる学習時間 | 1 日 2 時間未満 / 2 時間以上 | 少ない → 書籍 1 冊に絞る。多い → 書籍+問題集の 2 本 | | 残り期間 | 1 か月未満 / 1〜2 か月 / 2 か月超 | 短期 → 問題集+Ping-t 特化。長期 → 書籍で体系習得 | 購入前に「試験バージョン v5.0 対応か」を必ず確認する。v5.0 非対応書籍は出題範囲がずれる場合があるため注意が必要だ。 ## 教材比較一覧 {#comparison-table} 主要 6 教材の基本情報と特性をまとめた。詳細は各セクションで解説する。 | 教材 | 種別 | 出版社 / 提供元 | 価格(税込) | v5.0 対応 | 特徴 | | ------------------------------- | -------------- | --------------- | ------------------ | ---------------- | --------------------------------------- | | あずき本 | 参考書 | 翔泳社 | 4,180 円 | あり | 体系的インプット、解説厚め | | スピードマスター問題集 | 問題集 | 翔泳社 | 2,750 円 | あり | 演習特化、問題量多め | | 白本 候補 A(徹底攻略 LPIC-1) | 参考書+問題集 | インプレス | 4,620 円 | あり | 教科書+問題集の 1 冊完結型 | | 白本 候補 B(Linux 標準教科書) | 入門書 | LPI-Japan | 無料(PDF/EPUB) | 直接対応明記なし | LinuC 向け入門書、LPIC 入門として参考可 | | Ping-t | Web 問題集 | Ping-t | 2,640 円〜(月額) | あり | 問題数多、分野別演習強み | | Udemy | 動画講座 | 各講師 | 変動(セール多用) | コース依存 | 視覚・聴覚学習、入門者向け | (各価格・情報は 2026-05-22 時点の公式情報に基づく。価格は変動する場合があるため、購入前に各公式サイトで最新価格を確認すること) ## あずき本(Linux 教科書 LPIC レベル 1) {#azuki} ### 書誌情報 | 項目 | 内容 | | ------ | ------------------------------------------------ | | 書名 | Linux 教科書 LPIC レベル 1(Version 5.0 対応版) | | 著者 | 中島能和(監修: 濱野賢一朗) | | 出版社 | 翔泳社 | | 発行日 | 2019/04/08 | | ISBN | 9784798160498 | | 定価 | 4,180 円(本体 3,800 円+税 10%) | | 対応版 | LPIC Level 1 Version 5.0 | (出典: [翔泳社公式書誌](https://www.shoeisha.co.jp/book/detail/9784798160498)、2026-05-22 時点) ### 特徴と位置づけ 「あずき本」は LPIC-1 学習者のあいだで広く使われてきた参考書。正式名称は「Linux 教科書 LPIC レベル 1(Version 5.0 対応版)」で、翔泳社から刊行されている。表紙の色に由来した通称だが、版によって表紙デザインが変わっているため、旧版を指して「あずき本」と呼ぶ場合がある(後述の白本ゆらぎとも関係する)。 本書の主な特徴は体系的な解説の厚みにある。コマンドの動作原理・設定ファイルの意味・オプションの使い分けが丁寧に説明されており、Linux の基礎からじっくり学びたい層に向いている。 **向いている学習者**: - Linux 実務経験がなく、コマンドの意味から理解したい - 読み込み型で体系的に理解を積み上げたいスタイル - 問題演習前のインプット基盤として使いたい **注意点**: 問題演習は別途スピードマスター問題集や Ping-t と組み合わせるのが一般的な使い方だ。 ## スピードマスター問題集 {#speed-master} ### 書誌情報 | 項目 | 内容 | | ------ | ------------------------------------------------------------------ | | 書名 | Linux 教科書 LPIC レベル 1 スピードマスター問題集 Version 5.0 対応 | | 著者 | 山本道子・大竹龍史 | | 出版社 | 翔泳社 | | 発行日 | 2019/09/11 | | ISBN | 9784798160856 | | 定価 | 2,750 円(本体 2,500 円+税 10%) | | 対応版 | LPIC Level 1 Version 5.0 | (出典: [翔泳社公式書誌](https://www.shoeisha.co.jp/book/detail/9784798160856)、2026-05-22 時点) ### 特徴と位置づけ 「スピードマスター問題集」は翔泳社から刊行された問題集特化型の書籍。参考書(あずき本)と同シリーズで、演習量を増やしたい学習者が参考書と併用するケースが多い。 本書の主な特徴は問題演習への特化にある。解説はコンパクトにまとめられており、問題を解きながら知識を固めるスタイルに向いている。実務経験があってすでに Linux の基礎知識を持っている学習者が、試験形式に慣れる目的で使うのに適している。 **向いている学習者**: - Linux 実務経験があり、知識の棚卸し・試験形式慣れが目的 - あずき本や別の参考書でインプットを済ませた後の演習フェーズ - 書籍ベースの問題演習を Ping-t と並行したい **注意点**: 問題集特化のため、コマンドの原理から学びたい層には参考書との組み合わせが必要だ。 ## 「白本」とは何か - 命名ゆらぎの整理 {#shiro} ### なぜ「白本」は一冊に特定できないか 「白本」という通称は、複数の文脈で異なる書籍を指して使われてきた。Web 上の情報を見ると、以下の 3 候補が「白本」と呼ばれるケースが存在する。購入前に「自分が参照している情報の『白本』はどれか」を確認する必要がある。 ::: warning 「白本で勉強した」という情報を見たとき、どの書籍を指しているかは文脈によって異なる。3 候補を混同して購入判断しないよう注意。 ::: ### 候補 A: 徹底攻略 LPIC Level 1 教科書&問題集(インプレス) | 項目 | 内容 | | ------ | ---------------------------------------------------------- | | 書名 | 徹底攻略 LPIC Level 1 教科書&問題集(Version 5.0 対応版) | | 著者 | 橋本明子 | | 出版社 | インプレス | | 発行日 | 2023/10/24 | | ISBN | 9784295017974 | | 定価 | 4,620 円(本体 4,200 円+税 10%) | | 対応版 | LPIC-1 Version 5.0 | (出典: [インプレス公式書誌](https://book.impress.co.jp/books/1120101147)、2026-05-22 時点) 現行版として入手性が高く、教科書と問題集が 1 冊にまとまった構成が特徴。3 候補の中では最も新しい版(2023 年発行)で、v5.0 対応を明記している。「白本」と言われた場合に現行の文脈で最も参照されやすい候補だ。 **向いている学習者**: - 書籍 1 冊で参考書と問題集の両方をカバーしたい - あずき本+スピードマスターの 2 冊購入を避けたい - 比較的新しい版(2023 年)の情報で学びたい ### 候補 B: Linux 標準教科書(LPI-Japan 提供) | 項目 | 内容 | | ---------- | --------------------------------------------------- | | 書名 | Linux 標準教科書 Ver. 4.0.1 | | 提供元 | LPI-Japan | | 発行日 | 2026/02/18 | | 公式 URL | https://linuc.org/textbooks/linux/ | | ライセンス | CC BY-NC-ND 4.0 | | 価格 | PDF / EPUB は無料、製本版は別途有料 | | 対応試験 | LinuC レベル 1 向け(LPIC v5.0 直接対応の明記なし) | (出典: [LPI-Japan 公式](https://linuc.org/textbooks/linux/)、2026-05-22 時点) LPI-Japan が無料公開している入門書。PDF/EPUB を公式サイトから直接ダウンロードできる。ただし対応試験は LinuC レベル 1 向けと明記されており、LPIC v5.0 への直接対応は公式には記載されていない。LPIC-1 の入門導入として参考にする分には有用だが、試験範囲の完全な対応を前提に使うのは適切でない。 **向いている学習者**: - Linux の基礎概念をコストなしで確認したい - LinuC レベル 1 を受験予定 - LPIC-1 の入門段階として概念把握に活用したい(試験特化ではない位置づけとして) ::: warning 本書の対応試験は LinuC レベル 1。LPIC-1 v5.0 の試験範囲と重複する部分はあるが、完全一致ではない。LPIC-1 受験を目的とする場合は、本書のみでの試験対策は不完全になる可能性がある。 ::: ### 候補 C: 旧版あずき本(表紙色ゆらぎ) 「白本」という呼称の 3 つ目の由来は、翔泳社「Linux 教科書 LPIC レベル 1」の旧版(第 5 版以前)を指すケースだ。版によって表紙のデザイン・色が異なるため、旧版を「白本」「黒本」と通称していた時期がある。現行版(Version 5.0 対応版)を購入するなら候補 A・B と混同しないよう注意する。 **整理**: 旧版購入は避けること。試験バージョン v5.0 対応の現行版を選ぶのが基本だ。 ## オンライン教材 {#online} ### Ping-t Ping-t は Web ブラウザで利用できる問題演習サービス。LPIC を含む複数の IT 資格に対応している。 | 項目 | 内容 | | ------------ | ---------------------------------------------------------------- | | 公式 URL | https://mondai.ping-t.com/g/premium_plans | | 無料利用 | 一部問題のみ公開 | | 1 か月 | 2,640 円(買い切り、自動更新なし) | | 3 か月 | 3,960 円(買い切り) | | 6 か月 | 4,950 円(買い切り) | | 1 年 | 6,930 円(買い切り) | | 主な収録内容 | LPIC Lv1-102 (v5.0) 655 問等、AI Assistant トークン(20 倍)付き | (出典: [Ping-t 公式料金ページ](https://mondai.ping-t.com/g/premium_plans)、2026-05-22 時点。価格は変動する可能性がある) Ping-t の主な強みは問題数の多さと分野別演習機能にある。書籍の問題集と比べて問題数が多く、弱点分野を繰り返し演習するのに向いている。書籍でインプットを済ませた後の演習フェーズで使うのが一般的な位置づけだ。 **向いている学習者**: - 書籍でのインプット後に問題演習量を増やしたい - 分野別に弱点を特定して集中的に演習したい - 移動時間など隙間時間にブラウザで学習したい ### Udemy Udemy は動画講座プラットフォームで、LPIC-1 関連コースが複数公開されている。 | 項目 | 内容 | | -------------- | ---------------------------------------------------------------------------------------- | | Linux トピック | https://www.udemy.com/topic/linux/ | | 価格 | コースごとに変動、セール多用のため公式で最新価格を確認 | | コース選択基準 | 受講時間 / 評価スコア / レビュー数 / 最終更新日(v5.0 試験範囲対応かは購入前に必ず確認) | (出典: [Udemy 公式サイト Linux トピック](https://www.udemy.com/topic/linux/)、2026-05-22 時点(LPIC トピックは Linux トピックへ統合された)。コース受講時間・評価値はコースおよび時点により変動するため、個別コースの数値は購入前に該当コースページで確認すること) 動画による視覚・聴覚での学習が特徴。入門者が「読んでも理解しにくい」概念を映像で掴むのに向いている。ただしコース品質は講師によって異なるため、レビュー・評価を確認してから購入する。 **向いている学習者**: - Linux 完全入門で「読む」より「見て聞く」スタイルが合う - 日本語書籍の文体が苦手で動画のほうが理解しやすい - 英語コースに抵抗がなく、豊富な実演を見たい **向かない学習者**: - Linux 実務経験があり、知識の棚卸しと試験形式慣れが目的 - 試験直前で問題演習だけに集中したい - 動画より書籍で体系的に読み進めるほうが定着する ::: warning 動画は概念把握の入口として有効だが、LPIC-1 の問題演習は別途必要。動画のみで試験対策を完結させるのは難しい。書籍または問題集との組み合わせを前提にする。 ::: ## 教材の組み合わせパターン {#combination} 学習スタイル・予算・期間別に推奨する組み合わせを示す。 | パターン | 構成 | 向いている状況 | | ---------- | -------------------------------- | -------------------------------------------- | | 定番 2 冊 | あずき本+スピードマスター問題集 | 書籍でしっかり学びたい、書店購入派 | | 1 冊完結 | 白本 候補 A(徹底攻略 LPIC-1) | 書籍 1 冊でインプット+演習を完結させたい | | 書籍+Web | あずき本 or 白本 A + Ping-t | 書籍インプット後に問題数を増やしたい | | 短期集中 | スピードマスター問題集+Ping-t | 実務経験あり・残り期間 1 か月未満 | | 入門+演習 | Linux 標準教科書(無料)+Ping-t | コストを抑えて入門から学ぶ(LinuC 向け兼用) | | 動画+書籍 | Udemy+あずき本 or 白本 A | 完全入門者・動画で概念を掴んでから読書 | **選択の基本方針**: 1. 「試験バージョン v5.0 対応」を明記している教材を選ぶ 2. インプット(参考書・動画)と演習(問題集・Ping-t)のバランスを確保する 3. 期間が短いほど問題演習に偏重する 4. 完全初心者は参考書か動画から始め、理解を固めてから問題演習に移行する ### 完全初心者向けの推奨ルート {#beginner-route} 実務経験がない完全初心者は、次の 4 段階で教材をつなぐと挫折しにくい。表の「動画+書籍」パターンを時系列に展開したルートだ。参考書をそのまま読み進められる場合は手順 1 の動画を省略し、書籍から始めてよい。要否の判断基準は[LPIC-1 対策に Udemy は必要?](/articles/lpic/udemy-for-lpic1)を参照。 1. **最初**: Udemy の動画で Linux 全体像と基本概念を掴む(読んで理解しにくい概念を映像で把握する) 2. **中盤**: あずき本または白本 候補 A で体系的に理解を積み上げる 3. **直前**: Ping-t / スピードマスター問題集で分野別に演習する 4. **仕上げ**: [LPIC-1 仮想ターミナル演習](/terminal?category=lpic)と [LPIC-1 学習ハブ](/lpic1)の問題でコマンド実操作と理解度を確認する この構成では動画は書籍の競合ではなく、書籍・問題演習に入る前の入口教材として機能する。いきなり参考書を読み始めて挫折するより、映像で全体像を掴んでから読むほうが定着しやすい。 ## 教材選びの注意点 {#pitfalls} ### 旧版・非対応版を買わないこと LPIC-1 の試験バージョンは v5.0 が現行(2026-05-22 時点)。v4.0 以前の書籍は出題範囲が異なるため購入しない。中古本・フリマアプリでの購入時は特に注意が必要だ。 ### 「白本」の混同を避けること 前述のとおり、「白本」は複数の書籍を指す場合がある。Web の合格体験記や学習ブログで「白本を使った」という情報を見たとき、候補 A・B・C のどれを指しているかは文脈から判断する。書名・出版社・ISBN を確認してから購入するのが確実だ。 ### 価格情報は公式で確認すること 本記事の価格情報は 2026-05-22 時点のもの。書籍定価・Ping-t 料金・Udemy 価格はいずれも変動する可能性がある。購入前に各公式サイトで最新価格を確認すること。 ### アフィリエイトリンクを経由したレビューに注意 教材比較記事の中には、アフィリエイト報酬が発生するリンクを含むものがある。報酬構造が評価の客観性に影響する場合があるため、リンク経由のレビューは鵜呑みにせず、公式の書誌情報・価格・対応版・学習用途を自分で確認して判断すること。本記事では動画教材を「完全入門者が概念を掴む補助教材」として位置づけている。 ## 教材選択チェックリスト {#checklist} 教材を購入する前に以下を確認する。 - [ ] 試験バージョン v5.0 に対応していることを書名または帯で確認した - [ ] 書名・出版社・ISBN を確認し、「白本」などの通称のみで判断していない - [ ] インプット教材と演習教材の両方を確保している(どちらか一方のみで完結させようとしていない) - [ ] 価格を購入直前に公式サイトで確認した - [ ] 教材数を絞り込んだ(3 冊以上購入して消化不良になるリスクを認識している) - [ ] Udemy を選ぶ場合、講座のレビュー数・評価・最終更新日を確認した ## まとめ {#summary} | 教材 | 最適な使い方 | 対応版 | | ------------------------------- | ------------------------------------ | ----------------------------------- | | あずき本 | 体系的インプット / 入門〜中盤 | v5.0 対応 | | スピードマスター問題集 | 演習フェーズ / あずき本との併用 | v5.0 対応 | | 白本 候補 A(徹底攻略) | 1 冊完結 / 書籍でインプット+演習 | v5.0 対応 | | 白本 候補 B(Linux 標準教科書) | 入門概念把握 / 無料で試したい層 | LinuC 向け(LPIC 直接対応明記なし) | | Ping-t | 演習特化 / 問題数強化 / 隙間時間活用 | v5.0 対応 | | Udemy | 動画入門 / 概念を映像で掴む | コース依存 | (各情報は 2026-05-22 時点の公式情報に基づく) **教材選びの結論**: - 書籍派 → あずき本+スピードマスター問題集、または白本 候補 A の 1 冊完結 - コスト優先 → Linux 標準教科書(無料)+Ping-t(1 か月) - 完全入門者 → 概念理解から入る。参考書を読み進められるなら書籍のみで可、難しければ Udemy 動画を入口にする([要否判断](/articles/lpic/udemy-for-lpic1)) 教材を揃えたら、勉強時間の計画と学習プランの設計に進む。 ## 次に読む {#next} - [LPIC-1 対策に Udemy は必要?書籍・Ping-t との使い分け](/articles/lpic/udemy-for-lpic1) — 動画講座の要否判断と組み込み方 - [LPIC-1 勉強時間・勉強方法・最短攻略ガイド](/articles/lpic/study-time-and-method) — 教材決定後の期間別学習プラン - [LPIC-1 難易度・合格点・配点 完全FAQ](/articles/lpic/difficulty-and-passing-score) — 500 点合格・60 問・90 分の根拠 - [LPIC-1 vs LPIC-2 / Linux+ / Linux Essentials / LinuC 比較](/articles/lpic/lpic1-comparison) — 受験する資格を迷っている層向け - [LPIC-1 仮想ターミナル演習](/terminal?category=lpic) — ブラウザ完結のコマンド実操作 --- 本記事の書誌情報・価格・公式情報は **2026-05-22** 時点で各公式サイトから取得。書籍価格・料金プランは変動する可能性があるため、購入前に各公式サイトで最新情報を確認すること。 - 翔泳社(あずき本・スピードマスター問題集): [https://www.shoeisha.co.jp/](https://www.shoeisha.co.jp/) - インプレス(白本 候補 A): [https://book.impress.co.jp/](https://book.impress.co.jp/) - LPI-Japan(Linux 標準教科書): [https://linuc.org/textbooks/linux/](https://linuc.org/textbooks/linux/) - Ping-t: [https://mondai.ping-t.com/g/premium_plans](https://mondai.ping-t.com/g/premium_plans) - Udemy Linux トピック: [https://www.udemy.com/topic/linux/](https://www.udemy.com/topic/linux/) # LPIC-1 勉強時間・勉強方法・最短攻略ガイド(1 週間〜2 ヶ月) Source: https://penguin-gym-linux.com/articles/lpic/study-time-and-method ## この記事で達成できること {#intro} - 自分の経験レベルと確保できる時間から、最適な勉強期間を判断できる - 1 週間・10 日・2 週間・1 ヶ月・2 ヶ月の期間別プランをそのまま実行に移せる - 書籍・問題集・仮想ターミナルの役割分担を理解し、教材を無駄なく使える - 101 試験と 102 試験の受験順序・同時受験の判断基準を把握できる - 落ちやすいパターンと対処法を事前に知り、1 回で合格する確率を高められる LPIC-1 は「101-500」「102-500」の 2 科目構成。LPI が定める試験バージョン v5.0 に基づき、両科目の合格で資格が認定される。(出典: LPI 公式サイト, 2026-05 時点)。認定有効期限は 5 年間で、有効期限内に上位資格取得または再認定手続きが必要だ。(出典: LPI Japan 公式サイト, 2026-05 時点) 受験料は 1 科目 15,000 円(税別)、LPIC-1 取得には 2 科目合計 30,000 円が目安となる。(出典: LPI Japan 公式サイト, 2026-05 時点)。試験の費用対効果を最大化するためにも、計画的な学習が重要だ。 ## 勉強時間の判断フロー {#flow} LPIC-1 の勉強時間は「Linux 実務経験の有無」と「1 日に確保できる学習時間」の 2 軸で決まる。以下の表は当サイト学習者の観察に基づく目安だ。個人差があるため、あくまで計画立案の出発点として使うこと。 | Linux 実務経験 | 1 日の学習時間 | 2 科目合計目安時間 | 推奨プラン | | -------------------- | -------------- | ------------------ | -------------- | | あり(1 年以上) | 5〜8 時間 | 40〜70 時間 | 1 週間〜10 日 | | あり(半年〜1 年) | 4〜5 時間 | 60〜100 時間 | 10 日〜2 週間 | | あり(〜半年) | 3〜4 時間 | 70〜100 時間 | 2 週間〜1 ヶ月 | | なし(ITエンジニア) | 3〜4 時間 | 100〜140 時間 | 1 ヶ月 | | なし(完全未経験) | 2〜3 時間 | 120〜200 時間 | 2 ヶ月 | (当サイト学習者の観察に基づく目安) ※ 本記事の総時間は **2 科目合計** で記載している。LPIC-1 学習ハブ FAQ の「1 科目あたり 50〜100 時間(未経験)/ 20〜40 時間(実務経験あり)」を 2 倍した値が 2 科目合計の理論ベースで、本表はそのレンジに集中度・演習バッファを加味した目安。試験は 101 / 102 別受験のため、1 科目ごとに時間配分する場合は概ね半分を充てる計算となる。 「1 週間で受かった」という体験記も「2 ヶ月かけた」という体験記もどちらも正しい。経験量と学習密度が異なるだけだ。 **判断の指針**: 1. まず「コマンドライン操作を業務で日常的に使うか」を確認する 2. 次に「平日に安定して学習時間を確保できるか」を確認する 3. 両方 Yes なら 1〜2 週間プランが現実的。片方 No なら 1〜2 ヶ月プランを選ぶ ## 期間別学習プラン {#plans} 各プランは「学習内容」「演習」の 2 列で構成した日次タスク表を示す。試験範囲は LPI 公式の試験範囲(v5.0)に基づいている。(出典: LPI 公式サイト, 2026-05 時点) ### 1 週間プラン **前提**: Linux 実務経験 1 年以上 / 1 日 6〜8 時間確保可能 / 2 科目合計 40〜60 時間 知識の整理と試験対策演習に集中するプラン。コマンドの動作を説明できる状態が前提。短期集中で高負荷のため、有給休暇取得などまとまった時間が確保できる前提が現実的(当サイト学習者の観察に基づく目安)。 | 日 | 学習内容 | 演習 | | --- | -------------------------------------------------- | ---------------------------------------------- | | 1 | 試験範囲の全体把握・出題傾向確認(101/102 両科目) | Ping-t で弱点分野の洗い出し(各 50 問) | | 2 | 101: ファイル管理・パーミッション・リンク | 仮想ターミナルで `chmod` / `chown` / `ln` 操作 | | 3 | 101: シェル操作・正規表現・テキスト処理 | `grep` / `sed` / `awk` 演習 20 問 | | 4 | 102: システム起動・ブートローダー・パッケージ管理 | 模擬問題 50 問(Ping-t) | | 5 | 102: ネットワーク基礎・SSH・セキュリティ | 模擬問題 50 問(Ping-t) | | 6 | 全範囲模擬試験(2 回転)・弱点の集中復習 | 模擬試験 × 2 セット | | 7 | 直前総復習・コマンドオプション確認 | 全範囲総なめ(弱点のみ) | **このプランの注意点**: 1 日でも遅れると全体が崩れる。学習済みの知識が前提なので、「コマンドを見て動作が説明できない」場合は 10 日〜2 週間プランへ移行すること。 ### 10 日プラン **前提**: Linux 実務経験あり(半年〜1 年以上)/ 1 日 5〜7 時間確保可能 / 2 科目合計 50〜70 時間 1 週間プランにバッファを加えた、実務経験者の標準プラン。1 週間プランの密度ではリスクが高い層が、復習日を組み込んで安定合格を狙う構成(当サイト学習者の観察に基づく目安)。 | 日 | 学習内容 | 演習 | | --- | ------------------------------------------------- | ---------------------------- | | 1 | 試験範囲確認・弱点分析(101/102 各 30 問) | Ping-t 弱点洗い出し | | 2 | 101: コマンドライン基礎・ファイル管理 | 仮想ターミナル操作演習 | | 3 | 101: パーミッション・リンク・プロセス | `chmod` / `ps` / `kill` 演習 | | 4 | 101: テキスト処理・正規表現・シェルスクリプト入門 | Ping-t 101 分野 30 問 | | 5 | 101: 総復習 + 模擬問題 | 模擬問題 50 問 | | 6 | 102: システム起動・ブートローダー・カーネル | Ping-t 102 分野 30 問 | | 7 | 102: パッケージ管理・ユーザー管理 | コマンド実操作演習 | | 8 | 102: ネットワーク・SSH・セキュリティ | 模擬問題 50 問 | | 9 | 101/102 全範囲模擬試験 2 回転 | 弱点の集中復習 | | 10 | 直前総復習・オプション確認 | 暗記カード方式で最終確認 | ### 2 週間プラン **前提**: 既経験者の復習 / 未経験スピード / 1 日 4〜5 時間確保可能 / 2 科目合計 60〜80 時間 ブランクのある経験者や、未経験者が短期で資格取得を狙う場合の標準ライン。学習密度が高めなので、平日も毎日学習時間を確保できることが前提(当サイト学習者の観察に基づく目安)。 | 日 | 学習内容 | 演習 | | ------ | ---------------------------------------------------- | -------------------------------- | | 1〜2 | 試験概要把握・Linux の基本概念・ファイルシステム構造 | 仮想ターミナルで基本操作 | | 3〜4 | 101: コマンドライン基礎・シェル操作・PATH | 仮想ターミナル演習 / Ping-t 基礎 | | 5〜6 | 101: ファイル管理・パーミッション・リンク | `chmod` / `chown` / `ln` 実操作 | | 7〜8 | 101: テキスト処理・正規表現・パイプ | `grep` / `sed` / `awk` 演習 | | 9〜10 | 102: システム起動・パッケージ管理 | Ping-t 102 基礎 | | 11〜12 | 102: ネットワーク・ユーザー管理・SSH | 模擬問題 50 問 | | 13 | 全範囲模擬試験 2 回転 | 弱点の集中復習 | | 14 | 直前総復習・苦手オプション確認 | 暗記カード方式 | ### 1 ヶ月プラン **前提**: Linux 未経験スピード / IT エンジニア(コマンドラインは入門レベル)/ 1 日 3〜4 時間確保可能 / 2 科目合計 100〜120 時間 未経験者が 1 ヶ月で合格を狙う標準プラン。週末に復習バッファを設ける構成で、平日も毎日学習時間を確保できる前提。完全未経験で時間確保が難しい場合は 2 ヶ月プランを推奨(当サイト学習者の観察に基づく目安)。 | 週 | 学習内容 | 演習 | | ------- | ------------------------------------------------------ | ------------------------------ | | 第 1 週 | Linux 概要・シェル操作・ファイルシステム・基本コマンド | 仮想ターミナルで毎日 15 分操作 | | 第 2 週 | 101: パーミッション・リンク・プロセス・テキスト処理 | Ping-t 101 分野を日次 20 問 | | 第 3 週 | 102: システム起動・パッケージ管理・ネットワーク基礎 | Ping-t 102 分野を日次 20 問 | | 第 4 週 | 102: SSH・ユーザー管理・模擬試験 2 回転・全範囲復習 | 模擬試験 × 2 + 弱点集中 | **各週の詳細(週初めにその週の範囲を確認し、週末に演習で定着させる)**: 週初め(月曜): 当週の試験範囲(LPI 主題番号)を確認し、記事で概要をつかむ。週中(火〜木): コマンドを実際に動かして記憶に定着させる。週末(金〜土): Ping-t または仮想ターミナルで演習。日曜: 前週の弱点を復習。 ### 2 ヶ月プラン(推奨) **前提**: Linux 完全未経験 / 1 日 2〜3 時間確保可能 / 2 科目合計 120〜180 時間 最も再現性が高い標準プラン。理解不足のまま進まず、各週のチェックリストをクリアしてから次に進む。完全未経験者が無理のない学習密度で合格を狙える(当サイト学習者の観察に基づく目安)。 | 週 | 学習内容 | 演習 | | ------------------ | ---------------------------------------------------------- | --------------------------------------------- | | 第 1〜2 週 | Linux の世界観・シェル操作・ファイルシステム・基本コマンド | 仮想ターミナルで毎日 20 分操作 | | 第 3〜4 週 | 101: コマンドライン操作・パス解決・引用符・ヒストリ | Ping-t 101 コマンドライン分野 / 記事読み込み | | 第 5〜6 週 | 101: パーミッション・リンク・プロセス制御・優先度 | コマンド実操作 + Ping-t 演習日次 15 問 | | 第 7〜8 週 | 101: テキスト処理・正規表現・シェルスクリプト基礎 | `grep` / `sed` / `awk` を仮想ターミナルで実習 | | 第 1〜2 週(後半) | 102: システム起動・GRUB・ランレベル / systemd | Ping-t 102 起動分野 / 読み込み | | 第 3〜4 週(後半) | 102: パッケージ管理・ユーザー管理・権限 | `apt` / `rpm` 操作演習 | | 第 5〜6 週(後半) | 102: ネットワーク設定・SSH・セキュリティ | 設定ファイル読み込み / Ping-t 演習 | | 第 7〜8 週(後半) | 模擬試験 4 回転・弱点集中・直前総復習 | 模擬試験 × 4 + 全範囲暗記カード | **2 ヶ月プランのポイント**: - 1 日 1.5 時間のうち 30 分は必ず「手を動かす」時間にする - Ping-t の正答率が 80% 未満の分野は次週へ進まず復習を優先する - 模擬試験は正答率が 75% を超えたら受験日を設定する目安にする ## 勉強方法の組み合わせ {#methods} 教材の役割を明確にして組み合わせると学習効率が上がる。以下の表で各教材の位置づけを整理する。(当サイト学習者の観察に基づく目安) | 教材 | 主な役割 | 推奨タイミング | | -------------------- | ----------------------------------------- | ---------------------------------- | | 参考書(書籍) | 試験範囲の体系的な理解 / 概念の把握 | 学習序盤〜中盤(最初の 1〜2 週間) | | Ping-t(Web 問題集) | 問題形式への慣れ / 弱点の発見 | 中盤以降(参考書と並行) | | Udemy 動画講座 | 視覚・聴覚で概念をつかむ / 入門者の導入 | 学習開始直後(入門者向け) | | 仮想ターミナル | コマンドの体感理解 / オプションの記憶定着 | 全期間(毎日 15〜30 分) | | クイズ(当サイト) | 知識の定着確認 / 試験前の最終チェック | 中盤以降 / 直前期 | **参考書の選び方**: LPIC-1 専用の参考書であれば、最新版を選ぶことが最重要。試験バージョン v5.0 に対応していない書籍は出題範囲がずれる場合があるため注意する。 **Ping-t の使い方**: 最初から「模擬試験モード」で解くより「分野別」から始めた方が弱点が把握しやすい。正答率が 80% を超えたら模擬試験モードに切り替えて時間感覚をつかむ。 **仮想ターミナルの活用**: 読んで覚えたコマンドをすぐに手で動かすことで記憶の定着率が上がる。特に `chmod` / `chown` / `grep` / `find` / `ps` は試験頻出なので毎日 1 回は実際に操作する習慣をつける。 **Udemy 動画の位置づけ**: 動画は「理解の入口」として有効だが、試験直前に動画ばかり見ていると演習が不足する。中盤以降は問題演習と実操作を優先すること。 理解度クイズで知識確認 → 仮想ターミナルで実操作 → という流れは [LPIC-1 学習ハブ](/lpic1) でブラウザ完結で実行できる。 ## 101 / 102 の順番と同時受験 {#order} LPIC-1 は 101-500(101 試験)と 102-500(102 試験)の 2 科目。受験順序に LPI の規定はなく、どちらから受けても構わない。(出典: LPI 公式サイト, 2026-05 時点) **受験順序の判断フロー**: まず「コマンドライン操作に不安があるか」を確認する。 - 不安がある → **101 試験から受験**(コマンドライン基礎・ファイル管理が 101 の中心) - ネットワーク・サーバー設定に慣れている → **102 試験から受験**も可 - 試験会場の日程が先に 102 しか空いていない → どちらでも対応可能 **一般的な推奨**: 101 → 102 の順序が学習の流れとして自然だ。101 で習得するコマンドライン操作・ファイルシステム・プロセス管理の知識が、102 のシステム起動・ネットワーク設定を理解する土台になる。当サイト学習者の観察でも、101 から始めた場合のほうが 102 の学習がスムーズに進むケースが多い。(当サイト学習者の観察に基づく目安) **同時受験(同日に 101 + 102)について**: - **推奨しない**(ただし実務経験豊富な場合は例外) - 1 日に 2 科目は集中力と時間の両面で負担が大きい - 片方を落とすと時間・費用の両方が無駄になるリスクがある - まず 101 を合格し、その勢いで 102 を受けるパターンが安定する **101 の出題範囲(主要トピック)**: - コマンドライン操作(シェル / bash / PATH)→ [コマンドライン基礎](/articles/lpic/command-line-basics) - テキスト処理(grep / sed / awk)→ [テキストストリームフィルタ](/articles/lpic/text-stream-filters) - 正規表現 → [正規表現の基礎](/articles/lpic/regular-expressions) - ファイル管理・リンク → [ハードリンクとシンボリックリンク](/articles/lpic/hard-symbolic-links) - プロセス管理・優先度 → [プロセス優先度と nice](/articles/lpic/process-priorities-nice) **102 の出題範囲(主要トピック)**: - シェル環境・環境変数 → [シェル環境変数](/articles/lpic/shell-environment) - システム起動・ブートローダー / systemd - パッケージ管理(apt / rpm / dpkg) - ネットワーク設定・SSH ## 落ちないための注意点 {#pitfalls} ### 症状: 模擬試験では通るが本番で落ちる **原因**: 問題の「パターン暗記」に偏り、コマンドの動作原理を理解していない **確認**: - 正答した問題で「なぜその答えか」を言語化できるか確認する - オプションの意味をドキュメント(`man` ページ)で確認できるか確認する **対処**: 仮想ターミナルで実際にコマンドを動かし、出力を観察する。「覚えた」ではなく「使える」状態にする。 ### 症状: 学習が途中で止まる **原因**: 範囲が広く、どこまで学べばよいか見えなくなる **確認**: - 試験の LPI 公式出題範囲([lpi.org](https://lpi.org))の主題番号リストを確認しているか確認する - 「全部完璧に」を目指していないか確認する **対処**: 合格点は 500 点(200〜800 点スケール)であり、満点を目指す必要はない。各主題の weight(出題比重)に応じた一定の正答率を取れば合格できる。(出典: LPI 公式 FAQ `https://www.lpi.org/ja/about-lpi/frequently-asked-questions`, 取得日: 2026-05-23)。weight が大きい主要主題を確実に押さえる戦略をとる。 ### 症状: 試験範囲外のコマンドに時間をかけすぎる **原因**: 興味が広がり、LPIC の出題範囲外に踏み込んでいる **確認**: - LPI 公式の試験範囲(v5.0)に照らして、今学習している内容が範囲内か確認する **対処**: LPIC-1 の試験範囲は LPI 公式サイト([lpi.org](https://lpi.org))で公開されている。(出典: LPI 公式サイト, 2026-05 時点)。まず範囲内を完成させてから応用に進む。 ### [注意] dumps 系コンテンツは使用しない dumps(本試験の問題を無断転載したコンテンツ)を使った学習は LPI の受験規約に違反する。また、Google のスパムポリシー上も問題があるコンテンツだ。「dumps で合格した」という情報がネット上に存在するが、違反が発覚した場合、認定取り消しのリスクがある。 合法的かつ効果的な演習は Ping-t / 書籍付属の模擬問題 / 当サイトのクイズで十分対応できる。 ### 症状: コマンドのオプションを覚えられない **原因**: 視覚的な暗記のみで、実際の動作と結びついていない **確認**: - 仮想ターミナルでそのコマンドを実際に実行したか確認する **対処**: オプションは「動かして覚える」のが最速。[LPIC-1 仮想ターミナル演習](/terminal?category=lpic)で実操作と組み合わせると定着が早い。 ## 学習開始前チェックリスト {#checklist} 以下をすべて確認してから学習を始めると、計画倒れになりにくい。 - [ ] 受験する科目(101 / 102 / 両方)と受験日の目標を決めた - [ ] 1 日に確保できる学習時間を現実的に見積もった(「2 時間」ではなく「22 時〜23 時半」のように具体化) - [ ] 使用する教材(参考書 / Ping-t / 動画 / 当サイト)を選んだ - [ ] 仮想ターミナル環境を確認した(当サイト: [/terminal.html?category=lpic](/terminal?category=lpic)) - [ ] LPI 公式サイトで試験範囲(v5.0)を確認した - [ ] dumps 系コンテンツは使用しないと決めた ## まとめ {#summary} | 経験レベル | 推奨プラン | 2 科目合計目安時間 | | ----------------------- | -------------- | ------------------ | | 実務経験 1 年以上 | 1 週間〜10 日 | 40〜70 時間 | | 実務経験あり(半年〜) | 10 日〜2 週間 | 60〜100 時間 | | 未経験(IT エンジニア) | 1 ヶ月 | 100〜140 時間 | | 完全未経験 | 2 ヶ月(推奨) | 120〜200 時間 | (当サイト学習者の観察に基づく目安。LPIC-1 学習ハブ FAQ の「1 科目あたり 50〜100 時間(未経験)/ 20〜40 時間(実務経験あり)」を 2 科目合計に換算した値が基準) LPIC-1 は「丸暗記」では合格が難しい試験だ。コマンドの動作原理を理解し、実際に手を動かして確認する学習サイクルが最も再現性が高い。期間別プランはあくまで骨格なので、週次で進捗を確認しながら柔軟に調整すること。 理解度クイズで現在地を確認 → 仮想ターミナルで実操作 → が当サイトの学習フロー。[LPIC-1 学習ハブ](/lpic1)から各リソースに一気にアクセスできる。 ## 次に読む {#next} - [コマンドライン操作の基礎](/articles/lpic/command-line-basics) - [テキストストリームフィルタ](/articles/lpic/text-stream-filters) - [正規表現の基礎](/articles/lpic/regular-expressions) - [プロセス優先度と nice](/articles/lpic/process-priorities-nice) - [ハードリンクとシンボリックリンク](/articles/lpic/hard-symbolic-links) - [シェル環境変数](/articles/lpic/shell-environment) - [LPIC-1 理解度クイズ](/quiz?category=lpic) - [LPIC-1 仮想ターミナル演習](/terminal?category=lpic) # システムロギング - syslog/journald/logger【LPIC-1 108.2】 Source: https://penguin-gym-linux.com/articles/lpic/system-logging ## この記事で達成できること {#intro} - rsyslog の「ファシリティ.プライオリティ」書式でログの振り分けルールを書ける - ファシリティとプライオリティの一覧を区別して答えられる - `journalctl` の主要オプション(`-u` / `-b` / `-f` / `-p` / `--since`)を使い分けられる - systemd-journald を揮発から永続ストレージに切り替えられる - `logger` で任意のメッセージを syslog に記録できる - `logrotate` でログの肥大化を防ぐ仕組みを説明できる LPIC-1 主題 108.2「システムのログを管理する」の中核。rsyslog と systemd-journald の二系統、それを操作する `journalctl` / `logger` / `logrotate` を一通り押さえる。 ## どのログ機構を使えばいいのか {#overview} 現代の多くのディストリビューションでは、systemd-journald がログを一次受信し、必要に応じて rsyslog に転送して従来型のテキストログ(`/var/log/messages` 等)にも書き出す二段構成が一般的。 | 機構 | 保存形式 | 主な確認コマンド | 設定ファイル | | ---------------- | ------------------- | ---------------- | ---------------------------- | | systemd-journald | バイナリ(journal) | `journalctl` | `/etc/systemd/journald.conf` | | rsyslog | テキスト | `cat` / `grep` | `/etc/rsyslog.conf` | journald は構造化されたメタデータ(ユニット名・PID・起動 ID 等)を保持し、`journalctl` で柔軟に絞り込める。rsyslog はプレーンテキストで、リモートへの転送や長期保管の運用に強い。試験ではこの両方が問われる。 ## rsyslog の振り分けルールはどう書くのか {#rsyslog} rsyslog の振り分けは「セレクタ(ファシリティ.プライオリティ)+アクション(出力先)」の組で記述する。`/etc/rsyslog.conf` または `/etc/rsyslog.d/` 配下のファイルに書く。 書式は次の形。 ```bash mail.info /var/log/mail.log authpriv.* /var/log/secure *.emerg :omusrmsg:* cron.* /var/log/cron ``` 左がセレクタ、右が出力先。`mail.info` は「mail ファシリティのプライオリティ info 以上」を意味する。`info` のように 1 つ指定すると、それ**以上の重大度**がすべて該当する点が頻出ポイント。`authpriv.*` の `*` は全プライオリティ、`*.emerg` は全ファシリティの emerg を意味する。 ### ファシリティとプライオリティ {#facility-priority} セレクタはファシリティ(メッセージの発生源カテゴリ)とプライオリティ(重大度)の組み合わせ。 ファシリティの主な値: | ファシリティ | 意味 | | ------------------- | -------------------------------- | | `auth` / `authpriv` | 認証・セキュリティ関連 | | `cron` | cron / at のジョブ | | `daemon` | 各種デーモン(システムサービス) | | `kern` | カーネルメッセージ | | `mail` | メールサブシステム | | `user` | ユーザープロセス(デフォルト) | | `local0`〜`local7` | 独自用途に割り当て可能な予約枠 | プライオリティ(重大度。低い順に並べる): | プライオリティ | 数値 | 意味 | | -------------- | ---- | ---------------------- | | `debug` | 7 | デバッグ情報 | | `info` | 6 | 通常の情報 | | `notice` | 5 | 正常だが注意すべき事象 | | `warning` | 4 | 警告 | | `err` | 3 | エラー | | `crit` | 2 | 危機的状態 | | `alert` | 1 | 即時対応が必要 | | `emerg` | 0 | システム使用不能 | `*.info` は info 以上(info / notice / warning / err / crit / alert / emerg)が対象。特定プライオリティだけに限定したい場合は `mail.=info` のように `=` を付ける。 ::: tip ファシリティとプライオリティは「.(ドット)」で結ぶ。`local0`〜`local7` はアプリケーションが独自ログを syslog に流すための予約枠で、自作スクリプトや業務アプリの分離によく使われる。 ::: ### ログのリモート転送 {#remote} rsyslog は他ホストへログを転送できる。送信側の設定ファイルにセレクタと転送先を記述する。 ```bash *.* @192.168.1.10:514 *.* @@192.168.1.10:514 ``` `@` 1 つは UDP、`@@` 2 つは TCP での転送。集中ログサーバへ集約する運用で使う。受信側は該当ポートでの受信を有効化する必要がある。 ## journalctl の主要オプションは何か {#journalctl} `journalctl` は systemd-journald が収集したログを検索・表示するコマンド。引数なしで実行すると全ログを古い順に表示する。 代表的な絞り込みオプション: ```bash journalctl -u sshd.service journalctl -b journalctl -f journalctl -p err journalctl --since "2026-05-30 09:00:00" --until "2026-05-30 12:00:00" ``` | オプション | 機能 | | --------------------- | --------------------------------------------------- | | `-u UNIT` | 指定したサービスユニットのログだけ表示 | | `-b [ID]` | 起動単位で絞る(`-b` は今回、`-b -1` は前回の起動) | | `-f` | 末尾を追従表示(`tail -f` 相当) | | `-p PRIORITY` | プライオリティで絞る(`-p err` は err 以上) | | `--since` / `--until` | 期間で絞る(`--since today` 等の相対指定も可) | | `-k` | カーネルメッセージのみ(`dmesg` 相当) | | `-r` | 新しい順に表示 | | `-n N` | 末尾 N 行を表示 | 実行例: ```bash journalctl -u sshd --since today -p warning ``` ```output May 30 09:14:22 host sshd[1421]: Failed password for invalid user test from 203.0.113.5 port 55012 ssh2 May 30 10:02:51 host sshd[1588]: error: maximum authentication attempts exceeded for root ``` `-p` のプライオリティ指定は rsyslog と同じ語(`emerg`〜`debug`)または数値(0〜7)が使える。`-p err` は err 以上をまとめて拾う。 ## journald のログを永続化するには {#persistence} systemd-journald のログ保存先は `/etc/systemd/journald.conf` の `Storage=` で制御する。デフォルト(`auto`)では `/var/log/journal/` が存在すればそこに永続保存し、無ければ `/run/log/journal/`(メモリ上の tmpfs)に揮発保存する。 ```bash [Journal] Storage=persistent ``` | `Storage=` の値 | 挙動 | | --------------- | ----------------------------------------------------------------- | | `volatile` | 常にメモリ(`/run/log/journal/`)のみ。再起動で消える | | `persistent` | 常にディスク(`/var/log/journal/`)に保存。ディレクトリも自動作成 | | `auto` | `/var/log/journal/` があれば永続、無ければ揮発(デフォルト) | | `none` | 保存しない(転送のみ) | 永続化する手順: ```bash sudo mkdir -p /var/log/journal sudo systemctl restart systemd-journald journalctl --disk-usage ``` ```output Archived and active journals take up 112.4M in the file system. ``` `Storage=persistent` を設定するか `/var/log/journal/` を作成して journald を再起動すれば、再起動後もログが残る。`journalctl --disk-usage` で消費量を確認できる。 ::: warning 多くのディストリビューションでは journald のデフォルトが `auto` かつ `/var/log/journal/` が未作成のため、ログが揮発(メモリ保存)になっている。この状態では再起動すると過去ログが消える。`journalctl -b -1` で「前回の起動分」を見ようとして何も出ない場合、永続化されていないことを疑う。 ::: ## logger コマンドの使い方 {#logger} `logger` はコマンドラインやスクリプトから syslog にメッセージを送るツール。シェルスクリプトの実行ログを残す用途で多用する。 ```bash logger "backup script started" logger -p local0.info -t backup "nightly backup finished" ``` | オプション | 機能 | | ---------- | ------------------------------------------------------- | | `-p` | ファシリティ.プライオリティを指定(既定 `user.notice`) | | `-t TAG` | メッセージにタグ(識別名)を付ける | | `-s` | 標準エラー出力にも同時に出す | 記録結果の確認: ```bash logger -p local0.info -t backup "nightly backup finished" journalctl -t backup -n 1 ``` ```output May 30 02:00:03 host backup[2840]: nightly backup finished ``` `-t backup` で付けたタグは `journalctl -t backup` で絞り込める。スクリプト内に `logger` を仕込むと、cron 等から走らせた処理の成否を後から journal で追跡できる。 ## logrotate でログの肥大化を防ぐには {#logrotate} `logrotate` はログファイルが無制限に大きくならないよう、定期的に世代交代(ローテーション)させる仕組み。主設定は `/etc/logrotate.conf`、個別設定は `/etc/logrotate.d/` 配下に置く。 `/etc/logrotate.conf` の例: ```bash weekly rotate 4 create compress include /etc/logrotate.d ``` `weekly` は週次ローテーション、`rotate 4` は 4 世代保持、`create` はローテーション後に空のログを作成、`compress` は古いログを gzip 圧縮する。`include /etc/logrotate.d` でサービスごとの個別設定を読み込む。 個別設定(`/etc/logrotate.d/nginx` の例): ```bash /var/log/nginx/*.log { daily rotate 14 missingok notifempty postrotate systemctl reload nginx > /dev/null 2>&1 || true endscript } ``` `daily` で日次、`rotate 14` で 14 世代保持。`missingok` はファイルが無くてもエラーにせず、`notifempty` は空なら回さない。`postrotate`〜`endscript` でローテーション後にプロセスへ再読み込みを通知する。 動作確認(実際には回さず判定だけ表示): ```bash logrotate -d /etc/logrotate.conf ``` ```output rotating pattern: /var/log/nginx/*.log after 1 days (14 rotations) considering log /var/log/nginx/access.log log does not need rotating (log has been already rotated) ``` `-d`(debug)はドライランで、実際にはローテーションせず判定内容だけ出力する。設定を変更したら本番適用前に `-d` で確認するのが定石。`-f`(force)を付けると条件を満たさなくても強制的に回す。 ::: tip logrotate は通常 `cron.daily` または systemd timer から 1 日 1 回呼ばれる。`daily` と設定しても実行は呼び出しタイミングに依存するため、「設定した瞬間に回る」ものではない点に注意。 ::: ## 主要なログファイルはどこにあるか {#logfiles} rsyslog が書き出すテキストログの場所はディストリビューションで異なる。試験では Red Hat 系と Debian 系の差が問われる。 | 内容 | Red Hat 系 | Debian 系 | | -------------------- | ------------------- | ---------------------------------- | | 一般的なシステムログ | `/var/log/messages` | `/var/log/syslog` | | 認証関連 | `/var/log/secure` | `/var/log/auth.log` | | cron ログ | `/var/log/cron` | `/var/log/syslog`(syslog に統合) | | カーネルメッセージ | `/var/log/dmesg` | `/var/log/kern.log` | 認証失敗を調べるなら Red Hat 系は `/var/log/secure`、Debian 系は `/var/log/auth.log` を見る。systemd-journald のみの環境ではこれらのテキストファイルが存在しないこともあり、その場合は `journalctl` で同等の情報を得る。 ## よくあるミス {#mistakes} ログ管理で初学者がつまずく代表的なパターン。 ::: danger **ミス 1: 再起動したら過去ログが消えた** journald がデフォルトの揮発保存(`/run/log/journal/`)のままだと、再起動でログが失われる。`/var/log/journal/` を作成するか `Storage=persistent` を設定して永続化する。 ::: - **ミス 2: `mail.info` を「info だけ」と誤解する** — セレクタの単一プライオリティ指定は「それ以上の重大度すべて」を含む。info だけに限定したいなら `mail.=info` と `=` を使う。 - **ミス 3: ファシリティとプライオリティを取り違える** — `auth` / `cron` / `mail` 等は発生源(ファシリティ)、`err` / `warning` / `debug` 等は重大度(プライオリティ)。混同すると振り分けルールが意図通りに動かない。 - **ミス 4: logrotate を設定した瞬間に回ると思い込む** — logrotate は cron / timer からの呼び出し時に条件を評価する。即時確認したいなら `logrotate -d`(判定のみ)や `-f`(強制実行)を使う。 - **ミス 5: テキストログだけ見て journal を見落とす** — systemd 環境では多くのサービスログが journal にしかない。`/var/log/` にファイルが無くても `journalctl -u サービス名` で確認する。 ## トラブルシューティング {#troubleshooting} ### 症状: journalctl -b -1 で前回の起動ログが出ない **原因**: journald が揮発保存になっており、再起動でログが消えている **確認**: ```bash cat /etc/systemd/journald.conf | grep Storage ls /var/log/journal 2>/dev/null ``` **対処**: `/var/log/journal/` を作成し `sudo systemctl restart systemd-journald` で永続化する。または `Storage=persistent` を設定する。 ### 症状: rsyslog のルールを書いたのにログが出力されない **原因**: セレクタのファシリティ/プライオリティの綴り誤り、または設定再読み込み漏れ **確認**: ```bash sudo rsyslogd -N1 systemctl status rsyslog ``` **対処**: `rsyslogd -N1` で構文検証し、`sudo systemctl restart rsyslog` で再読み込みする。出力先ディレクトリの存在と権限も確認する。 ### 症状: ログでディスクが逼迫している **原因**: logrotate の保持世代が多すぎる、または journald の上限が大きい **確認**: ```bash journalctl --disk-usage du -sh /var/log/* ``` **対処**: logrotate の `rotate` 世代数や `compress` を見直す。journald は `journalctl --vacuum-size=200M` で過去分を削減できる。 ## 作業完了チェックリスト {#checklist} - [ ] rsyslog のセレクタ書式(ファシリティ.プライオリティ)を読み書きできる - [ ] ファシリティとプライオリティの一覧を区別できる - [ ] `journalctl -u` / `-b` / `-f` / `-p` / `--since` を使い分けられる - [ ] journald を永続化する手順を実行できる - [ ] `logger` でタグ付きメッセージを記録できる - [ ] `logrotate -d` でローテーション判定を確認できる ## まとめ {#summary} | 目的 | コマンド / 設定 | | ------------------ | ---------------------------------------- | | 振り分けルール | `/etc/rsyslog.conf`(`mail.info /path`) | | ユニットログ確認 | `journalctl -u UNIT` | | 起動別ログ確認 | `journalctl -b` / `-b -1` | | リアルタイム追従 | `journalctl -f` | | 永続化 | `Storage=persistent`(journald.conf) | | メッセージ記録 | `logger -p local0.info -t TAG "msg"` | | ローテーション確認 | `logrotate -d /etc/logrotate.conf` | システムロギングは障害調査とセキュリティ監査の土台。rsyslog のファシリティ/プライオリティと journald の永続化、logrotate による容量管理を押さえれば 108.2 は確実に得点できる。 ## 次に読む {#next} - [ジョブスケジューリング - cron と systemd timer](/articles/lpic/job-scheduling) - [boot プロセスと systemd](/articles/lpic/boot-and-systemd) - [システム時刻の管理](/articles/lpic/system-time) - [LPIC-1 出題範囲完全ガイド - 101 / 102 全トピック対応表](/articles/lpic/exam-scope-101-102) - [LPIC-1 学習ハブ](/lpic1) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) # システム時刻の管理 - date/timedatectl/NTP/chrony【LPIC-1 108.1】 Source: https://penguin-gym-linux.com/articles/lpic/system-time ## この記事で達成できること {#intro} - `date` で現在時刻を表示し、書式と設定を使い分けられる - システムクロックとハードウェアクロック(RTC)の関係を `hwclock` で説明できる - `timedatectl` でタイムゾーンと時刻同期をまとめて管理できる - NTP(`ntpd` / `chrony`)で時刻同期を構成し、状態を確認できる - 試験頻出の「UTC とローカル時刻」「手動設定と NTP の競合」を根拠付きで答えられる LPIC-1 主題 108.1「システム時刻を保守する」の中核。ログの突合、cron の正確な起動、TLS 証明書の検証はすべて正しい時刻が前提になる。 ## システム時刻はどの層で管理されるのか {#layers} Linux の時刻は「システムクロック(カーネルが保持するソフトウェア時計)」と「ハードウェアクロック(マザーボード上の RTC)」の 2 層で管理される。日常の時刻同期はシステムクロックに対して行い、RTC は起動時の初期値として使う。 | 時計 | 実体 | 操作コマンド | 電源OFF時 | | --------------------------- | ------------------------------ | ---------------------- | ---------- | | システムクロック | カーネル内部のソフトウェア時計 | `date` / `timedatectl` | 失われる | | ハードウェアクロック(RTC) | マザーボードの電池駆動チップ | `hwclock` | 保持される | 起動時に RTC からシステムクロックへ時刻が読み込まれ(`hctosys`)、運用中は NTP がシステムクロックを補正する。`hwclock --systohc` でシステムクロックを RTC に書き戻すことで、次回起動時の初期値を正確に保つ。 ::: tip systemd 環境では起動時の RTC 読み込みと書き戻しは systemd が自動で行うため、手動の `hwclock` 操作は通常不要。NTP 同期が有効なら timedatectl が定期的に RTC を更新する。 ::: ## date で時刻を表示・設定する {#date} `date` は現在のシステム時刻を表示・設定するコマンド。`+` で始まる書式指定子で出力形式を自由に整形できる。設定には root 権限が必要だが、NTP 同期中の手動設定は避ける。 ### 時刻の表示と書式指定 ```bash date date '+%Y-%m-%d %H:%M:%S' date '+%Y%m%d' date -u ``` ```output 2026年 5月 30日 土曜日 14:23:07 JST 2026-05-30 14:23:07 20260530 2026年 5月 30日 土曜日 05:23:07 UTC ``` 主要な書式指定子は `%Y`(4桁の年)、`%m`(月)、`%d`(日)、`%H`(時、24時間)、`%M`(分)、`%S`(秒)。`-u` を付けると UTC で表示する。バックアップのファイル名生成などで `date '+%Y%m%d'` が定番。 ### 時刻の手動設定 ```bash sudo date -s '2026-05-30 14:30:00' ``` ```output 2026年 5月 30日 土曜日 14:30:00 JST ``` `-s`(`--set`)で時刻を直接設定する。ただし NTP 同期が有効な環境では、設定してもすぐ NTP に上書きされる。手動設定する場合は先に同期を止める(後述)。 ## hwclock でハードウェアクロックを操作する {#hwclock} `hwclock` は RTC を読み書きするコマンド。`--systohc` でシステム時刻を RTC に、`--hctosys` で RTC をシステム時刻にコピーする。RTC を UTC で持つかローカル時刻で持つかが試験の頻出ポイント。 ### RTC の参照と同期方向 ```bash sudo hwclock --show sudo hwclock --systohc sudo hwclock --hctosys ``` ```output 2026-05-30 14:31:05.123456+09:00 ``` `--show`(`-r`)で RTC の現在値を表示する。同期方向は名前で覚える。 | オプション | 方向 | 用途 | | ------------------- | -------------- | ------------------------------- | | `--systohc`(`-w`) | システム → RTC | 正しいシステム時刻を RTC に保存 | | `--hctosys`(`-s`) | RTC → システム | RTC の値でシステム時刻を初期化 | ### UTC とローカル時刻 ```bash sudo hwclock --systohc --utc sudo hwclock --systohc --localtime ``` `--utc` を付けると RTC を UTC として扱い、`--localtime` ならローカル時刻として扱う。Linux 単独運用では UTC を強く推奨。RTC をローカル時刻にすると、夏時間の切り替えやタイムゾーン変更で二重補正・ずれが起きやすい。 ::: warning Windows とのデュアルブートでは Windows が既定で RTC をローカル時刻として扱うため、Linux 側も `--localtime` に合わせるか、Windows 側を UTC 運用に変更する。混在すると起動のたびに時刻がずれる。 ::: ## timedatectl で時刻とタイムゾーンを管理する {#timedatectl} `timedatectl` は systemd 環境の標準コマンドで、時刻・タイムゾーン・時刻同期・RTC モードを一元管理する。引数なしの `timedatectl` は `status` と同じ出力を返す。 ### 現在の状態を確認する ```bash timedatectl status ``` ```output Local time: 土 2026-05-30 14:35:12 JST Universal time: 土 2026-05-30 05:35:12 UTC RTC time: 土 2026-05-30 05:35:11 Time zone: Asia/Tokyo (JST, +0900) System clock synchronized: yes NTP service: active RTC in local TZ: no ``` `System clock synchronized` と `NTP service` で同期状態を確認できる。`RTC in local TZ: no` は RTC を UTC で保持していることを示す(推奨状態)。 ### タイムゾーンを設定する ```bash timedatectl list-timezones | grep Tokyo sudo timedatectl set-timezone Asia/Tokyo ``` ```output Asia/Tokyo ``` `list-timezones` で利用可能なタイムゾーン一覧を表示し、`set-timezone` で設定する。これにより `/etc/localtime` のシンボリックリンクが更新される(後述)。 ### 時刻同期を有効化する ```bash sudo timedatectl set-ntp true ``` `set-ntp true` は、利用可能な最初の時刻同期サービスを有効化・起動する(systemd 環境では `systemd-timesyncd` が代表)。`false` で無効化する。手動で時刻を設定したいときは、まず `set-ntp false` で同期を止める。 ### RTC を UTC で保持する ```bash sudo timedatectl set-local-rtc 0 ``` `set-local-rtc 0` で RTC を UTC として維持する(推奨)。`1` を指定するとローカル時刻になるが、公式ドキュメントは「ローカル時刻での RTC 保持は完全にはサポートされず、タイムゾーン変更や夏時間調整で問題を起こす。可能な限り UTC モードを維持せよ」と明記している。 ## タイムゾーンはどのファイルで決まるのか {#tzfiles} システムのタイムゾーンは `/etc/localtime` が `/usr/share/zoneinfo/` 配下のどのファイルを指すかで決まる。Debian 系はこれに加えて `/etc/timezone` にゾーン名をテキストで持つ。 | パス | 役割 | | ---------------------- | ---------------------------------------- | | `/usr/share/zoneinfo/` | 全タイムゾーン定義(バイナリ)の格納場所 | | `/etc/localtime` | システム TZ を指すシンボリックリンク | | `/etc/timezone` | ゾーン名のテキスト(Debian / Ubuntu 系) | ```bash ls -l /etc/localtime ``` ```output lrwxrwxrwx 1 root root 30 May 30 14:00 /etc/localtime -> /usr/share/zoneinfo/Asia/Tokyo ``` `timedatectl set-timezone` を使えばこのリンクと `/etc/timezone` の整合性を自動で保てる。手動でリンクを張り替えるより `timedatectl` を使うのが安全。個別ユーザーの一時的な変更は環境変数 `TZ` でも可能。 ## NTP で時刻を同期する {#ntp} NTP(Network Time Protocol)は上位サーバから正確な時刻を取得してシステムクロックを補正するプロトコル。実装は古典的な `ntpd`、後継の `chrony`、軽量な `systemd-timesyncd` の 3 系統がある。 ### 3 つの実装の違い | 実装 | 設定ファイル | 確認コマンド | 特徴 | | -------------------------- | ----------------------------- | -------------------------------------- | ---------------------------- | | `ntpd`(ntp.org 参照実装) | `/etc/ntp.conf` | `ntpq -p` | 歴史が長く資料が豊富 | | `chrony`(chronyd) | `/etc/chrony.conf` | `chronyc sources` / `chronyc tracking` | 断続的な接続や仮想環境に強い | | `systemd-timesyncd` | `/etc/systemd/timesyncd.conf` | `timedatectl status` | SNTP クライアントのみ、軽量 | 同一ホストで複数の時刻同期デーモンを同時に動かすと互いに競合するため、使うのは 1 つだけにする。 ### ntpd の確認 ```bash ntpq -p ``` ```output remote refid st t when poll reach delay offset jitter ============================================================================== *ntp1.example.com 133.243.x.x 2 u 45 64 377 8.123 0.512 0.087 +ntp2.example.com 133.243.x.x 2 u 38 64 377 12.456 -0.231 0.142 ``` `ntpq -p`(peers)は対向サーバ一覧を表示する。行頭の `*` が現在同期中のサーバ、`+` が同期候補。`reach` は到達性(8進数、`377` が直近 8 回すべて成功)、`offset` がローカルとの時刻差(ミリ秒)。設定は `/etc/ntp.conf` の `server` 行で指定する。 ### chrony の確認 ```bash chronyc sources chronyc tracking ``` ```output MS Name/IP address Stratum Poll Reach LastRx Last sample =============================================================================== ^* ntp1.example.com 2 6 377 23 +12us[ +18us] +/- 14ms ^- ntp2.example.com 2 6 377 21 -103us[ -103us] +/- 21ms ``` `chronyc sources` の `M` 列は時刻源の種別(`^` はサーバ)、`S` 列は選択状態(`*` が同期中)。`Stratum` は基準時計からのホップ数、`Reach` は到達性レジスタ。`chronyc tracking` は `Reference ID`・`Stratum`・`System time`(NTP 時計とシステム時計の現在のオフセット)・`Last offset`・`RMS offset` など同期品質の詳細を表示する。設定は `/etc/chrony.conf` の `server` / `pool` 行で指定する。 ::: tip 試験対策では「`chronyc sources` で時刻源、`chronyc tracking` で同期状態の詳細」「`ntpq -p` は ntpd の対向サーバ一覧」という対応を押さえる。設定ファイル名(`/etc/ntp.conf` と `/etc/chrony.conf`)も頻出。 ::: ## よくあるミスと対処 {#mistakes} 時刻管理は設定間の競合や UTC/ローカルの取り違えでトラブルが起きやすい。試験でも実務でも問われる典型例を挙げる。 - **UTC とローカル時刻の混同**: RTC を UTC で持つ前提なのに `--localtime` で書き込み、起動のたびにタイムゾーン分(日本なら 9 時間)ずれる。Linux 単独なら UTC で統一する。 - **NTP と手動設定の競合**: NTP 同期中に `date -s` や `hwclock --systohc` で手動設定しても、すぐ NTP に上書きされる。手動設定するなら先に `timedatectl set-ntp false`(または該当デーモン停止)で同期を止める。 - **複数の時刻同期デーモンの同時起動**: `ntpd` と `chronyd` を両方起動すると同じポートと役割を奪い合い同期が不安定になる。1 つだけ有効化する。 - **タイムゾーン変更が反映されない**: `/etc/localtime` を手動で書き換えたが `/etc/timezone`(Debian 系)と食い違う。`timedatectl set-timezone` を使えば両者の整合性が保たれる。 - **稼働中プロセスに TZ 変更が効かない**: タイムゾーンを変えても、起動済みのデーモン(cron・アプリ)は古い TZ を保持し続けることがある。変更後はサービスを再起動する。 ## トラブルシューティング {#troubleshooting} 時刻同期の不具合は「症状 → 原因 → 確認 → 対処」の順で切り分けると速い。 ### 症状: timedatectl で同期されない(System clock synchronized: no) **原因**: 時刻同期サービスが無効、または NTP サーバへ到達できていない **確認**: ```bash timedatectl status sudo systemctl status systemd-timesyncd ``` **対処**: `sudo timedatectl set-ntp true` で同期を有効化する。それでも `no` のままなら、ファイアウォールで UDP 123 番が塞がれていないか、設定ファイルの `server` 行が正しいかを確認する。 ### 症状: date -s で設定してもすぐ時刻が戻る **原因**: NTP 同期が有効で、手動設定が即座に上書きされている **確認**: ```bash timedatectl status ``` **対処**: `System clock synchronized: yes` なら正常動作。意図的に手動設定したい場合は `sudo timedatectl set-ntp false` で同期を止めてから `date -s` を実行する。 ### 症状: 起動するたびに時刻がタイムゾーン分ずれる **原因**: RTC のモード(UTC / ローカル)とシステムの解釈が食い違っている **確認**: ```bash timedatectl status sudo hwclock --show ``` **対処**: Linux 単独なら `sudo timedatectl set-local-rtc 0` で RTC を UTC に統一する。Windows とのデュアルブートなら、どちらかのモードに揃える。 ## 作業完了チェックリスト {#checklist} - [ ] `timedatectl status` で時刻・TZ・同期状態を確認した - [ ] `timedatectl set-timezone` で正しいタイムゾーンを設定した - [ ] `timedatectl set-ntp true` で時刻同期を有効化した - [ ] `chronyc sources` / `ntpq -p` で時刻源の到達性を確認した - [ ] RTC を UTC(`set-local-rtc 0`)で保持していることを確認した ## まとめ {#summary} | 場面 | コマンド | 目的 | | ------------ | ------------------------------ | ---------------------------- | | 時刻表示 | `date '+%Y-%m-%d'` | 書式を指定して表示 | | 状態確認 | `timedatectl status` | 時刻・TZ・同期をまとめて確認 | | TZ 設定 | `timedatectl set-timezone` | タイムゾーン変更 | | 同期制御 | `timedatectl set-ntp true` | NTP 同期の有効化 | | RTC 書き戻し | `hwclock --systohc` | システム時刻を RTC に保存 | | chrony 確認 | `chronyc sources` / `tracking` | 時刻源と同期品質 | | ntpd 確認 | `ntpq -p` | 対向サーバ一覧 | システム時刻はログ・cron・証明書検証の土台。108.1 を押さえたら、時刻に依存するログ管理やジョブスケジューリングと合わせて運用知識が完成する。 ## 次に読む {#next} - [LPIC-1 学習ハブ(出題範囲マップ)](/lpic1) - [システムログの管理 - journald と rsyslog](/articles/lpic/system-logging) - [ジョブのスケジューリング - cron と at](/articles/lpic/job-scheduling) - [LPIC-1 出題範囲完全ガイド](/articles/lpic/exam-scope-101-102) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # テキストストリームフィルタ - cat・sort・uniq・wc・head・tail Source: https://penguin-gym-linux.com/articles/lpic/text-stream-filters ## この記事で達成できること {#intro} - 標準入力から受け取ったテキストをフィルタで加工する型を組める - `sort` / `uniq` の組み合わせでログ集計を正確に実行できる - `head` / `tail` で大容量ファイルの必要部分だけを安全に確認できる - `cut` / `tr` / `nl` でフィールド抽出・文字変換・行番号付与ができる - フィルタを連結したワンライナーで頻出の集計タスクを処理できる LPIC-1 主題 103.2「フィルタを使ったテキストストリーム処理」の中核。フィルタは「標準入力を読み、加工し、標準出力へ書く」コマンド群で、パイプで連結することで強力な処理になる。 ## どのフィルタをいつ使うか {#flow} | やりたいこと | 使うフィルタ | 代表オプション | | -------------------- | --------------- | ------------------------------------- | | 行を並べ替える | `sort` | `-n` 数値 / `-r` 逆順 / `-k` キー指定 | | 重複を除去・集計する | `uniq` | `-c` 件数 / `-d` 重複のみ | | 件数を数える | `wc` | `-l` 行 / `-w` 単語 / `-c` バイト | | 先頭/末尾だけ見る | `head` / `tail` | `-n` 行数 / `tail -f` 追従 | | 列を抜き出す | `cut` | `-d` 区切り / `-f` フィールド | | 文字を置換/削除 | `tr` | `-d` 削除 / `-s` 連続圧縮 | | 行番号を付ける | `nl` / `cat -n` | `-b a` 全行付番 | `uniq` は連続する重複しか集約しないため、ほぼ常に `sort` と組み合わせる。これが試験でも実務でも最頻出のパターン。 ## 手順 {#steps} ### Step 1: ファイルを連結して標準出力に流す ```bash cat access.log cat -n script.sh cat file1 file2 > merged.txt ``` ```output 1 #!/bin/bash 2 echo "start" 3 exit 0 ``` `cat` は複数ファイルを連結する。`-n` で行番号、`-A` で改行・タブなどの不可視文字を可視化できる。1 ファイルを単に表示するだけなら `less` の方が大容量に強い。 ### Step 2: ソートして重複を集計する ```bash sort access.log | uniq -c | sort -nr | head -n 5 ``` ```output 143 GET /index.html 97 GET /login 61 POST /api/data 28 GET /favicon.ico 12 GET /robots.txt ``` 「ソート → `uniq -c` で件数化 → 件数で逆順ソート → 上位 5 件」は頻出アクセス集計の定番。`uniq -c` は直前の Step で `sort` 済みであることが前提。 ### Step 3: 行・単語・バイト数を数える ```bash wc -l access.log wc -lwc README.md ``` ```output 10234 access.log 120 856 5421 README.md ``` `-l` は行数、`-w` は単語数、`-c` はバイト数(`-m` は文字数)。パイプ末尾に置けば「条件に一致した件数」をそのまま得られる。 ### Step 4: 先頭・末尾を切り出す ```bash head -n 20 large.csv tail -n 50 syslog tail -f /var/log/nginx/access.log ``` ```output 2026-05-17 10:01:22 INFO start 2026-05-17 10:01:23 INFO ready ``` `tail -f` はファイルへの追記をリアルタイム表示する。ログ監視の基本。`head` と `tail` を組み合わせると「N 行目から M 行」のような範囲抽出もできる。 ### Step 5: 列抽出と文字変換 ```bash cut -d: -f1,7 /etc/passwd echo "Hello World" | tr 'a-z' 'A-Z' cat data.txt | tr -s ' ' | tr -d '\r' ``` ```output root:/bin/bash daemon:/usr/sbin/nologin HELLO WORLD ``` `cut -d: -f1` は `:` 区切りの 1 列目を抽出する。`tr` は文字単位の変換・削除で、`-s` は連続文字を 1 個に圧縮、`-d` は指定文字を削除する。Windows 由来の `\r` 除去によく使う。 ## なぜフィルタを連結するのか {#why} 各フィルタは「1 つのことだけをうまくやる」という Unix 哲学に従って設計されている。`sort` は並べ替えだけ、`uniq` は隣接重複処理だけを担う。単機能だからこそパイプで自由に組み合わせられ、巨大な専用ツールを書かずに集計・抽出を実現できる。 `uniq` が隣接重複しか扱わないのは、ストリームを一行ずつ処理して状態を持たない設計のため。全体の重複を扱うには事前にソートして同一行を隣接させる必要がある。この制約を理解していれば `sort | uniq` を反射的に書けるようになる。 ## トラブルシューティング {#troubleshooting} ### 症状: uniq -c で重複が集約されない **原因**: 入力がソートされていない **確認**: ```bash sort file | uniq -c ``` **対処**: `uniq` の前に必ず `sort` を入れる。または `sort -u` で「ソート + 重複除去」を一度に行う(ただし `-c` の件数は得られない)。 ### 症状: sort -n が期待通り並ばない **原因**: 数値列に空白や単位文字が混在している、またはキー位置の指定漏れ **確認**: ```bash sort -k2 -n data.txt ``` **対処**: `-k` でソート対象フィールドを明示し、`-t` で区切り文字を指定する。人間可読サイズ(1K, 2M)は `sort -h` を使う。 ### 症状: tail -f が更新を追わない **原因**: ログがローテーションで別 inode に切り替わった **確認**: ```bash tail -F /var/log/syslog ``` **対処**: `-f`(小文字)は inode を追従するため、ローテーション後は `-F`(大文字)でファイル名ベースの追従に切り替える。 ## 作業完了チェックリスト {#checklist} - [ ] `sort | uniq -c | sort -nr` で集計ワンライナーを実行した - [ ] `wc -l` をパイプ末尾で件数取得に使った - [ ] `head` / `tail` で大容量ファイルの必要部分のみ確認した - [ ] `cut -d -f` でフィールド抽出した - [ ] `tr` で文字変換・`\r` 削除を確認した ## まとめ {#summary} | 場面 | コマンド | 目的 | | --------- | ----------------------------- | -------------------- | | 集計 | `sort \| uniq -c \| sort -nr` | 出現頻度ランキング | | 件数 | `wc -l` | 行数カウント | | 先頭/末尾 | `head -n` / `tail -f` | 範囲抽出・追従監視 | | 列抽出 | `cut -d: -f1` | フィールド切り出し | | 文字変換 | `tr a-z A-Z` | 文字単位の置換・削除 | フィルタの連結はテキスト処理の基本パターン。より複雑なパターンマッチには正規表現と grep が必要になる。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全12記事と出題範囲マップ)](/lpic1) - [正規表現とgrep応用](/articles/lpic/regular-expressions) - [パイプとリダイレクト入門](/articles/tutorials/pipe-redirect-basics) - [コマンドライン基礎](/articles/lpic/command-line-basics) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # LPIC-1 対策に Udemy は必要?書籍・Ping-t との使い分け Source: https://penguin-gym-linux.com/articles/lpic/udemy-for-lpic1 ## この記事で達成できること {#intro} - 「LPIC-1 対策に Udemy が必要かどうか」を自分の状況に当てはめて判断できる - 書籍(あずき本・白本)・Ping-t・Udemy の役割分担を整理できる - Udemy を使う場合のコース選定基準と購入時の注意点がわかる - 動画・書籍・問題演習をどの順番で組み合わせるかを決められる ## 結論 - Udemy は必須ではない {#conclusion} 先に結論を示す。 **LPIC-1 対策に Udemy は必須ではない。ただし Linux 完全初心者が「最初の概念理解」に使う場合に限り、有効な選択肢になる。** LPIC-1 の試験範囲は書籍と問題演習(Ping-t・問題集)で網羅でき、動画教材は合格の必須要素ではない。一方で、Linux に触れた経験がゼロの状態でいきなり参考書を読み始めると、コマンドの動作イメージが掴めず挫折しやすい。この「読んでも動作がイメージできない」段階を映像で乗り越えるのが、LPIC-1 対策における動画講座の唯一にして最大の役割だ。 ::: warning 動画のみで試験対策を完結させるのは難しい。LPIC-1 は問題演習への慣れが合否を分けるため、どのルートを選んでも書籍または問題集・Ping-t との組み合わせが前提になる。 ::: ## 判断フロー {#flow} 自分の状況を上から順に確認する。最初に該当した行の判断に従う。 | 状況 | Udemy の要否 | 代わりに使うもの | | ---------------------------------------------------- | ------------ | ---------------------------------------------- | | Linux 実務経験が半年以上ある | 不要 | スピードマスター問題集・Ping-t で演習から開始 | | 試験まで 1 か月未満 | 不要 | 問題演習に全時間を投下(動画を見る時間がない) | | 書籍を読み進めるのが苦にならない | 不要 | あずき本 or 白本 候補 A でインプット → 演習 | | Linux 完全初心者で、参考書を読んで挫折した経験がある | 有効 | Udemy で全体像 → 書籍 → 演習 | | Linux 完全初心者で、文字より映像のほうが頭に入る | 有効 | 同上 | 判断に迷う場合の基準は 1 つ。**「参考書の最初の 2 章を読み通せるか」**。読み通せるなら書籍だけで進めてよい。読み通せない・イメージが湧かないなら、動画で概念を掴んでから書籍に戻るほうが結果的に速い。 ## 教材の役割分担 {#roles} LPIC-1 の学習は「インプット」「演習」「仕上げ」の 3 フェーズに分かれる。各教材が担当できるフェーズは決まっており、Udemy が代替できるのはインプットの入口だけだ。 | 教材 | インプット | 演習 | 仕上げ | 位置づけ | | ----------------------------------- | ---------- | ---- | ------ | ---------------------------- | | Udemy 動画講座 | 入口のみ | 不可 | 不可 | 全体像・概念の映像理解 | | あずき本 / 白本 候補 A | 主力 | 一部 | 不可 | 体系的な知識の積み上げ | | Ping-t / スピードマスター問題集 | 不可 | 主力 | 一部 | 分野別演習・試験形式への慣れ | | 本サイトの仮想ターミナル・LPIC 問題 | 不可 | 一部 | 主力 | コマンド実操作の確認 | この表が示すとおり、Udemy は書籍の競合ではない。書籍・問題演習に入る前の入口教材であり、「Udemy か書籍か」の二者択一で悩む必要はない。使うなら順番に組み込む、使わないなら書籍から始める、の二択だ。 各教材の価格・書誌情報・選び方の詳細は[LPIC-1 参考書比較ガイド](/articles/lpic/study-materials-comparison)を参照。 ## Udemy が向いている人・向かない人 {#fit} **向いている人**: - Linux 完全初心者で、コマンドの動作を画面で見て理解したい - 本だけだと集中が続かず、参考書で挫折した経験がある - まず全体像を掴んでから、書籍・問題演習に進みたい - 通勤時間などに音声・映像で学習を進めたい **向かない人**: - Linux 実務経験があり、知識の棚卸しと試験形式慣れが目的 - 試験直前で、問題演習だけに時間を使いたい - 書籍で体系的に読み進めるほうが定着するタイプ - 教材費を最小化したい(書籍+無料公開分の Ping-t で開始可能) ## 使う場合の学習パターン {#pattern} Udemy を組み込む場合の標準ルートは次の 4 段階だ。 1. **最初**: Udemy の動画で Linux 全体像と基本概念を掴む(1〜2 週間。全レクチャーを完璧に理解しようとしない) 2. **中盤**: あずき本または白本 候補 A で体系的に理解を積み上げる 3. **直前**: Ping-t / スピードマスター問題集で分野別に演習する 4. **仕上げ**: [LPIC-1 仮想ターミナル演習](/terminal?category=lpic)と [LPIC-1 学習ハブ](/lpic1)の問題でコマンド実操作と理解度を確認する 期間配分を含む学習計画の立て方は[LPIC-1 勉強時間・勉強方法ガイド](/articles/lpic/study-time-and-method)を参照。 ::: tip 動画パートの目的は「全体像の把握」であり、暗記ではない。動画で 100% 理解しようとして繰り返し視聴するより、概ね把握できた段階で書籍に進む進め方が定石だ。細部は書籍と問題演習で埋まる。 ::: ## コース選定チェックリスト {#course-checklist} Udemy の LPIC 関連コースは品質が講師によって異なる。購入前に以下を確認する。 - [ ] LPIC-1 の現行試験バージョン v5.0 の範囲に対応しているか(コース説明・カリキュラムで確認) - [ ] 最終更新日が古すぎないか(更新が止まったコースは試験範囲とずれるリスクがある) - [ ] レビュー数と評価スコアを確認したか(レビュー数が極端に少ないコースは判断材料が不足) - [ ] 受講時間が自分の確保できる期間に収まるか - [ ] 現在価格を公式サイトで確認したか(セール多用のため実勢価格は変動する。定価購入は避ける) (コース情報の確認先: [Udemy 公式サイト Linux トピック](https://www.udemy.com/topic/linux/)。コース内容・価格・評価は変動するため、購入前に該当コースページで最新情報を確認すること) ## まとめ {#summary} | 質問 | 回答 | | ------------------ | ---------------------------------------------------------------------------- | | Udemy は必須か | 必須ではない。書籍+演習のみで合格可能 | | 誰に有効か | Linux 完全初心者の「最初の概念理解」に限り有効 | | 単体で合格できるか | 難しい。書籍または問題集・Ping-t との組み合わせが前提 | | 買うタイミング | セール時。v5.0 対応・更新日・レビューを確認してから | | 組み込む順番 | 動画(入口)→ 書籍(体系理解)→ 演習(Ping-t・問題集)→ 仕上げ(実操作確認) | 迷ったら「参考書の最初の 2 章を読み通せるか」で判断する。読み通せるなら書籍から、読み通せないなら動画から始める。 ## 次に読む {#next} - [LPIC-1 参考書比較ガイド - あずき本・スピードマスター・白本の選び方](/articles/lpic/study-materials-comparison) - 書籍・Ping-t を含む全教材の比較と組み合わせパターン - [LPIC-1 勉強時間・勉強方法・最短攻略ガイド](/articles/lpic/study-time-and-method) - 教材決定後の期間別学習プラン - [LPIC-1 難易度・合格点・配点 完全FAQ](/articles/lpic/difficulty-and-passing-score) - 500 点合格・60 問・90 分の根拠 - [LPIC-1 仮想ターミナル演習](/terminal?category=lpic) - ブラウザ完結のコマンド実操作 # ユーザー・グループ管理 - useradd/passwd と /etc/passwd・shadow【LPIC-1 107.1】 Source: https://penguin-gym-linux.com/articles/lpic/user-group-administration ## この記事で達成できること {#intro} - `useradd` / `usermod` / `userdel` でユーザーを作成・変更・削除できる - `groupadd` / `groupmod` / `groupdel` / `gpasswd` でグループを管理できる - `passwd` / `chage` でパスワードと有効期限を制御できる - `/etc/passwd` / `/etc/shadow` / `/etc/group` / `/etc/gshadow` の各フィールドを読める - `/etc/skel` / `/etc/login.defs` がユーザー作成にどう影響するか説明できる - 試験頻出の `usermod -aG`(`-G` 単独指定の罠)を根拠付きで答えられる LPIC-1 主題 107.1「ユーザーとグループおよび関連するシステムファイルを管理する」の中核。アカウント管理は権限・セキュリティの土台になる。 ## ユーザー管理コマンドはどう使い分けるか {#commands-overview} ユーザー操作は「作成(`useradd`)・変更(`usermod`)・削除(`userdel`)」の3コマンドが基本。パスワードは別系統の `passwd`、有効期限は `chage` が担当する。 | 目的 | コマンド | 代表オプション | | ---------- | --------- | ----------------------------- | | 作成 | `useradd` | `-m` `-d` `-s` `-g` `-G` `-u` | | 変更 | `usermod` | `-aG` `-L` `-U` `-l` | | 削除 | `userdel` | `-r` | | パスワード | `passwd` | `-l` `-u` `-e` | | 有効期限 | `chage` | `-l` `-M` `-E` | ディストリビューションによっては対話的な `adduser`(Debian/Ubuntu 系の Perl スクリプト)も存在するが、LPIC-1 で問われるのは低レベルな `useradd` ファミリ。本記事はこちらを扱う。 ## ユーザーを作成・変更・削除する {#user-steps} `useradd` はデフォルトではホームディレクトリを作らない。`-m` を付けないと「ログインできてもホームがない」状態になりやすいので注意する。 ### Step 1: useradd でユーザーを作成する ```bash sudo useradd -m -s /bin/bash -c "Sato Taro" sato getent passwd sato ``` ```output sato:x:1001:1001:Sato Taro:/home/sato:/bin/bash ``` 主要オプション(man useradd): | オプション | 意味 | | ------------ | ------------------------------------------------ | | `-m` | ホームディレクトリを作成(`/etc/skel` をコピー) | | `-d DIR` | ホームディレクトリのパスを指定 | | `-s SHELL` | ログインシェルを指定 | | `-g GROUP` | プライマリ(主)グループを指定 | | `-G G1,G2` | 補助(サブ)グループを指定 | | `-u UID` | UID を明示指定 | | `-c COMMENT` | コメント(GECOS)欄を設定 | ::: tip `-g` は「プライマリグループ」、`-G` は「補助グループ」。1ユーザーにプライマリは1つだけ、補助は複数持てる。 ::: ### Step 2: passwd でパスワードを設定する ```bash sudo passwd sato ``` ```output New password: Retype new password: passwd: password updated successfully ``` `useradd` 直後のアカウントはパスワード未設定でロック状態のことが多い。`passwd` で設定して初めて通常ログインできる。 ### Step 3: usermod でユーザーを変更する ```bash sudo usermod -aG wheel,docker sato id sato ``` ```output uid=1001(sato) gid=1001(sato) groups=1001(sato),10(wheel),998(docker) ``` 主要オプション(man usermod): | オプション | 意味 | | ------------ | ----------------------------------------------------- | | `-aG G1,G2` | 補助グループへ**追加**(`-a` は append、既存維持) | | `-G G1,G2` | 補助グループを**置き換え**(`-a` なしは上書き) | | `-L` | パスワードをロック(`/etc/shadow` の先頭に `!` 付与) | | `-U` | パスワードのロック解除 | | `-l NEWNAME` | ログイン名を変更 | | `-g GROUP` | プライマリグループを変更 | ::: danger `usermod -G group user` を `-a` なしで実行すると、指定しなかった既存の補助グループから外れる。補助グループへ「追加」したいときは必ず `-aG` を使う。試験でも実務でも最頻出の事故。 ::: ### Step 4: userdel でユーザーを削除する ```bash sudo userdel -r sato ``` ```output (出力なし。-r でホームディレクトリとメールスプールも削除) ``` `userdel` 単体ではホームディレクトリは残る。`-r` を付けるとホームディレクトリとメールスプールも削除する。実行中プロセスを持つユーザーは削除に失敗することがある。 ## グループはどう管理するか {#group-steps} グループ操作は `groupadd`(作成)・`groupmod`(変更)・`groupdel`(削除)、メンバー管理は `gpasswd` が担当する。`/etc/group` と `/etc/gshadow` が実体。 ### Step 1: groupadd でグループを作成する ```bash sudo groupadd -g 1500 developers getent group developers ``` ```output developers:x:1500: ``` `-g GID` で GID を明示指定する。省略時は `/etc/login.defs` の範囲から自動採番される。 ### Step 2: groupmod でグループを変更する ```bash sudo groupmod -n devs developers sudo groupmod -g 1600 devs getent group devs ``` ```output devs:x:1600: ``` `-n NEWNAME` でグループ名を変更、`-g GID` で GID を変更する。 ### Step 3: gpasswd でメンバーを管理する ```bash sudo gpasswd -a sato devs sudo gpasswd -d sato devs getent group devs ``` ```output Adding user sato to group devs Removing user sato from group devs devs:x:1600: ``` `gpasswd -a user group` でメンバー追加、`-d user group` で削除(man gpasswd)。`gpasswd -A user group` でグループ管理者を指定できる。 ### Step 4: groupdel でグループを削除する ```bash sudo groupdel devs ``` ```output (出力なし) ``` あるユーザーのプライマリグループになっているグループは削除できない。先にユーザーのプライマリグループを変更する必要がある。 ## /etc/passwd と /etc/shadow は何が違うのか {#files} `/etc/passwd` はアカウントの基本情報、`/etc/shadow` は暗号化パスワードと有効期限を保持する。パスワードを `passwd` 本体から分離したのがシャドウパスワードの仕組み。 ### /etc/passwd の7フィールド ```output sato:x:1001:1001:Sato Taro:/home/sato:/bin/bash ``` コロン区切りで7フィールド(man 5 passwd): | # | フィールド | 例 | 意味 | | --- | ---------- | ------------ | ----------------------------------- | | 1 | ユーザー名 | `sato` | ログイン名 | | 2 | パスワード | `x` | `x` は実体が `/etc/shadow` にある印 | | 3 | UID | `1001` | ユーザーID | | 4 | GID | `1001` | プライマリグループID | | 5 | GECOS | `Sato Taro` | コメント(氏名等) | | 6 | ホーム | `/home/sato` | ホームディレクトリ | | 7 | シェル | `/bin/bash` | ログインシェル | ::: tip 第7フィールドが `/sbin/nologin` や `/bin/false` のアカウントは、サービス専用でインタラクティブログインできない。 ::: ### /etc/shadow の9フィールド ```output sato:$6$xyz...:19500:0:99999:7::: ``` コロン区切りで9フィールド(man 5 shadow): | # | フィールド | 意味 | | --- | ------------------ | ----------------------------------------- | | 1 | ユーザー名 | `/etc/passwd` と対応 | | 2 | 暗号化パスワード | ハッシュ。`!` や `*` 始まりはロック・無効 | | 3 | 最終変更日 | 1970-01-01 からの日数 | | 4 | 最小変更日数 | 変更後この日数は再変更不可 | | 5 | 最大有効日数 | この日数で要変更 | | 6 | 警告日数 | 期限切れ前に警告する日数 | | 7 | 猶予日数 | 期限切れ後ログイン可能な日数 | | 8 | アカウント有効期限 | 1970-01-01 からの日数 | | 9 | 予約 | 未使用 | ::: warning `/etc/shadow` は root のみ読める(パーミッション `0640` 等)。一般ユーザーから見えないことで、ハッシュをオフライン解析される攻撃を防いでいる。 ::: ### /etc/group と /etc/gshadow ```output developers:x:1500:sato,suzuki ``` `/etc/group` は4フィールド(man 5 group): グループ名、パスワード(`x`)、GID、メンバー一覧(補助グループとしての所属者をカンマ区切り)。`/etc/gshadow` はグループパスワードと管理者・メンバーを保持する(man 5 gshadow)。 ::: tip `/etc/group` のメンバー欄に出るのは、そのグループを**補助グループ**として持つユーザーのみ。プライマリグループとして所属するユーザーはここには列挙されない(`/etc/passwd` の GID で表現される)。 ::: ## /etc/skel と /etc/login.defs の役割 {#skel-logindefs} `/etc/skel` は新規ホームの雛形、`/etc/login.defs` は `useradd` のデフォルト値を定義する。どちらもユーザー作成時の挙動を左右する。 `useradd -m` でホームを作る際、`/etc/skel` 配下のファイル(`.bashrc`、`.profile` 等)が新しいホームへコピーされる。全ユーザーに配りたい初期設定はここに置く。 `/etc/login.defs` は UID/GID の自動採番範囲(`UID_MIN` / `UID_MAX` 等)、パスワード有効期限のデフォルト(`PASS_MAX_DAYS` / `PASS_MIN_DAYS` / `PASS_WARN_AGE`)、ホーム自動作成の可否(`CREATE_HOME`)などを定義する(man 5 login.defs)。 ```bash grep -E '^(UID_MIN|UID_MAX|PASS_MAX_DAYS)' /etc/login.defs ``` ```output UID_MIN 1000 UID_MAX 60000 PASS_MAX_DAYS 99999 ``` ## パスワードの有効期限はどう制御するか {#chage} `chage` は `/etc/shadow` の期限フィールドを操作する専用コマンド。`passwd` のロック系オプションと併せて押さえる。 ### chage で期限を確認・設定する ```bash sudo chage -l sato ``` ```output Last password change : May 30, 2026 Password expires : never Password inactive : never Account expires : never Minimum number of days between password change : 0 Maximum number of days between password change : 99999 Number of days of warning before password expires : 7 ``` 主要オプション(man chage): | オプション | 意味 | | ---------- | ----------------------------------------------- | | `-l` | 現在の期限設定を一覧表示 | | `-M DAYS` | パスワード最大有効日数 | | `-m DAYS` | パスワード最小変更日数 | | `-W DAYS` | 期限切れ前の警告日数 | | `-E DATE` | アカウント有効期限(`YYYY-MM-DD`) | | `-d DATE` | 最終変更日(`-d 0` で次回ログイン時に変更強制) | ### passwd でロックと変更強制を行う ```bash sudo passwd -l sato sudo passwd -u sato sudo passwd -e sato ``` ```output passwd: password expiry information changed. ``` `passwd -l` でロック(`/etc/shadow` の先頭に `!` を付ける)、`-u` で解除、`-e` で即時失効させ次回ログイン時にパスワード変更を強制する。 ## よくあるミスと対処法 {#pitfalls} ### usermod -G を -a なしで使い既存グループが外れる `usermod -G docker sato` は補助グループを `docker` だけに**置き換える**ため、それまで所属していた `wheel` 等から外れる。追加したいなら `usermod -aG docker sato` を使う。 ### useradd で -m を付けずホームがない `useradd sato` だけだとホームディレクトリが作られず、ログイン後に `/etc/skel` の設定も適用されない。`useradd -m sato` を基本とする(`/etc/login.defs` の `CREATE_HOME yes` で既定化も可能)。 ### ロックされたパスワードの ! 表記を「壊れた」と誤認する `/etc/shadow` のハッシュ先頭の `!` や `!!`、`*` はロック/無効を表す正常な状態。`passwd -l` や `usermod -L` で付与される。`passwd -u` または `usermod -U` で解除する。 ### userdel だけでホームが残りディスクを圧迫する `userdel sato` はホームディレクトリを残す。完全に消すなら `userdel -r sato`。残ったホームは元の UID 所有のまま放置され、後で同じ UID が再利用されると所有権が混乱する。 ### グループ変更が反映されない `usermod -aG` での補助グループ追加は、既存のログインセッションには即反映されない。新しいログイン(または `newgrp group`)で有効になる。`id user` で `/etc/group` 上の所属を確認できる。 ## トラブルシューティング {#troubleshooting} ### 症状: useradd で「user already exists」 **原因**: 同名ユーザーが既に存在する、または UID が衝突している **確認**: ```bash getent passwd sato getent passwd 1001 ``` **対処**: 既存アカウントを確認し、不要なら `userdel` で削除。UID 衝突なら `-u` で空いている UID を指定する。 ### 症状: ユーザーが正しいグループにいるのに権限がない **原因**: 補助グループ追加後にログインし直していない(セッションに反映されていない) **確認**: ```bash id sato getent group docker ``` **対処**: 一度ログアウトして再ログインする。即時反映したい単発操作なら `newgrp docker` でグループを切り替える。 ### 症状: groupdel で「cannot remove the primary group」 **原因**: 削除しようとしたグループが、あるユーザーのプライマリグループになっている **確認**: ```bash getent passwd | awk -F: '$4=="1500"{print $1}' ``` **対処**: 該当ユーザーのプライマリグループを `usermod -g other user` で変更してから `groupdel` する。 ## 作業完了チェックリスト {#checklist} - [ ] `useradd -m -s /bin/bash` でホーム付きユーザーを作成した - [ ] `passwd` でパスワードを設定した - [ ] `usermod -aG`(`-a` 付き)で補助グループへ追加した - [ ] `/etc/passwd` と `/etc/shadow` の各フィールドを読めた - [ ] `chage -l` でパスワード有効期限を確認した - [ ] `userdel -r` でホームごと削除した ## まとめ {#summary} | 場面 | コマンド | 目的 | | ---------------- | ------------------------------ | ---------------------- | | 作成 | `useradd -m -s /bin/bash user` | ホーム付きユーザー作成 | | パスワード | `passwd user` | パスワード設定 | | 補助グループ追加 | `usermod -aG group user` | 既存維持で追加 | | ロック | `passwd -l` / `usermod -L` | アカウント無効化 | | 有効期限 | `chage -M 90 user` | パスワード期限設定 | | 削除 | `userdel -r user` | ホームごと削除 | | 参照 | `getent passwd` / `id` | アカウント情報確認 | ユーザー・グループ管理は権限とセキュリティの基盤。次はファイル管理やシェル環境と組み合わせると、運用知識がつながる。 ## 次に読む {#next} - [LPIC-1 学習ハブ(全記事と出題範囲マップ)](/lpic1) - [基本的なファイル管理](/articles/lpic/basic-file-management) - [シェル環境変数の設定](/articles/lpic/shell-environment) - [セキュリティ管理](/articles/lpic/security-administration) - [LPIC-1 理解度チェッククイズ](/quiz?category=lpic) - [仮想ターミナルで LPIC-1 演習](/terminal?category=lpic) # LPIC-1 とは - 制度・試験構成・有効期限・v5.0 を徹底解説 Source: https://penguin-gym-linux.com/articles/lpic/what-is-lpic1 ## この記事で達成できること {#intro} - LPIC-1 が何の資格か、LPI が何の団体かを説明できる - 試験コード・出題数・試験時間・合否基準の構造を把握できる - 認定有効期限と再認定の 3 方式を理解できる - 最新バージョン v5.0 が何を意味するかを説明できる - LinuC との違いを整理し、自分に適した選択ができる LPIC-1 を受験する前に制度の全体像を把握することで、学習計画の精度が上がり、無駄なく試験準備を進められる。 ## LPIC-1 の制度概要 {#overview} LPIC-1(Linux Professional Institute Certification Level 1)は、カナダのオタワに本部を置く非営利組織 LPI(Linux Professional Institute)が認定する Linux 技術者資格の入門レベルにあたる。 LPI は 2026-05 時点で 180 以上の国・地域で認定を展開し、取得者は 350,000 人以上にのぼる国際資格である。(出典: LPI 公式, 2026-05 時点) 資格の位置づけとして、LPIC には 3 段階のレベルがある。 | レベル | 資格名 | 対象スキル | | ------- | ------ | ---------------------------------------- | | Level 1 | LPIC-1 | Linux 基本操作・ファイル管理・シェル基礎 | | Level 2 | LPIC-2 | サーバー管理・ネットワーク設定 | | Level 3 | LPIC-3 | 高度なシステム管理・セキュリティ等 | LPIC-1 は Level 1 に相当し、Linux の入門から実務基礎までをカバーする。 ## 試験構成(101 + 102) {#exam-structure} LPIC-1 は 2 科目の合格によって取得できる。いずれか一方だけ合格しても LPIC-1 の認定は受けられない。(出典: LPI 公式, 2026-05 時点) | 科目 | 試験コード | 主な出題範囲 | | -------- | ---------- | ------------------------------------------------------------ | | 101 試験 | 101-500 | システムアーキテクチャ・ファイル操作・シェル・パッケージ管理 | | 102 試験 | 102-500 | シェルスクリプト・ネットワーク・セキュリティ・システム管理 | ### 試験形式 - 問題数: 各 60 問(出典: LPI 公式, 2026-05 時点) - 試験時間: 各 90 分(出典: LPI 公式, 2026-05 時点) - 出題形式: 択一選択・複数選択・記述式 - 受験方式: Pearson VUE テストセンター または オンライン監視試験(OnVUE) 合否スコアは 200〜800 点スケールで算出され、合格点は 500 点。LPI 公式 FAQ には「各LPI試験は、200から800の間でランク付けされており、合格点は500点となっています」と明記されている。(出典: LPI 公式 FAQ `https://www.lpi.org/ja/about-lpi/frequently-asked-questions`, 取得日: 2026-05-23) ### 受験順序 {#exam-order} 101 試験・102 試験はどちらから受験しても構わない。ただし、コマンドライン基礎・ファイル操作を扱う 101 試験から学ぶと、102 試験の内容がスムーズに理解できるケースが多い。 ### 受験料 {#exam-fee} 101 試験・102 試験それぞれ 15,000 円(税別)。2 科目取得で合計 30,000 円が目安。(出典: LPI Japan 公式サイト, 2026-05 時点) ::: tip 受験の有効期間: 101 試験に合格後、102 試験を 5 年以内に合格する必要がある。期間を超過すると 101 試験の合格が失効し、再受験が必要になる。 ::: ## 認定有効期限と再認定 {#renewal} LPIC-1 の認定有効期限は 5 年間。有効期限を超過すると認定は失効する。(出典: LPI 公式 renewal ページ, 2026-05 時点) ### 再認定の 3 方式 {#renewal-methods} | 方式 | 内容 | | -------------------------------- | ------------------------------------------------- | | 上位資格の取得 | LPIC-2 を取得することで LPIC-1 も自動的に更新 | | 同レベルの再試験 | 最新バージョンの 101-500 / 102-500 を再受験・合格 | | LPI ラーニングパートナー認定試験 | LPI 認定パートナーが提供する試験での更新 | 更新手続きは LPI の LPI ID 管理ポータルから確認できる。(出典: LPI 公式 renewal ページ, 2026-05 時点) ## 最新バージョン v5.0 {#version} 2019 年 4 月に LPIC-1 バージョン 5.0 が施行された。現行の試験コード(101-500 / 102-500)の末尾 `-500` がバージョン 5.0 に対応することを示す。(出典: LPI 公式, 2026-05 時点) v5.0 の主な変更点は以下のとおり。 - `systemd` が標準として位置づけられ、SysV init との並立から移行 - セキュリティ関連 Objective の強化(パーミッション・暗号化基礎) - クラウド・仮想化に関連するトピックの拡充 旧バージョン(4.0 以前)の試験コードは廃止済み。旧バージョンで取得した認定は有効期限内であれば継続するが、更新時は最新バージョンで受験する必要がある。 ## 取得後のキャリアパス {#career} LPIC-1 はエントリーレベルのポジションから実務経験を積む上でのベースラインとして機能する。 **直接つながるスキル領域**: - Linux サーバー運用・監視 - インフラ構築(オンプレミス / クラウド) - DevOps・SRE の基礎 - ネットワーク管理の入口 **上位資格への進路**: LPIC-1 を取得後、LPIC-2(サーバー管理の応用)へ進むルートが一般的。他に CompTIA Linux+(LPIC-1 と相互認証関係あり)への展開もある。 ## よくある疑問 {#faq} ### LinuC と LPIC-1 は何が違う? {#faq-linuc} LinuC(Linux 技術者認定試験)は LPI Japan が 2018 年に独自に立ち上げた国内向け資格。日本語で出題・回答できる点と、クラウド・DevOps 領域を重視した出題傾向が特徴。LPIC-1 は LPI(カナダ)が認定するグローバル資格で、国際的な通用性がある。試験の内容・制度は別物のため、学習テキストや問題集を購入する際は両者を混同しないよう注意が必要。(出典: LPI Japan 公式サイト, 2026-05 時点) ### 試験は日本語で受験できる? {#faq-language} 101-500 / 102-500 は日本語での受験が可能。テストセンター予約時に言語を選択する。英語でも受験できる。(出典: LPI Japan 公式サイト, 2026-05 時点) ### LPI ID とは何か? {#faq-lpi-id} LPI ID は LPI が発行する個人識別子で、試験申込・成績確認・認定管理に使用する。Pearson VUE での受験申込前に LPI 公式サイトで取得する必要がある。LPI ID は一生涯に 1 つのみ発行され、変更できないため正確な情報で登録する必要がある。(出典: LPI 公式, 2026-05 時点) ### 認定実績のデータはどこで確認できる? {#faq-stats} LPI 公式サイトには 350,000 人以上・180 以上の国・地域という認定実績が掲載されている。LPI ID を保有する受験者は LPI の認定確認ポータルで自身の認定状況を第三者に公開することもできる。(出典: LPI 公式, 2026-05 時点) ## まとめ {#conclusion} - LPIC-1 は LPI が認定するグローバル Linux 資格の Level 1 - 101-500 / 102-500 の 2 科目合格が必要。各 90 分 60 問 - 認定有効期限は 5 年間。3 方式で再認定可能 - 現行バージョンは v5.0(2019 年 4 月〜) - LinuC とは別の資格制度であり混同に注意 ## 次に読む {#next} - [コマンドライン基礎 - シェル・bash・コマンド実行の仕組み](/articles/lpic/command-line-basics) - [テキストストリームとフィルタ](/articles/lpic/text-stream-filters) - [正規表現による検索](/articles/lpic/regular-expressions) - [シェル環境のカスタマイズ](/articles/lpic/shell-environment) - [LPIC-1 概要 (lpi.org)](https://www.lpi.org/our-certifications/lpic-1-overview/) - [LPIC-1 概要 日本語版 (lpi.org/ja)](https://www.lpi.org/ja/our-certifications/lpic-1-overview/) - [認定更新・再認定 (lpi.org)](https://www.lpi.org/our-certifications/renewal/)