Linux初心者がコマンドを使うときに知っておくと少し幸せになること

# はじめに
最近周りでLinuxのBashを触る人が増え、意外と知られていないコマンドの使い方や、おしい所で詰まっているコマンドの記述を目にするようになってきたので、その中でもコマンド共通して参考になりそうなモノをピックアップして紹介します。

また、それぞれの詳細な説明は記事が沢山出ているので、ここでは紹介する程度に留めておきます。

LinuxでBashを触り始めて、なんとなくコマンドやパイプが使えるようになってきた人に、一歩踏み込んだ使い方として参考になれば幸いです。

なお、[[Linuxで先頭行や末尾行へ文字列を追加・削除・抽出するいろんな方法]](https://qiita.com/b-mente/items/6589b46bb2fbb21bc11c)に`sed`コマンドや`awk`コマンド、`cat`コマンドなどの応用方法を記載したので、興味のある方はそちらを参照してください。

# ・ショートカットが便利
`Tab`での補完や、`Ctrl+C`で実行したコマンドを中断する、`↑↓`で実行したコマンドの履歴を表示する以外にも、コンソール上で操作をするのに便利なキーボードショートカットが用意されています。

お勧めのショートカットとしては以下3つになりますが、これ以外にも色々なショートカットが用意されており、使いこなせれば操作効率が大きく改善されると思います。

- `Ctrl+R`:過去に実行したコマンドの履歴を検索
- `Ctrl+A`:カーソルの位置を先頭に移動
- `Ctrl+E`:カーソルの位置を行末に移動


# ・移動する直前のディレクトリに簡単に戻れる
ディレクトリを移動した後に、移動する直前のディレクトに戻りたい時があります。その時は`cd`コマンドで移動する直前のパスを指定してディレクトリを移動することもできますが、`cd -`を実行すると簡単に直前にいたディレクトに戻ることができます。
```bash:dir1からdir2に移動し、パスを指定してdir1に戻る
$ pwd
/dirs/dir1
$ cd ../dir2
$ pwd
/dirs/dir2
$ cd ../dir1
$ pwd
/dirs/dir1
$ 
```

```bash:dir1からdir2に移動し、'cd -' でdir1に戻る
$ pwd
/dirs/dir1
$ cd ../dir2
$ pwd
/dirs/dir2
$ cd -
$ pwd
/dirs/dir1
$ 
```

パスが長い場合や、ディレクトリを行き来する場合は非常に便利なのでぜひ活用してください。

# ・作成したディレクトリに簡単に移動できる
`mkdir`コマンドでディレクトリを作成した直後に作成したディレクトリに移動する場合、`cd`コマンドで作成したディレクトリのパスを指定して移動することができますが、`cd $_`を使用すると簡単に作成したディレクトリに移動することができます。
```bash:dir1-1を作成した後にパスを入力してディレクトリに移動する
$ mkdir -p dirs/dir1/dir1-1
$ cd dirs/dir1/dir1-1
$ pwd
$ /dirs/dir1/dir1-1
$ 
```
```bash:dir1-1を作成した後に'cd $_'を入力してディレクトリに移動する
$ mkdir -p dirs/dir1/dir1-1
$ cd $_
$ pwd
$ /dirs/dir1/dir1-1
$ 
```
```bash:dir1-1を作成した後に'cd $_'を入力してディレクトリに移動する(省略形)
$ mkdir -p dirs/dir1/dir1-1 && cd $_
$ pwd
$ /dirs/dir1/dir1-1
$ 
```
:::note warn
`$_`は前回実行したコマンドの最後の引数(`mkdir dirs/dir1/dir1-1`であれば`dirs/dir1/dir1-1`)を示します。そのため`mkdir`コマンドと`cd $_`の間に別なコマンドを挟む場合は作成したディレクトリを示すことが出来ないので注意してください。
```bash:mkdirと'cd $_'の間にechoコマンドが挟まっている場合はechoコマンドの引数が参照される
$ mkdir -p dirs/dir1/dir1-1
$ echo test
test
$ cd $_
bash: cd: test: No such file or directory
$ 
```

また、`mkdir`コマンドでスペースを含むディレクトリを作成した場合はスペースで引数が分割されてしまうので`cd "$_"`のように`$_`をダブルクォートで囲ってください。
```bash:スペースを含むdir1 1を作成した後に'cd "$_"'を入力してディレクトリに移動する
$ mkdir -p "dirs/dir1/dir1 1" && cd "$_"
$ pwd
$ /dirs/dir1/dir1 1
$ 
```
※そもそもディレクトリ名やファイル名にスペースや記号を含めることは推奨されないので、可能であれば別な名前を検討してください
:::
こちらもパスが長い場合や、ディレクトリを行き来する場合は非常に便利なのでぜひ活用してください。

# ・コマンドの説明を表示できる
コマンドはわかるけど使い方を忘れてしまった...といった場合に、コマンドの説明をコンソール上で確認する事ができます。

代表的なのは`man`コマンドで、引数にコマンドを指定して実行するとマニュアルを表示する事ができます。
標準ではlessコマンドでマニュアルが表示されるので、`/(検索文字列)`で文字列検索、`↑↓`で表示移動、`q`で表示を終了するなど、lessコマンドでできる操作がそのまま使用できます。
(英語が苦手な方は少し手間ですが、表示を日本語化する事もできます)

```bash:grepコマンドのマニュアルを表示
$ man grep
```

また、多くのコマンドでは`-h`や`–help`オプションをつけてコマンドを実行するとusage(使い方)を表示する事ができます。

```shell:grepコマンドのusageを表示
$ grep -h
usage: grep [-abcDEFGHhIiJLlmnOoqRSsUVvwxZ] [-A num] [-B num] [-C[num]]
	[-e pattern] [-f file] [--binary-files=value] [--color=when]
	[--context[=num]] [--directories=action] [--label] [--line-buffered]
	[--null] [pattern] [file ...]
```

見方を覚えるまでは呪文のようメッセージですが、慣れてくるとコマンドのWebサイト巡りをしなくてもサクっとその場で使い方を確認できるようになります。


# ・コマンドに直接ファイルを指定できる
grepやawk、sedなどのテキスト処理系のコマンドを実行する際に`cat`コマンドでファイルを開いてからパイプ`|`で渡して処理する事もできますが、多くのコマンドでは直接ファイルを指定して実行する事ができます。

```bash:catでfileを出力してからgrepでhelloの文字列を含む行を出力
$ cat file | grep hello
```

```bash:grepでfileの中からhelloの文字列を含む行を出力
$ grep hello file
```

また、ファイルを指定する際にワイルドカードが使用できるので複数ディレクトリの中の特定の拡張子のファイルを指定したい場合は、ワイルドカードで階層と拡張子を指定してコマンドを実行する事ができます。


```bash:dirディレクトリが複数あり、その中にdmpファイルとlogファイルが混在している
$ tree .
.
├── dir1
│   ├── file.dmp
│   └── file.log
├── dir2
│   ├── file.dmp
│   └── file.log
└── dir3
    ├── file.dmp
    └── file.log

3 directories, 6 files
$ 
```
```bash:複数あるdirディレクトリの中からlogファイルだけを対象にgrepでhelloの文字列を含む行を出力
$ grep hello */*.log
dir1/file.log:hello
dir2/file.log:hello
dir3/file.log:hello
$ 
```
`find`コマンドを使ってファイルを検索してコマンドを実行しても同じ結果が得られますが、同じ階層のファイルを対象とする場合はワイルドカードを使用するとシンプルに記述することができます。


# ・同じオプションが複数回指定できる
コマンドによっては、条件やスクリプトなどのオプションによる指定が複数回できる場合があります。
同じコマンドをパイプ`|`で連結して繰り返し記述している場合や、複数の条件を指定する方法で困っている場合は、一度コマンドのオプションを調べてみると解決策が見つかるかもしれません。

```bash
$ cat file 
title
hello
world
text
$ 
```
```bash:grepでfileの中からhelloかworldの文字列を含む行を出力
$ grep -e hello -e world file
hello
world
$ 
```
```bash:sedでhelloをAに置換した後にworldをBに置換して出力
$ sed -e 's/hello/A/' -e 's/world/B/' file
title
A
B
text
$ 
```


# ・コンソールで変数が使える
プログラムの記述と直接関係無いように見えるコンソール操作ですが、コマンドの引数やオプションなどに変数を使用する事ができます。
また、変数に格納された値は加工をして参照する`変数展開`が使用できるので非常に便利です。

```bash:helloの文字列をVALに格納し、eをaに置換してechoで出力
$ VAL=hello
$ echo $VAL
hello
$ echo ${VAL/e/a}
hallo
$ 
```

```bash:変数Pにファイルのパスを設定し、cpコマンドの引数に変数P(file)と変数Pを置換したパス(file.bk)を指定してバックアップファイルを作成する
$ P=/home/user/tmp/file
$ echo $P
/home/user/tmp/file 
$ echo ${P/%file/file.bk}
/home/user/tmp/file.bk
$ cp $P ${P/%file/file.bk}
$ ls /home/user/tmp/
file  file.bk
$ 
```

また、上記では変数`P`の末尾の`file`という文字列を`file.bk`に変数展開で置換しましたが(`${P/%XX/YY}`で変数`P`の末尾の`XX`の文字列を`YY`に置換)、ファイルのパスが長い場合は`{}`を使用した`ブレース展開`を使うと、よりコンパクトに書くことができます。

```bash:cpコマンドの引数にファイルの絶対パスのブレース展開
$ cp /home/user/tmp/file{,bk}
$ ls /home/user/tmp/
file  file.bk
$ 
```

ファイルのバックアップをとる際にはよく使われる方法ですが、簡単にいうとbashは中括弧`{}`(ブレース)の中身を展開してくれるので、それを利用して長いパスを2回書かずにファイルのコピーを実現しています。

```bash:,を使用したブレース展開と..を使用したブレース展開
$ echo {1,2,3}
1 2 3
$ echo {1..3}
1 2 3
$ 
```

```bash:,を使用したブレース展開で、fileの末尾に.txtと.bkをつけて表示
$ echo file{.txt,.bk}
file.txt file.bk
$ 
```

```bash:,を使用したブレース展開で、fileの末尾に何もつけないのと.bkをつけて表示
$ echo file{,.bk}
file file.bk
$ 
```

`ブレース展開`も非常に便利なので`変数展開`と一緒に使い方を調べてみる事をお勧めします。
また、`ブレース展開`はデフォルト有効になっていると思いますが、もし無効になっている場合は`set -B`で有効に切り替えられます。
(`set +B`で無効に切り替えられます)

```bash:ブレース展開を無効→有効に変更
$ echo {1..3}
{1..3}
$ set -B
$ echo {1..3}
1 2 3
$ 
```

# ・コンソールでif、forが使える

変数だけでなく、コンソール上で`if`や`for`などの制御文も使う事ができます。
引数やオプションの一部の文字を変えながら繰り返し同じコマンドを実行する、などの場合は、コンソール上で変数や制御文を使ってコマンドを実行すると楽に処理することができます。

```bash:1から3までを変数iに格納しながらforループで繰り返してechoで変数iを出力
$ for i in {1..3} ; do echo $i ; done
1
2
3
$ 
```

```bash:grepでfileの中からhello文字列を見つけられたらechoでHITを出力
$ if grep -q hello file ; then echo HIT ; fi
HIT
$ if grep -q hallo file ; then echo HIT ; fi
$ 
```

また、制御文と同様に`&&`や`||`などの論理演算子もコンソール上で使う事ができます。
簡単な条件分岐であれば`if`をシンプルな記述に置き換えられたり、三項演算子の代わりとして使用することもできます。

```bash:grepでfileの中からhello文字列を見つけられたらechoでHITを出力
$ grep -q hello file && echo HIT
HIT
$ grep -q hallo file && echo HIT
$ 
```

```bash:grepでfileの中からh'a'llo文字列を見つけられなかったらechoでNONEを出力
$ grep -q hallo file || echo NONE
NONE
$ grep -q hello file || echo NONE
$ 
```

# ・コマンドの実行結果を確認できる
コマンドを実行すると実行結果(終了ステータス)が`$?`という特殊変数に格納され、この`$?`を`echo`コマンドで出力する事で実行結果が確認できます。
また、実行結果はコマンドが成功すると`0`、失敗すると`0`以外が格納されます。

```bash:fileの中からhelloとh'a'llo文字列をgrepした結果　(helloは成功、h'a'lloは失敗)
$ grep hello file
hello
$ echo $?
0
$ grep hallo file
$ echo $?
1
$ 
```

ただ、`$?`は直前に実行したコマンドの実行結果が格納されるため、パイプ`|`で連結した複数のコマンドの実行結果を確認する事はできません。
その場合は`${PIPESTATUS[@]}`を`echo`コマンドで出力する事で実行結果を確認できます。

```bash:catでfileを出力してからhello文字列をgrepした結果(catは成功、grepも成功)
$ cat file | grep hello file
hello
$ echo ${PIPESTATUS[@]}
0 0
$ 
```

```bash:catでfileを出力してからhello文字列をgrepした結果(catは成功、grepは失敗)
$ cat file | grep hallo file
$ echo ${PIPESTATUS[@]}
0 1
$ 
```

パイプ`|`でコマンドを連結する数が増えてきた場合は、実行後に`echo ${PIPESTATUS[@]}`で実行結果を確認することで、どこのコマンドが想定外の動きをしているか特定がしやすくなります。



# おわりに
コンソールやコマンドは注意点も多いのでどこまで書くか悩みましたが、知っていれば自分で調べることが出来るのでそういうモノが存在する事を知ってもらおうと身近で見かけた事からピックアップしてみました。

数も少ないので中途半端な形になりましたが、少しでも幸せになれた人がいれば幸いです。