使ってますか？ググる前の、manコマンド（日本語manコマンドのススメ）



マカーのみなさん、ターミナルからコマンドを打とうとした時、オプションがわからなくなったことありますよね？そんなとき、あなたならどうしますか？

1. ググる
1. `-h` や `--help` オプションをつけてみる
1. `man` コマンドを使う

色々方法はありますが、今回は `man` コマンドを推す記事になっております。

エンジニアたるや、一度CLIを開いたんだからキーボードからはなるべく手を離さない方がイケてますよね（`w3m` があるやろやい、という変態な方、どうぞご自愛くださいませ）。シェル芸人への第一歩と言っても過言ではないかもしれません。


# manコマンドとは？

```bash:shell
$ man ls
```

このように、ターミナルの全画面でコマンドの説明が表示されます。

コマンドによって構成はまちまちですが、基本的にはコマンドとオプションのフォーマット、説明、各オプションの詳細説明という流れになっています。

それから、 `man` コマンドはローカルにインストールされていますので **ネットがなくても見られます** 。これは「ググる」に対する大きなアドバンテージですね！電車や飛行機の移動でオフラインになったときのMacbookをただの文鎮にしたくなければ、是非manで各コマンドのマニュアルを読んでみましょう。


```bash
LS(1)                                                                                General Commands Manual                                                                                LS(1)

NAME
     ls – list directory contents

SYNOPSIS
     ls [-@ABCFGHILOPRSTUWabcdefghiklmnopqrstuvwxy1%,] [--color=when] [-D format] [file ...]

DESCRIPTION
     For each operand that names a file of a type other than directory, ls displays its name as well as any requested, associated information.  For each operand that names a file of type
     directory, ls displays the names of files contained within that directory, as well as any requested, associated information.

     If no operands are given, the contents of the current directory are displayed.  If more than one operand is given, non-directory operands are displayed first; directory and non-directory
     operands are sorted separately and in lexicographical order.

     The following options are available:

     -@      Display extended attribute keys and sizes in long (-l) output.

     -A      Include directory entries whose names begin with a dot (‘.’) except for . and ...  Automatically set for the super-user unless -I is specified.
...
```



# 英語につらみ？（日本語コマンド manj）

`man` コマンドは詳細が載っていていいんだけど、全部英語で辛いやん？という方に朗報です。

Macの方はこちら

```bash:shell
$ brew install sh0nk/tap/man-japanese
```

これで、

```bash:shell
$ manj ls
```

このように `manj` コマンドが使えるようになり、日本語マニュアルが表示できるようになります。（対応していないコマンドもあります。）

```bash:manj ls
LS(1)                                                                                    ユーザーコマンド                                                                                   LS(1)

名前
       ls - ディレクトリの内容をリスト表示する

書式
       ls [オプション]... [ファイル]...

説明
       FILE (デフォルトは現在のディレクトリ) に関する情報を一覧表示します。 -cftuvSUX のいずれも指定されず、 --sort も指定されていない場合、 要素はアルファベット順でソートされます。

       長いオプションで必須となっている引数は短いオプションでも必須です。

       -a, --all
              . で始まる要素を無視しない
...
```

ちなみにこちらの `manj` コマンドは私がHomebrewで用意したもので、こちらの記事で作った過程も紹介していますのでよろしければどうぞ。

https://qiita.com/sh0nk/items/6bbc241b7c0d9f18fcfa

# ざっくりとした見方

ここで、書式（コマンドのフォーマット）について軽く触れておきます。lsのケースはシンプル過ぎるので、manコマンド自体の例を持って説明してみます。

```bash:manj man
書式
       man [-acdfFhkKtwW] [--path] [-m system] [-p string] [-C config_file] [-M pathlist] [-P pager] [-B browser] [-H htmlpager] [-S section_list] [section] name ...
```

最初に登場する `man` がコマンド自身、その後は引数とオプションです。このとき `[]` で囲まれている部分が **任意オプション** ですので、指定しても、しなくてもよいものです。その後の、 `[]` がない部分が **必須** のパラメータです。

そのため、 `man` コマンドの場合は、 `name` として何のコマンドのマニュアルが読みたいかを渡してあげる必要があります。また、 `...` があるので、 `name` は複数指定することが可能です。 

これらの文法は、POSIXとして規定されています。


:::note info
lsとcdの違い

lsとcdの違いは何ですか？と聞かれた時すぐにわかるでしょうか？lsはファイルリストを表示して、cdはカレントディレクトリを・・
そうなんですが、そもそもコマンドのつくりが違います。

lsは通常のコマンドなのですが、cdはシェルに付属した、シェルビルトインコマンドです。そのことはmanコマンドの結果からもわかります。言われてみれば、カレントディレクトリを変更するということ自体がシェルの中での話なので、当たり前といえば当たり前のことですね。

> NAME
     builtin, !, %, ., :, @, {, }, alias, alloc, bg, bind, bindkey, break, breaksw, builtins, case, cd, chdir, command, complete, continue, default, dirs, do, done, echo, echotc, elif, else,
     end, endif, endsw, esac, eval, exec, exit, export, false, fc, fg, filetest, fi, for, foreach, getopts, glob, goto, hash, hashstat, history, hup, if, jobid, jobs, kill, limit, local, log,
     login, logout, ls-F, nice, nohup, notify, onintr, popd, printenv, pushd, pwd, read, readonly, rehash, repeat, return, sched, set, setenv, settc, setty, setvar, shift, source, stop, suspend,
     switch, telltc, test, then, time, times, trap, true, type, ulimit, umask, unalias, uncomplete, unhash, unlimit, unset, unsetenv, until, wait, where, which, while – shell built-in commands

:::




# 使い方例 (1) grepコマンド

早速ですが、このコマンドはどういう意味でしょうか？

```bash:shell
$ grep -E 'warn|error' -i -A2 -B3 /var/log/install.log`
```

知っとるわ、という方には実につまらない話ですが、まあ一例なので少しお付き合いください。
まずは、 `grep` コマンドが何なのかからおさらいしてみましょう。

```bash:manj grep
名前
       grep, egrep, fgrep - パターンにマッチする行を表示する

書式
       grep [OPTIONS] PATTERN [FILE...]
       grep [OPTIONS] [-e PATTERN | -f FILE] [FILE...]

説明
       grep は FILE で名前を指定されたファイルを検索して、与えられた PATTERN にマッチする部分を含む行を探します。 ファイルが指定されていない場合や、 ファイル名の代わりに 1 個のマイナス記号 “-”
       が指定されている場合は、 grep は標準入力から検索します。 デフォルトでは、 grep はマッチした行を表示します。

       さらに、兄弟プログラム egrep と fgrep は、それぞれ grep -E と grep -F と同じです。 これらの兄弟プログラムは非推奨ですが、後方互換性のために用意されています。
```

ふむふむ、ファイルの中からパターンにマッチした行を返却するコマンドなわけですね。 `/var/log/install.log` の中からパターンにマッチする行を返すということがわかりました。


## `-E` オプション

`-E` オプションは何でしょう？

```:manj grep
正規表現の選択
    -E, --extended-regexp
       PATTERN を拡張正規表現 (ERE) として扱います (下記参照)。
```

拡張正規表現というのがあり、パターンを拡張正規表現として解釈するわけですね。

```bash:manj grep
正規表現
       正規表現とは、一群の文字列を一まとめにして表現するパターンのことです。 正規表現の構成方法は、数式によく似ています。すなわち、さまざまな演算子を
       使い、小さな表現を組み合わせて構成するのです。

       grep は、「基本」正規表現 (BRE)、「拡張」正規表現 (ERE)、「Perl の」正規表現 (PCRE) という 3 種類の正規表現文法を扱うことができます。 GNU grep
       では、「基本」と「拡張」の文法の間で、利用できる機能に違いはありません。 他の実装では、基本正規表現は拡張正規表現ほど強力ではないものです。 ここでは、拡張正規表現について説明し、
       基本正規表現との相違については、後で簡単にまとめることにします。 なお、Perl 互換の正規表現にはより多くの機能があり、 pcresyntax(3) や pcrepattern(3) で詳細に解説されていますが、 PCRE
       が用意されているシステムでしか利用できません。
```

なるほど、正規表現にも複数種類があり、このマニュアル中のこの部分では基本的には「拡張正規表現」の方について書かれているとのことです。

その違いは、

```bash:manj grep
基本正規表現と拡張正規表現
    基本正規表現では、メタ文字 ?, +, {, |, (, ) は、その特殊な意味を失います。バックスラッシュを付けた \?, \+, \{, \|, \(, \) を代わりに使用してください。
```

ということがわかりました。今回の例の `-E 'warn|error'` には `|` が含まれているので、「基本正規表現」では無効となるようです。 `-e` なら使ったことあったんだけど、という方、 `-e 'warn|error'` だと実はうまく動かず、わざわざ `-e 'warn\|error'` と書いてあげる必要があることがわかります。

この `|` の意味は、

```bash:manj grep
選択
    2 つの正規表現は中置き型演算子の | で繋ぐことができます。 結果としてできあがる正規表現は、 どちらかの部分表現にマッチするどんな文字列にもマッチします。
```

と書かれています。

すなわち、 `-E 'warn|error'` は、 `warn` または `error` という文字列を探すオプションだとわかりました。




## `-i` オプション

`-i` オプションは何でしょうか？

```bash:manj grep
-i, --ignore-case
        PATTERN と入力ファイルの双方で、アルファベットの大文字と小文字を 区別しないようにします。
```

なるほど、パターンが大文字でも小文字でもマッチするんですね。つまり、ここでは `error` のみならず、 `ERROR` でも、はたまた `ErRoR` でもマッチするというわけです。


## `-A`, `-B` オプション

`-A2 -B3` オプションは何でしょうか？

```bash:manj grep
-A NUM, --after-context=NUM
        NUM で指定した行数だけ、パターンにマッチした行の後に続く文脈も表示します。 マッチした行を含むグループ同士の間には、グループを区切る印 (--) からなる行を置きます。 -o や
        --only-matching と同時に使うと、このオプションは効果がなく、警告メッセージを出します。

-B NUM, --before-context=NUM
        NUM で指定した行数だけ、パターンにマッチした行に先行する文脈も表示します。 マッチした行を含むグループ同士の間には、グループを区切る印 (--) からなる行を置きます。 -o や
        --only-matching と同時に使うと、このオプションは効果がなく、警告メッセージを出します。
```

なるほど、パターンにマッチした行の前や後の行を表示するオプションだということがわかりますね。今回は、 `-A2 -B3` ですので、マッチした行の前2行、後ろ3行も表示するということのようです。それぞれ after/before の頭文字だったんですね。


## 実行

実際に上記のコマンドを実行したときの結果は次のようになりました。（わかりやすいように色がついていた部分に \*\* を加えています。）

```bash:shell
2021-11-15 07:48:43-08 MacBook-Pro Installer Progress[51]: Progress app is loading…
2021-11-15 07:48:43-08 MacBook-Pro Installer Progress[51]: Progress app is running with no progress set
2021-11-15 07:48:43-08 MacBook-Pro Installer Progress[51]: Progress app is running…
2021-11-15 07:48:43-08 MacBook-Pro softwareupdated[295]: SUOSUServiceDaemon: **Error** reading /var/folders/zz/zyxvpxvq6csfxvn_n00000s0000068/C/com.apple.OSUpdate.SUOSUServiceDaemon.state: **Error** Domain=NSCocoaErrorDomain Code=260 "The file “com.apple.OSUpdate.SUOSUServiceDaemon.state” couldn’t be opened because there is no such file." UserInfo={NSFilePath=/var/folders/zz/zyxvpxvq6csfxvn_n00000s0000068/C/com.apple.OSUpdate.SUOSUServiceDaemon.state, NSUnderlyingError=0x1387259a0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
2021-11-15 07:48:43-08 MacBook-Pro softwareupdated[295]: SUOSUServiceDaemon: Use standard 6 hour auto update interval
2021-11-15 07:48:43-08 MacBook-Pro softwareupdated[295]: authorizeWithEmptyAuthorizationForRights: Requesting provided rights: 1
--
	}
2021-11-15 07:48:44-08 MacBook-Pro softwareupdated[295]: Adding client SUUpdateServiceClient pid=221, uid=0, installAuth=NO rights=(), transactions=0 (/usr/libexec/mobileassetd)
2021-11-15 07:48:44-08 MacBook-Pro Language Chooser[208]: Setup: Factory cable attached: 0
...
```


4行目に大文字小文字が混在した `Error` がマッチしており、前後それぞれ3行と2行が取られているのがわかりますね。



# 使い方例 (2) findコマンド

次は別の趣旨の例を考えてみましょう。上記のgrepコマンドでは、既に用意されたオプションの正体を調べましたが、実際にはコマンドを書くときに、どのようなオプションをどう指定してあげるかを考えなければいけないケースが多いと思います。

今回はこれにならって、findコマンドで古いログファイルを探し出し、消したいとします。

**"1日以上前に作られた、ファイルサイズが1MB以上のファイルを消したい"** 場合のコマンドを書くとします。findを使い慣れていないとオプションの細部を思い出すのは難しいですよね。確か -mtime と -size というオプションがあったはずだったな、と思い出し、

```bash:shell
$ find /var/log -mtime 24 -size 1M
```

こう書いたとします。

わかる人はわかると思いますが、間違っていますよね。3つの間違いが含まれています。manコマンドで調べてみましょう。

## `-mtime` オプション

```bash:manj find
-mtime n
        ファイルの最終更新時間が、 n*24 時間前と比較して、 それよりも前か後かちょうど同じかをテストします。 何日前かを計算する際、 時間差を 24 時間で割った結果を丸めるため、
        ファイル更新時間の解釈にあたって、 その処理がどのように影響するかについては、 -atime の説明を参照してください。
```

まず `-mtime` の説明によると、このオプションは「時間」ではなく「何日前」かを指定するとありますね。前か後か、というのはどうやってテストするんでしょうか？

テストの項目を見ると、

```bash:manj find
テスト (test)
    テストの中には、 -newerXY や -samefile のように、 その時点で対象となっているファイルと、 コマンドラインから指定する参照ファイル (reference file) との間で、 比較を行うものがあります。
    そのようなテストが指定された際に、 参照ファイルに対する解釈は、 -H, -L, -P といったオプションや、 先行する -follow の存在によって決定します。 ただし、 参照ファイルが調べられるのは、
    コマンドラインが解析されるときの 1 回だけです。 参照ファイルが調べられなかった場合 (たとえば、 stat(2) システムコールに失敗した場合)、 エラーメッセージが出力されて、 find は 0
    以外のステータスを返して終了します。

    数値引数である n は、 以下のテスト、 -amin, -mtime, -gid, -inum, -links, -size, -uid, -used において指定できます。 そして以下のような指定方法があります。

    +n     n より大きい。

    -n     n より小さい。

    n      n にちょうど等しい。

```

とあります。 `-mtime 1` としてしまうと、 **ちょうど** 1日前に更新されたファイルでないといけないので、それより前を指定する場合は、 `-mtime +1` と書く必要があることがわかりました。

## `-size` オプション

```:manj find
-size n[cwbkMG]
        ファイルが n 単位分の容量と比較して、 それよりも小さいか大きいかちょうど同じかをテストします。 ここでは丸め処理が行われます。 以下のサフィックスが利用できます。

        `b'    512 バイトブロックを表します (サフィックスの指定がない場合は、 これがデフォルトとなります)。

        `c'    バイトを表します。

        `w'    2 バイトワードを表します。

        `k     キビバイトを表します (kibibyte, KiB, 1024 バイト単位)。

        `M'    メビバイトを表します (mebibyte, MiB, 1024 * 1024 単位 = 1048576 バイト)。

        `G'    ギビバイトを表します (gibibyte, GiB, 1024 * 1024 * 1024 単位 = 10737418241073741824)。

        サイズというのは単純に、 システムコールの lstat (あるいは stat) に含まれている struct stat の メンバー st_size のことであり、 上に示すようにして切り上げられます。言い換えると、
        その結果は ls -l で得られるものと一致します。-printf における `%k' と `%b' の書式指定子は、 スパースファイルに対して、 異なる扱いをする点に注意してください。 サフィックス `b' は、
        常に 512 バイトブロックを表していて、 1024 バイトブロックを表すことはありません。 この点は、 -ls の動作とは異なるところです。

        プレフィックスの + と - は、 ごく普通に、 それより上、 それより下、 を表します。 したがって、 n とちょうど同じ数には一致しません。 サイズとは、
        次の数単位に向けて切り上げられることに注意してください。 つまり -size -1M というのは、 -size -1048576c と同じではありません。 前者は、 空のファイルにしか一致しません。 また後者は、
        バイトサイズが 0 から 1,048,575 までのファイルに一致します。
```

`-size` の方も同様に、1MB以上であれば、`-size +1M` と書かなければいけないことがわかります。
また、このとき `1MB` は1メガバイト (1000 * 1000 bytes) ではなく、 1メビバイト (1024 * 1024 bytes)であることもわかりますね。今回はこの違いは問題ないと判断し、1メビバイト以上を削除することにします。


```bash:shell
$ find /var/log -mtime +1 -size +1M

/var/log/powermanagement/2022.06.08.asl
/var/log/powermanagement/2022.06.09.asl
/var/log/powermanagement/2022.06.07.asl
/var/log/powermanagement/2022.06.06.asl
/var/log/powermanagement/2022.06.12.asl
/var/log/powermanagement/2022.06.04.asl
/var/log/powermanagement/2022.06.10.asl
/var/log/powermanagement/2022.06.11.asl
/var/log/powermanagement/2022.06.05.asl
/var/log/powermanagement/2022.06.01.asl
/var/log/powermanagement/2022.06.02.asl
/var/log/powermanagement/2022.06.03.asl
```

これでファイルリストが得られました。

## `-delete` オプション

削除するためには、 `-exec` オプションで `rm` をコールしてやることもできますが、それだけではなく、

```bash:manj find
-delete
        ファイルまたはディレクトリを削除します。 削除に成功すると true を返します。 削除に失敗した場合は、 エラーメッセージを表示して、　ゼロ以外の終了ステータスを返します
        (最終終了するときです)。

        警告: find はコマンドラインを式として評価する点を忘れないでください。 したがって -delete を一番初めに記述すると、 それは find に対して、
        検索開始ポイント以下のものをすべて削除させることになります。

        コマンドラインから -delete オプションを指定すると、 自動的に -depth オプションがオンとなります。 一方で -depth は -prune を無効にするので、 -delete アクションは -prune
        と同時に用いることはできません。

        ユーザーの中ではよく、 コマンドライン上において -delete の前に -print を指定して、 削除が実際にどのように行われるかをテストしようとします。 予期しない結果を起こさないためにも、
        そのようなテストを行うときから、 -depth オプションを明示的に指定することを忘れないでください。

        -delete アクションは、 ディレクトリが空でない場合には削除に失敗します。

        -ignore_readdir_race オプションとともに、 このオプションを指定し、 さらに、 親ディレクトリを読み込んだ後に、 対象ファイルが消失している状況が発生した場合、 -delete
        アクションからのエラーは無視されます。 その場合には、 診断エラーは出力されず、 終了コードをゼロ以外には変えずに -deleteクションからの返り値も true になります。
```

`-delete` オプションというのがあることもわかります。今回は /var/log 内なのでsudoを指定し、 `-delete` オプションを付けてあげれば削除完了です。


# まとめ

困った時は、Googleに頼らず `man` コマンド。日本語版 `manj` コマンドもうまく活用してみてくださいね。