【bash・linux】困った時のメモ

bashとlinuxを学び始めて、困った時の対応・忘れそうな使い方のメモ。

# 索引
- [使い方メモ](#使い方メモ)
 - [コマンド置換](#コマンド置換)
 - [時間の書式](#時間の書式)
 - [ディレクトリ内のファイル数](#ディレクトリ内のファイル数)
 - [ファイル名やディレクトリ名の一部から検索する](#ファイル名やディレクトリ名の一部から検索する)
     - [findで見つけたファイルを別のディレクトリに移動させる](#findで見つけたファイルを別のディレクトリに移動させる)
 - [行数のカウント](#行数のカウント)
 - [最終行の改行を削除](#最終行の改行を削除)
 - [選択したテキストをファイルに挿入](#選択したテキストをファイルに挿入)
 - [特定のコマンドを調べる](#特定のコマンドを調べる)
     - [履歴](#履歴)
     - [プロセス](#プロセス)
 - [sedの後方参照は10個以上できない(はず)](#sedの後方参照は10個以上できないはず)
 - [csv関係](#csv関係)
     - [特定の文字列が何行目にあるか(csvのカラムの場所探し)](#特定の文字列が何行目にあるかcsvのカラムの場所探し)
     - [特定の列を取り出す](#特定の列を取り出す)
     - [カラム数の確認](#カラム数の確認)
     - [特定のフィールドにテキストを挿入](#特定のフィールドにテキストを挿入)
- [エラー対応](#エラー対応)
 - [/bin/bash^M: 誤ったインタプリタです: そのようなファイルやディレクトリはありません](#binbashm-誤ったインタプリタです-そのようなファイルやディレクトリはありません)

# 使い方メモ
## コマンド置換
- `` ` `(バッククォート) ``や`$()`で囲った内容はコマンドとして認識される。
- 基本的には`$()`を使うことが推奨される。
 - 入れ子にできるため。

【参考記事】
[bash Tips - コマンド置換と算術式展開、パラメータ展開](https://qiita.com/mashumashu/items/bbc3a79bc779fe8c4f99)

## 時間の書式
- 特定の日付・時間の形で出力したいとき。
- dateコマンドで使う。

例えば、「yyyyMMddHHmmss」(20201025131540)のような形式にしたい場合は

```bash
date "+%Y%m%d%H%M%S"
```
【参考記事】
[date コマンド](https://hydrocul.github.io/wiki/commands/date.html)

## ディレクトリ内のファイル数
- ディレクトリにファイルがいくつ存在しているかを調べる。

```bash
ls -1U | wc -l
```
【参考記事】
[【linux】あるディレクトリの下にいくつファイルがあるか調べる](https://www.softel.co.jp/blogs/tech/archives/4858)

## ファイル名やディレクトリ名の一部から検索する
- findコマンドで部分的なテキストから該当するファイルやディレクトリを検索する。

例えば、workディレクトリの「.csv」を含むファイルを検索したいときは

```bash
find work -name \*.csv
```
【参考記事】
[ファイルなどを検索する！findコマンドの詳細まとめ【Linuxコマンド集】](https://eng-entrance.com/linux-command-find#-name)

### findで見つけたファイルを別のディレクトリに移動させる
- findコマンドでファイルを見つけた後に、別のディレクトリに移動させたい。
- xargsを用いる。

```bash
find -name pf_diff_\*.csv | xargs -I% mv % other_dir
```
- xragsの`-I`オプションはその後の文字を前のコマンドの結果を格納した変数のように扱うことができるようになる。
- そのため、`%`には`find -name pf_diff_\*.csv`の結果が入っている。

【参考記事】
[find で検索したファイルを一括で移動（mv）させる](https://qiita.com/pekepek/items/207c13c79826b459db6b)

## 行数のカウント
- `wc -l`だと厳密には行数ではなく改行の数になる。
- そのため、最終行に改行が含まれていないと、1少ない数になってしまう。

```bash
 grep -c '' sample.txt
```

【参考記事】
[wc コマンド(grepで行数をカウントするには)](https://hydrocul.github.io/wiki/commands/wc.html)
[ファイルの行数を数えるのは「wc -l file」ではありません](https://blog.unfindable.net/archives/6937)
[wc -lでは文末に改行がないファイルの行数を正しく数えられない](https://www.greptips.com/posts/1216/)

## 最終行の改行を削除
- ファイルによっては自分で入れたはずはないのに、勝手に最終行に改行が入っていることがある。
- 少なくとも私の場合は、これが後の処理の邪魔で削除しなければならなかった。

例えば、下記のようなcsvファイル

```test.csv
"name","price","classification"
"pasta","250","carbohydrate"
"cabbage","150","vegetables"
"butter","120","fat"
←この行が改行のみで、スペースも文字もない状態
```
`"butter","120","fat"`の下の1行が勝手に改行になっている。
これを消すために下記のような処理を加える。

```bash
cat test.csv | head -c -1
```
`cat`の部分は特に気にしなくてもよいが、重要なのは`|`の後の`head -c -1`。
末尾の1文字以外を表示する処理なので、結果的に最終行の改行は無視されている。

## 選択したテキストをファイルに挿入
- すでに存在しているファイルに、何らかのテキストを挿入する。
- 今回はcsvファイル(test.csv)の1行目(カラム)を別のcsvファイル(new.csv)の先頭に挿入する。

#### sedを用いる場合

```bash
sed -i "1 i $(head -n 1 test.csv)" new.csv
```

もしくは
#### readと変数を用いる場合

```bash
head -n 1 test.csv | { read v ;sed -i "1 i $v" new.csv; }
```

- `sed -i "1 i ○○"`の`" "`はシングルクォーテーションでも問題ないが、変数を展開するときは`" "`を使うこと。
- sedの-iオプションはファイルを編集して上書きするためのもの。
 - これが抜けてると出力はされるが、ファイルは上書きされていない。
- sedのiコマンド(`"1 i ○○"`のところの`i`)は対象の「前」にテキストを挿入する。

【参考記事】
[sedコマンドでファイルの先頭行に文字列を挿入する](https://qiita.com/U_ikki/items/b86ce318cb7c086bb6c1)
[Linux：sedコマンドで上書き（インプレース）編集する方法](https://motamemo.com/linux/linux-tips/sed-in-place/)
[sed aコマンド、 iコマンドの動作（指定文字列の挿入、追加） 【Linuxコマンド】](http://blog.livedoor.jp/morituri/archives/51996552.html)
[bashでパイプ(標準入力)から受け付けた内容を変数に代入する](https://orebibou.com/ja/home/201708/20170806_001/)

## 特定のコマンドを調べる
### 履歴
- 過去のコマンドを調べるのには`history`を用いるが、特定のコマンドの履歴のみ表示したいときはgrepを用いる

例えば、lsコマンドの履歴を表示したいときは

```bash
history | grep ls
```

### プロセス
- プロセスの確認には`ps`や`ps aux`を用いるが、特定のコマンドや文字列についてはこちらもgrepを用いる。

例えば、inotifywaitについてだけ表示したいとき

```bash
ps aux | grep inotifywait
```
inotifywaitをバックグラウンドで動作させたときに、消し方が分からずにたどり着いたのがこの方法です。

## sedの後方参照は10個以上できない(はず)
- 業務でカラムが10個以上あるcsvファイルを扱うことがあったとき、後方参照(`/1`や`/2`)が10以降は正しく認識されなくて困った。

自分なりに考えたやり方が以下になります。

```bash
cat  sample.csv | cut -d ',' -f 10-11 | sed -e "s/\(.*\),\(.*\)/\1,\2/g"
```
- `cut -d ',' -f 10-11`で`,`区切りの10~11番目のテキストを切り出し
- `sed -e "s/\(.*\),\(.*\)/\1,\2/g"`で後方参照している
 - `cut`で切り出したのは10番目と11番目の2つだけなので、後方参照も\10と\11とはならず、\1と\2のように小さい数に抑えられる。

【参考記事】
[bioinformatics(sed)](https://bi.biopapyrus.jp/os/linux/sed.html)

## csv関係
業務でcsvをこれでもかと扱っていたのでcsvに関することはこちらでまとめています。

### 特定の文字列が何行目にあるか(csvのカラムの場所探し)
- これまた業務で扱ったcsvの話で、カラムの数が軽く200を超えており、その中から20個ほどを抽出して別のファイルを作成する必要があった。
- 考えたのは、カンマで区切りで改行⇒該当のカラムが何行目にあるか　という方法。

```bash
$ cat sample.csv
1番目のカラム,2番目のカラム,3番目のカラム,4番目のカラム,5番目のカラム
a,b,c,d,e
$ head -n 1 sample.csv | sed -e "s/,/,\n/g" | grep -e "3番目のカラム" -n
3:3番目のカラム,
```
このように、探したいカラムが何番目にあるかが検索できます。

【参考記事】
[Linuxのgrep（指定文字検索）で該当行の行番号のみ出力する](https://qiita.com/yuku/items/fe4a7f4de6242e958553)

### 特定の列を取り出す
- 例えば、カンマ区切りの1番目のフィールドを取り出す場合

```bash
cut -d ',' -f 1 sample.csv
```

【参考記事】
[CSV ファイルの特定の列を取り出す](https://qiita.com/pekepek/items/c28bc87171983d756e32)

### カラム数の確認
```bash
cat sample.csv | awk -F ',' '{print NF}'
```

【参考記事】
[awkを使ってCSVのカラム数をチェック](https://qiita.com/uuuno/items/f66bd1724e5380a54206)

### 特定のフィールドにテキストを挿入
- 例えば、30番目のフィールドが空で、そこにテキストを入れたい場合
 - なおかつ、csvの最終行が対象としてます。

```bash
tail -n -1 sample.csv | awk -F, 'BEGIN{OFS=","}{$30="abcde";print}' > sample2.csv
```

【参考記事】
[awkでcsvに任意の列を追加する](https://qiita.com/tochiji/items/284daafaedec941afdca)

### ※注意点
上の参考記事にも書かれていますが、`"abc,def"`のように、1個のカラムの中にカンマが含まれているとうまく動作しません。

# エラー対応
## /bin/bash^M: 誤ったインタプリタです: そのようなファイルやディレクトリはありません
- 複数人で開発をしていて、gitから引っ張ってきたシェルを実行するとこの表示がでることがよくあった。

```bash
$ ./test.sh
-bash: ./test.sh: /bin/bash^M: 誤ったインタプリタです: そのようなファイルやディレクトリはありません
```
なので、参考記事と同じことを実行

```
sed -i 's/\r//' test.sh
```

【参考記事】
[/bin/bash^M: bad interpreter: そのようなファイルやディレクトリはありません (No such file or directory)](https://totech.hateblo.jp/entry/2014/03/19/174129)

# 最後に
この記事に載せたのが最適解であるのかはわかりません。
調べればもっと楽なやり方もあるのかもしれません。