Linuxで先頭行や末尾行へ文字列を追加・削除・抽出するいろんな方法や注意事項

# はじめに

最近Linuxでコマンドの出力結果やファイルに文字列を追加したり削除する方法について説明する機会が多いので、備忘録も兼ねてコマンドの使い方を記載しておきます。

よく紹介されている`sed`コマンド以外の方法も記載しているので、用途に合った方法を選んでもらえればと思います。

# `sed`コマンドの先頭行・末尾行の操作方法

`sed`コマンドは文字列を置換をするコマンドとして有名ですが、文字列の追加や行の削除・抽出も行える非常に強力なコマンドです。

とりあえず`sed`コマンドを知っていればコマンドの出力結果(標準出力)やファイルの行の操作は何とかなりますので、覚えておくことをオススメします。

## ・行を追加する(標準出力)

コマンドの実行結果(標準出力)のテキストの先頭行や末尾行に追加したい文字列の行を追加する方法です。

【コマンドの書き方】
```bash:先頭行に文字列を追加するコマンド
(コマンド) | sed '1i(先頭行に追加する文字列)'
```
```bash:末尾行に文字列を追加するコマンド
(コマンド) | sed '$a(末尾行に追加する文字列)'
```
```bash:先頭行と末尾行に文字列を追加するコマンド
(コマンド) | sed -e '1i(先頭行に追加する文字列)' -e '$a(末尾行に追加する文字列)'
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt
top
middle
end
$ 
$ # 先頭行に文字列ADDを追加
$ cat hoge.txt | sed '1iADD'
ADD
top
middle
end
$ 
$ # 末尾行に文字列ADDを追加
$ cat hoge.txt | sed '$aADD'
top
middle
end
ADD
$ 
$ # 先頭行と末尾行に文字列ADDを追加
$ cat hoge.txt | sed -e '1iADD-TOP' -e '$aADD-END'
ADD-TOP
top
middle
end
ADD-END
$ 
```

## ・行を追加する(ファイル)

ファイルの先頭行や末尾行に追加したい文字列の行を追加する方法です。

【コマンドの書き方】
```bash:先頭行に文字列を追加するコマンド
sed -i '1i(先頭行に追加する文字列)' (ファイルパス)
```
```bash:末尾行に文字列を追加するコマンド
sed -i '$a(末尾行に追加する文字列)' (ファイルパス)
```
```bash:先頭行と末尾行に文字列を追加するコマンド
sed -i -e '1i(先頭行に追加する文字列)' -e '$a(末尾行に追加する文字列)' (ファイルパス)
```

【コマンドのサンプル】
```bash:サンプル
$ # 先頭行に文字列ADDを追加
$ sed -i '1iADD' hoge.txt
$ 
$ # 末尾行に文字列ADDを追加
$ sed -i '$aADD' hoge.txt
$ 
$ # 先頭行と末尾行に文字列ADDを追加
$ sed -i -e '1iADD-TOP' -e '$aADD-END' hoge.txt 
$ 
```

## ・行を削除する(標準出力)

コマンドの実行結果(標準出力)のテキストの先頭行や末尾行を削除する方法です。

【コマンドの書き方】
```bash:先頭行を削除するコマンド
(コマンド) | sed '1d'
```
```bash:末尾行を削除するコマンド
(コマンド) | sed '$d'
```
```bash:先頭行と末尾行を削除するコマンド
(コマンド) | sed -e '1d' -e '$d'
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt
top
middle
end
$ 
$ # 先頭行を削除
$ cat hoge.txt | sed '1d'
middle
end
$ 
$ # 末尾行を削除
$ cat hoge.txt | sed '$d'
top
middle
$ 
$ # 先頭行と末尾行を削除
$ cat hoge.txt | sed -e '1d' -e '$d'
middle
$ 
```

## ・行を削除する(ファイル)

ファイルの先頭行や末尾行を削除する方法です。

【コマンドの書き方】
```bash:先頭行を削除するコマンド
sed -i '1d' (ファイルパス)
```
```bash:末尾行を削除するコマンド
sed -i '$d' (ファイルパス)
```
```bash:先頭行と末尾行を削除するコマンド
sed -i -e '1d' -e '$d' (ファイルパス)
```

【コマンドのサンプル】
```bash:サンプル
$ # 先頭行を削除
$ sed -i '1d' hoge.txt
$ 
$ # 末尾行を削除
$ sed -i '$d' hoge.txt
$ 
$ # 先頭行と末尾行を削除
$ sed -i -e '1d' -e '$d' hoge.txt 
$ 
```

## ・行を抽出する(標準出力)

コマンドの実行結果(標準出力)のテキストの先頭行や末尾行を表示(抽出)する方法です。

【コマンドの書き方】
```bash:先頭行を抽出するコマンド
(コマンド) | sed -n '1p'
```
```bash:末尾行を抽出するコマンド
(コマンド) | sed -n '$p'
```
```bash:先頭行と末尾行を抽出するコマンド
(コマンド) | sed -n -e '1p' -e '$p'
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt
top
middle
end
$ 
$ # 先頭行を抽出
$ cat hoge.txt | sed -n '1p'
top
$ 
$ # 末尾行を抽出
$ cat hoge.txt | sed -n '$p'
end
$ 
$ # 先頭行と末尾行を抽出
$ cat hoge.txt | sed -n -e '1p' -e '$p'
top
end
$ 
```

:::note warn

上記の方法で読み込むテキストが大きい場合は処理が完了するまで時間がかかるので、先頭行か末尾行の片方だけ処理したい場合は下記コマンドを利用してください。
(下記の方法では先頭行と末尾行の両方を抽出することはできません)

```bash:サイズが大きいテキストを処理するコマンド
$ # 先頭行を抽出するコマンド
$ cat hoge.txt | sed '1q'
top
$ 
$ # 末尾行を抽出するコマンド
$ cat hoge.txt | tac | sed '1q'
end
$ 
```

また、`-e`で複数コマンドを記載した際にそれぞれのコマンドでマッチすると、マッチした回数同じ行が出力されますので注意してください。

```bash:topが2行抽出される
$ cat hoge.txt | sed -n -e '1p' -e '1p'
top
top
$ 
```

:::

## ・行を抽出する(ファイル)

ファイルの先頭行や末尾行のみに書き換える方法です。
注意事項については上記の[[行を抽出する(標準出力)]](#行を抽出する標準出力)をご覧ください。

【コマンドの書き方】
```bash:先頭行を抽出するコマンド
sed -i -n '1p' (ファイルパス)
```
```bash:末尾行を抽出するコマンド
sed -i -n '$p' (ファイルパス)
```
```bash:先頭行と末尾行を抽出するコマンド
sed -i -n -e '1p' -e '$p' (ファイルパス)
```

【コマンドのサンプル】
```bash:サンプル
$ # 先頭行を抽出
$ sed -i -n '1p' hoge.txt
$ 
$ # 末尾行を抽出
$ sed -i -n '$p' hoge.txt
$ 
$ # 先頭行と末尾行を抽出
$ sed -i -n -e '1p' -e '$p' hoge.txt
$ 
```

# `sed`コマンドの他の操作方法

行の追加・削除・抽出と同様に標準出力の場合は`(コマンド) |`、ファイルの場合は`sed`コマンドに`-i`オプションをつけてください。

## ・途中の行に追加する

【コマンドの書き方】
```bash:指定した行に追加するコマンド
sed '(行数)i(追加する文字列)'
```
```bash:指定した連続した複数行に追加するコマンド
sed '(開始行数),(終了行数)i(追加する文字列)'
```
```bash:文字列が含まれる行に追加するコマンド
sed '/(文字列)/i(追加する文字列)'
```
```bash:字列が含まれる連続した複数行に追加するコマンド
sed '/(開始文字列)/,/(終了文字列)/i(追加する文字列)'
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt
top
middle0
middle1
middle2
middle3
end
$ 
$ # 2行目に文字列ADDを追加
$ cat hoge.txt | sed '2iADD'
top
ADD
middle0
middle1
middle2
middle3
end
$ 
$ # 2行目と5行目に文字列ADDを追加
$ cat hoge.txt | sed -e '2iADD' -e '5iADD'
top
ADD
middle0
middle1
middle2
ADD
middle3
end
$ 
$ # 先頭行から2行目まで文字列ADDを追加
$ cat hoge.txt | sed '1,2iADD'
ADD
top
ADD
middle0
middle1
middle2
middle3
end
$ 
$ # 先頭行から2行目まで、5行目から末尾行までに文字列ADDを追加
$ cat hoge.txt | sed -e '1,2iADD' -e '5,$iADD'
ADD
top
ADD
middle0
middle1
middle2
ADD
middle3
ADD
end
$ 
$ # middle3が含まれる行に文字列ADDを追加
$ cat hoge.txt | sed '/middle3/iADD'
top
middle0
middle1
middle2
ADD
middle3
end
$ 
$ # middle2が含まれる行からmiddle3が含まれる行に文字列ADDを追加
$ cat hoge.txt | sed '/middle2/,/middle3/iADD'
top
middle0
middle1
ADD
middle2
ADD
middle3
end
$ 
```

## ・途中の行を削除する

【コマンドの書き方】
```bash:指定した行を削除するコマンド
sed '(行数)d'
```
```bash:指定した連続する複数行を削除するコマンド
sed '(開始行数),(終了行数)d'
```
```bash:文字列が含まれる行を削除するコマンド
sed '/(文字列)/d'
```
```bash:文字列が含まれる連続した複数行を削除するコマンド
sed '/(開始文字列)/,/(終了文字列)/d'
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt
top
middle0
middle1
middle2
middle3
end
$ 
$ # 2行目を削除
$ cat hoge.txt | sed '2d'
top
middle1
middle2
middle3
end
$ 
$ # 2行目と5行目を削除
$ cat hoge.txt | sed -e '2d' -e '5d'
top
middle1
middle2
end
$ 
$ # 先頭行から2行目までを削除
$ cat hoge.txt | sed '1,2d'
middle1
middle2
middle3
end
$ 
$ # 先頭行から2行目まで、5行目から末尾行までを削除
$ cat hoge.txt | sed -e '1,2d' -e '5,$d'
middle1
middle2
$ 
$ # middle3が含まれる行を削除
$ cat hoge.txt | sed '/middle3/d'
top
middle0
middle1
middle2
end
$ 
$ # middle2が含まれる行からmiddle3が含まれる行を削除
$ cat hoge.txt | sed '/middle2/,/middle3/d'
top
middle0
middle1
end
$ 
```

## ・途中の行を抽出する

【コマンドの書き方】
```bash:指定した行を抽出するコマンド
sed -n '(行数)p'
```
```bash:指定した連続する複数行を抽出するコマンド
sed -n '(開始行数),(終了行数)p'
```
```bash:文字列が含まれる行を抽出するコマンド
sed -n '/(文字列)/p'
```
```bash:文字列が含まれる連続した複数行を抽出するコマンド
sed -n '/(開始文字列)/,/(終了文字列)/p'
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt
top
middle0
middle1
middle2
middle3
end
$ 
$ # 2行目を抽出
$ cat hoge.txt | sed -n '2p'
middle0
$ 
$ # 2行目と5行目を抽出
$ cat hoge.txt | sed -n -e '2p' -e '5p'
middle0
middle3
$ 
$ # 先頭行から2行目までを抽出
$ cat hoge.txt | sed -n '1,2p'
top
middle0
$ 
$ # 先頭行から2行目まで、5行目から末尾行までを抽出
$ cat hoge.txt | sed -n -e '1,2p' -e '5,$p'
top
middle0
middle3
end
$ 
$ # middle3が含まれる行を抽出
$ cat hoge.txt | sed -n '/middle3/p'
middle3
$ 
$ # middle2が含まれる行からmiddle3が含まれる行に文字列ADDを抽出
$ cat hoge.txt | sed -n '/middle2/,/middle3/p'
middle2
middle3
$ 
```

## ・改行する

【コマンドの書き方】
```bash:\nを使用するコマンド
sed '(行数)i(追加する文字列1)\n(追加する文字列2)'
```
```bash:バックスラッシュを使用するコマンド
sed '(行数)i(追加する文字列1)\
(追加する文字列2)'
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt
top
middle
end
$ 
$ # 先頭行に文字列ADD1 改行(\n) ADD2を追加
$ cat hoge.txt | sed '1iADD1\nADD2'
ADD1
ADD2
top
middle
end
$ 
$ # 先頭行に文字列ADD1 改行(\) ADD2を追加
$ cat hoge.txt | sed '1iADD1\
> ADD2'
ADD1
ADD2
top
middle
end
$ 
```

## ・特殊文字を使う

【コマンドの書き方】
```bash:シングルクォートを使用するコマンド
sed '(行数)i(追加する文字列) '\'' '
```
```bash:バックスラッシュを使用するコマンド
sed '(行数)i(追加する文字列) \\ '
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt
top
middle
end
$ 
$ # 先頭行に記号を追加(大体そのまま使える)
$ cat hoge.txt | sed '1i!"#$%&()*+,-./:;<=>?@[]^_`{|}~'
!"#$%&()*+,-./:;<=>?@[]^_`{|}~
top
middle
end
$ 
$ # 先頭行に文字列ADD1と後続にシングルクォートを使用して追加`"
$ cat hoge.txt | sed '1iADD1'\''ADD2'
ADD1'ADD2
top
middle
end
$ 
$ # 先頭行に文字列ADD1と後続にバックスラッシュを使用して追加'
$ cat hoge.txt | sed '1iADD1\\ADD2'
ADD1\ADD2
top
middle
end
$ 
```

:::note warn

追加する文字列の先頭にあるスペースは省略されるので注意してください。
```bash:先頭行にスペースの後に文字列ADDを追加
$ cat hoge.txt | sed '1i   ADD'
ADD
top
middle
end
$ 
```
また、先頭にバックスラッシュを使用する場合は3つ連続(`\\\`)記載する必要があるので注意してください。
```bash:先頭行にバックスラッシュと後続に文字列ADDを追加
$ cat hoge.txt | sed '1i\\\ADD'
\ADD
top
middle
end
$ 
```
:::

## ・変数やコマンドを使う 

【コマンドの書き方】
```bash:変数を使用する
sed '(行数)i(追加する文字列) '変数' '
```
```bash:コマンドを使用する(eコマンド)
sed '(行数)eコマンド'
```
```bash:コマンドを使用する($())
sed '(行数)i(追加する文字列) '$(コマンド)' '
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt
top
middle
end
$ 
$ # 先頭行に文字列ADDと変数を追加
$ VAR=TEXT
$ cat hoge.txt | sed '1iADD '$VAR' '
ADD TEXT 
top
middle
end
$ 
$ # 先頭行にdateコマンド結果を追加
$ cat hoge.txt | sed '1edate "+%Y/%m/%d %H:%M:%S"'
2022/07/10 01:12:12
top
middle
end
$ 
$ # 先頭行に文字列ADDとdateコマンド結果を追加
$ cat hoge.txt | sed '1iADD '$(date "+%Y/%m/%d_%H:%M:%S")' '
ADD 2022/07/10_01:42:59 
top
middle
end
$ 
```

:::note warn

`$(コマンド)`でスペースの入ったコマンド結果を追加する場合は`"$(コマンド)"`のようにダブルクォートで結果を囲う必要があります。
```bash:コマンド結果にスペースが入っていると失敗
$ cat hoge.txt | sed '1iADD '$(date "+%Y/%m/%d %H:%M:%S")' '
sed: 01:49:54 を読み込めません: そのようなファイルやディレクトリはありません
$ 
```
```bash:ダブルクォートを追加
$ cat hoge.txt | sed '1iADD '"$(date "+%Y/%m/%d %H:%M:%S")"' '
ADD 2022/07/10 01:50:08 
top
middle
end
$ 
```

:::

# `sed`コマンド以外を使用する方法

## ・行を追加する

`sed`コマンドを学んでいくと忘れがちになりますが、ファイルの末尾行に文字列を追加するにはコマンド結果の追記のリダイレクト(`>>`)が使用できます。

`sed`コマンドより処理速度が早くメモリ使用量が少ないので、特に理由がなければこちらを使用することをオススメします。

【コマンドの書き方】
```bash:末尾行にファイルに格納された文字列を追加するコマンド
cat (末尾行に追加したい文字列のファイルパス) >> (ファイルパス)
```
```bash:末尾行に文字列を追加するコマンド
echo "末尾行に追加したい文字列" >> (ファイルパス)
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt 
top
middle
end
$ 
$ # fuga.txtの中身
$ cat fuga.txt 
fuga top
fuga middle
fuga end
$ 
$ # 末尾行にfuga.txtの文字列を追加
$ cat fuga.txt >> hoge.txt
$ 
$ # 末尾行に文字列ADDを追加
$ echo "ADD" >> hoge.txt
$ 
```

また、標準出力の先頭行や末尾行に文字列を追加する方法としても`cat`コマンドを使用することができます。

下記は先頭行に文字列を追加する方法になります。

【コマンドの書き方】
```bash:先頭行に文字列を追加するコマンド
cat (先頭行に追加したい文字列のファイルパス) -
```
```bash:先頭行に文字列を追加するコマンド
cat <(echo "先頭行に追加したい文字列") -
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt 
top
middle
end
$ 
$ # fuga.txtの中身
$ cat fuga.txt 
fuga top
fuga middle
fuga end
$ 
$ # 先頭行にfuga.txtの文字列を追加
$ cat hoge.txt | cat fuga.txt -
fuga top
fuga middle
fuga end
top
middle
end
$ 
$ # 先頭行に文字列ADDを追加
$ cat hoge.txt | cat <(echo "ADD") -
ADD
top
middle
end
$ 
```

下記は末尾行に文字列を追加する方法になります。

【コマンドの書き方】
```bash:末尾行に文字列を追加するコマンド
cat - (末尾行に追加したい文字列のファイルパス)
```
```bash:末尾行に文字列を追加するコマンド
cat - <(echo "末尾行に追加したい文字列")
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt 
top
middle
end
$ 
$ # fuga.txtの中身
$ cat fuga.txt 
fuga top
fuga middle
fuga end
$ 
$ # 先頭行にfuga.txtの文字列を追加
$ cat hoge.txt | cat - fuga.txt
top
middle
end
fuga top
fuga middle
fuga end
$ 
$ # 先頭行に文字列ADDを追加
$ cat hoge.txt | cat - <(echo "ADD")
top
middle
end
ADD
$ 
```

下記は先頭行と末尾行に文字列を追加する方法になります。

【コマンドの書き方】
```bash:先頭行と末尾行に文字列を追加するコマンド
cat (先頭行に追加したい文字列のファイルパス) - (末尾行に追加したい文字列のファイルパス)
```
```bash:先頭行と末尾行と文字列を追加するコマンド
cat <(echo "先頭行に追加したい文字列") - <(echo "末尾行に追加したい文字列")
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt 
top
middle
end
$ 
$ # fuga.txtの中身
$ cat fuga.txt 
fuga top
fuga middle
fuga end
$ 
$ # 先頭行と末尾行にfuga.txtの文字列を追加
$ cat hoge.txt | cat fuga.txt - fuga.txt 
fuga top
fuga middle
fuga end
top
middle
end
fuga top
fuga middle
fuga end
$ 
$ # 先頭行と末尾行に文字列ADDを追加
$ cat hoge.txt | cat <(echo "ADD") - <(echo "ADD")
ADD
top
middle
end
ADD
$ 
```

## ・行を削除する

`grep`コマンドでも特定の文字列が含まれる行を削除することができます。

【コマンドの書き方】
```bash:文字列が含まれる行を削除するコマンド
(コマンド) | grep -v (文字列)
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt 
top
middle
end
$ 
$ # middleが含まれる行を削除
$ cat hoge.txt | grep -v middle
top
end
$ 
```

また、`awk`コマンドでも指定した行を削除することができます。

【コマンドの書き方】
```bash:標準出力の行を削除するコマンド
(コマンド) | awk 'NR!=(行数)'
```
```bash:文字列が含まれる行を削除するコマンド
(コマンド) | awk '!/(文字列)/'
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt 
top
middle
end
$ 
$ # 2行目を削除
$ cat hoge.txt | awk 'NR!=2'
top
end
$ 
$ # middleが含まれる行を削除
$ cat hoge.txt | awk 'NR!/middle/'
top
middle
end
$ 
```

## ・行を抽出する

`head`コマンド、`tail`コマンド、`grep`コマンドを使用して行を抽出することができます。
この3つのコマンドは`sed`コマンドより処理速度が早くメモリ使用量も少ないので、大量のデータを処理する際はこちらを使用することをオススメします。

【コマンドの書き方】
```bash:標準出力の先頭行から指定した行数を抽出するコマンド
(コマンド) | head -(行数)
```
```bash:標準出力の末尾行から指定した行数を抽出するコマンド
(コマンド) | tail -(行数)
```
```bash:標準出力から文字列が含まれた行を抽出するコマンド
(コマンド) | grep 文字列
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt 
top
middle0
middle1
middle2
middle3
end
$ 
$ # 先頭行から2行目までを抽出
$ cat hoge.txt | head -2
top
middle0
$ 
$ # 末尾行から2行目までを抽出
$ cat hoge.txt | tail -2
middle3
end
$ 
$ # middleが含まれる行を抽出
$ cat hoge.txt | grep middle
middle0
middle1
middle2
middle3
$ 
```

また、複数のコマンドを同時に実行することができます。

【コマンドの書き方】
```bash:ファイルの先頭行から指定した行数と末尾行から指定した行数を抽出するコマンド
(head -(行数);tail -(行数)) <(ファイルパス)
```
```bash:ファイルの先頭行から指定した行数と文字列が含まれた行を抽出するコマンド
(head -(行数);grep 文字列) <(ファイルパス)
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt 
top
middle0
middle1
middle2
middle3
end
$ 
$ # 先頭行と末尾業を抽出
$ (head -1;tail -1) < hoge.txt 
top
end
$ 
$ # 先頭行とmiddleが含まれる行を抽出
$ (head -1;grep middle) < hoge.txt 
top
middle0
middle1
middle2
middle3
$ 
```

:::note warn

複数のコマンドを同時に実行する方法では1つ目に実行されるコマンドで読み込み終わった所から2つ目のコマンドに渡されて処理されます。

そのため、1つ目のコマンドで末尾行まで読まれてしまうと、後続のコマンドで該当行を抽出できなくなるので注意してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699056/f9129fb9-d566-3db1-735e-be7ac70b3e92.png)

`tail`コマンドを先に実行すると、後続の`head`コマンドでは何も表示されません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699056/2943cc18-fd78-fb24-f8b7-184a37becca8.png)

:::

また、awkコマンドでも指定した行や特定の文字列が含まれる行を抽出することができます。

【コマンドの書き方】
```bash:標準出力の行を抽出するコマンド
(コマンド) | awk 'NR==(行数)'
```
```bash:文字列が含まれる行を抽出するコマンド
(コマンド) | awk '/(文字列)/'
```

【コマンドのサンプル】
```bash:サンプル
$ # hoge.txtの中身
$ cat hoge.txt 
top
middle0
middle1
middle2
middle3
end
$ 
$ # 2行目を抽出
$ cat hoge.txt | awk 'NR==2'
middle0
$ 
$ # 先頭行から2行目までを抽出
$ cat hoge.txt | awk 'NR<=2'
top
middle0
$ 
$ # middleが含まれる行を抽出
$ cat hoge.txt | awk '/middle/'
middle0
middle1
middle2
middle3
$ 
$ # 先頭行とmiddleが含まれる行を抽出
$ cat hoge.txt | awk 'NR==1||/middle/'
top
middle0
middle1
middle2
middle3
$ 
```

## ・長く複雑な文字列を追加する

`sed`コマンド以外に標準出力のテキストを操作する方法として`cat`コマンドが使用できます。
こちらの方法ではバックスラッシュなどを行末につけなくても複数行追加でき、shellの変数やコマンドも使用できます。
また、ダブルクォート("")やシングルクォート('')は対になっていればエスケープせずに使用できます。

【コマンドの書き方】
```bash:先頭行に文字列を追加するコマンド
(コマンド) | cat <(cat <<EOF
先頭行に追加したい文字列
EOF
) -
```
```bash:末尾行に文字列を追加するコマンド
(コマンド) | cat - <(cat <<EOF
末尾行に追加したい文字列
EOF
)
```
```bash:先頭行と末尾行に文字列を追加するコマンド
(コマンド) | cat <(cat <<EOF
先頭行に追加したい文字列
EOF
) - <(cat <<EOF
末尾行に追加したい文字列
EOF
)
```

【コマンドのサンプル】
```bash:hoge.txtの中身
$ cat hoge.txt
top
middle
end
$ 
```
```bash:先頭行にheaderを2行追加
$ cat hoge.txt | cat <(cat <<EOF
> header1 変数:$PWD、コマンド:$(date "+%Y/%m/%d-%H:%M:%S")
> header2 'シングル' + "ダブル"
> EOF
> ) -
header1 変数:/home/hoge、コマンド:2022/07/10-11:26:35
header2 'シングル' + "ダブル"
top
middle
end
$ 
```
```bash:末尾行にfooterを2行追加
$ cat hoge.txt | cat - <(cat <<EOF
> footer1 変数:$PWD、コマンド:$(date "+%Y/%m/%d-%H:%M:%S")
> footer2 'シングル' + "ダブル"
> EOF
> ) -
top
middle
end
footer1 変数:/home/hoge、コマンド:2022/07/10-11:27:35
footer2 'シングル' + "ダブル"
```
```bash:先頭行にheader、末尾行にfooterを2行追加
$ cat hoge.txt | cat <(cat <<EOF
> header1 変数:$PWD、コマンド:$(date "+%Y/%m/%d-%H:%M:%S")
> header2 'シングル' + "ダブル"
> EOF
> ) - <(cat <<EOF
> footer1 変数:$PWD、コマンド:$(date "+%Y/%m/%d-%H:%M:%S")
> footer2 'シングル' + "ダブル"
> EOF
> )
header1 変数:/home/hoge、コマンド:2022/07/10-11:29:06
header2 'シングル' + "ダブル"
top
middle
end
footer1 変数:/home/hoge、コマンド:2022/07/10-11:29:06
footer2 'シングル' + "ダブル"
```

:::note warn

変数やコマンドを展開せずに表示したい場合はEOFをシングルクォートで囲ってください。

```bash:EOFをシングルクォートで囲う
$ cat hoge.txt | cat <(cat <<'EOF'
> header1 変数:$PWD、コマンド:$(date "+%Y/%m/%d-%H:%M:%S")
> header2 'シングル' + "ダブル"
> EOF
> ) -
header1 変数:$PWD、コマンド:$(date "+%Y/%m/%d-%H:%M:%S")
header2 'シングル' + "ダブル"
top
middle
end
```


:::

# コマンドの使い分け

## ・数やサイズによってコマンドを変える

`sed`コマンドでは柔軟な操作ができますが、大量のデータやサイズの大きなファイルを扱う場合は処理に時間がかかり、メモリの消費も大きくなります。

そういう場合に先頭行や末尾行の抽出を行う際には`sed`コマンドではなく、`head`コマンドや`tail`コマンドを使用すると早く処理でき、メモリの消費量も少なくなります。

```bash:サンプル
$ # sedコマンドで先頭行と末尾行を抽出
$ sed -n -e '1p' -e '$p' hoge.txt 
top
end
$ 
$ # headコマンドとtailコマンドで先頭行と末尾行を抽出
$ (head -1;tail -1) <hoge.txt 
top
end
$ 
```

ちなみに手元にあった1G(800万行)のファイルで上記のコマンドを10回実行した際の時間を計測してみたところ、`sed`コマンドは`head`コマンド + `tail`コマンドの組み合わせの400倍の時間がかかりました。

:::note warn

`sed`コマンドで大量のデータやサイズの大きなファイルを扱う場合は[[行を抽出する(標準出力)]](#行を抽出する標準出力)の注意事項に記載している`q`コマンドを使用してください。

:::

また、文字列を含む行の抽出についても大量のデータやサイズの大きなファイルの場合は`sed`コマンドではなく`grep`コマンドを使用すると早く処理でき、メモリの消費量も少なくなります。

参考までに`grep`コマンドを基準として、上記で計測したファイルから文字列が含まれる行を抽出する際にかかった時間をコマンド別に倍率で記載しておきます。

| コマンド | 倍率 |
|---|---|
| grep | 1.0 |
| sed | 4.4 |
| perl | 4.5 |
| awk | 5.5 |

先頭行と末尾行を抽出した計測ほど圧倒的な差はありませんが、`grep`コマンドの強力な性能はイメージできたと思います。

## ・後続処理によってコマンドを変える

数やサイズの小さなデータで単純に行の追加・削除・抽出だけを行いたいのであれば`sed`コマンドで十分ですが、その後に処理が継続するようであれば、後続処理の内容によってコマンドを変更する方がシンプルに記述できる場合があります。

例えば特定文字列がある行を抽出し、行の1番目の項目を抜き出したい...といった場合は、`sed`コマンドで行の抽出をしてから`awk`コマンドで項目の抜きだすより、初めから`awk`コマンドを使用して行の抽出と項目の抜きだす方がシンプルに記述できます。

```bash:サンプル
$ # fuga.txtの中身
$ cat fuga.txt 
fuga top
fuga middle
fuga end
$ 
$ # sedでmiddleが含まれる文字を抽出し、awkで1項目目を抜き出し
$ cat fuga.txt | sed -n '/middle/p' | awk '{print $1}'
fuga
$ 
$ # awkだけでmiddleが含まれる文字を抽出し、1項目目を抜き出し
$ cat fuga.txt | awk '/middle/{print $1}'
fuga
$ 
```

また、1行目にあるヘッダーと文字列が含まれる行を抽出したい場合は`sed`コマンドでも実現できますが、`grep`コマンド、`awk`コマンドでも代替できる場合があります。

単純に抽出処理だけしたい場合は`grep`コマンド、後続処理が文字列置換であれば`sed`コマンド、後続処理が項目の抜き出しや集計であれば`awk`コマンドを使用するなど、後続処理によってコマンドを変える方がシンプルに記述できます。

```bash:サンプル
$ # sedコマンドを使用してpsコマンドの結果をヘッダーを残しつつnginxが含まれる行を抽出
$ ps aux | sed -n -e '1p' -e '/[n]ginx/p'
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root       287  0.0  0.0　　　　　　1176　　　　　　100 ?        Ss   Jul10   0:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf
nginx      287  0.0  0.0　　　　　　1189   119 ?        S    Jul10   0:27 nginx: worker process                   
nginx      287  0.0  0.0　　　　　　1190   122 ?        S    Jul10   0:26 nginx: worker process                   
$ 
$ # grepコマンドを使用してpsコマンドの結果をヘッダーを残しつつnginxが含まれる行を抽出
$ ps aux | grep -e ^USER -e [n]ginx
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root       287  0.0  0.0　　　　　　1176　　　　　　100 ?        Ss   Jul10   0:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf
nginx      287  0.0  0.0　　　　　　1189   119 ?        S    Jul10   0:27 nginx: worker process                   
nginx      287  0.0  0.0　　　　　　1190   122 ?        S    Jul10   0:26 nginx: worker process                   
$ 
$ # awkコマンドを使用してpsコマンドの結果をヘッダーを残しつつnginxが含まれる行を抽出
$ ps aux | awk 'NR==1||/[n]ginx/'
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root       287  0.0  0.0　　　　　　1176　　　　　　100 ?        Ss   Jul10   0:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf
nginx      287  0.0  0.0　　　　　　1189   119 ?        S    Jul10   0:27 nginx: worker process                   
nginx      287  0.0  0.0　　　　　　1190   122 ?        S    Jul10   0:26 nginx: worker process                   
$ 
```

:::note info

上記のサンプルでは抽出コマンドのプロセスの表示を除外するために抽出対象の文字列の
先頭文字を`[]`で囲っています。

抽出コマンドのプロセスを除外する必要がなければ`[]`で囲う必要はありません。

```bash:[]を除外すると末尾行にsedコマンドのプロセスが表示される
$ ps aux | sed -n -e '1p' -e '/nginx/p'
USER       PID %CPU %MEM   STAT START   TIME COMMAND
root       287  0.0  0.0   Ss   Jul10   0:00 nginx: master
nginx      287  0.0  0.0   S    Jul10   0:27 nginx: worker
nginx      287  0.0  0.0   S    Jul10   0:26 nginx: worker
piyo       106  0.0  0.0   S+   13:48   0:00 sed -n -e 1p -e /nginx/p
$ 
```

:::

## ・文字列によってコマンドを変える

1行(ワンライナー)で全てのコマンドを記述する必要がない限り、固定された長い文字列を追加する場合は、文字列をファイルに格納して`cat`コマンドを使用する方法をオススメします。

改行、特殊文字、変数やコマンドなども気にする必要がなく、コマンド自体の可読性も上がるので、無理に`sed`コマンドで頑張る必要はありません。

(使い方は[[`sed`コマンド以外を使用する方法 - 行を追加する]](#行を追加する)の`cat`コマンドを参照)

また、ワンライナーの場合でも、SQLなどのシングルクォートが頻繁に出てくる文字列を扱う場合はエスケープするのが面倒だったり、変数やコマンドを使用したかったりするケースは多いと思いますが、そんな場合は`cat`コマンドとプロセス置換(`<()`)、ヒアドキュメント(`<<`)を使用すると比較的自由に文字列を追加することができます。

(使い方は[[`sed`コマンド以外を使用する方法 - 長く複雑な文字列を追加する]](#長く複雑な文字列を追加する)参照)

# `sed`コマンド操作の解説

今回紹介した`sed`コマンドの操作について、簡単に解説を記載していきます。

下記はよく見る`sed`コマンドの使い方で、ファイルの中身を読み取って全ての行に対して置換処理を行い結果を標準出力へ出力しています。(`^`は行の先頭を意味します)

```bash:hoge.txtの中身
top
middle
end
```

```bash:hoge.txtの内容を読み取って全ての行の先頭にADDを追加して標準出力に文字列を出力
$ sed -e 's/^/ADD /' hoge.txt
ADD top
ADD middle
ADD end
$ 
```

また、`sed`コマンドではスクリプトコマンドの前にアドレス(処理を行う対象の行数)を指定でき、省略した場合は全行が処理の対象となります。

```bash:先頭文字を置換して文字列を追加
$ # 2行目の先頭をADDに置換
$ sed -e '2s/^/ADD /' hoge.txt 
top
ADD middle
end
$ 
```

上記のコマンドを分解すると下記のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699056/fd907d0e-2e79-da8a-a42f-edfb9a311a71.png)
本稿で使用しているオプション、アドレス、スクリプトコマンド、入力、出力の意味は下記の通りになります。

- オプションの説明

| オプション | 説明 |
| --- | --- |
| `-e` | スクリプトコマンドを実行する。<br>(一つだけの場合は省略可能で、複数指定可能) |
| `-i` | 入力ファイルとして指定したファイルを書き換える。<br>　 |
| `-n` | 読み込んだテキストを出力しない。<br>(指定しない場合は読み込んだテキストを全て出力) |

- アドレスの説明

| アドレス | 説明 |
| --- | --- |
| `数字` | `数字`の行を指定する。 |
| `$` | 末尾行を指定する。 |
| `/文字列/` | `文字列`が含まれる行を指定する。 |
| `開始行,終了行` | `開始行`から`終了行`までを指定する。 |
| `/開始文字列/,/終了文字列/` | `開始文字列`が含まれる行から`終了文字列`が含まれる行を指定する。 |

- スクリプトコマンドの説明

| スクリプトコマンド | 説明 |
| --- | --- |
| `s/置換前/置換後/` | 指定した行の`置換前`の文字列を`置換後`の文字列に置き換える。 |
| `i文字列` | 指定した行の前に`文字列 + 改行`を追加する。 |
| `a文字列` | 指定した行の後に`文字列 + 改行`を追加する。 |
| `p` | 指定した行を出力する。 |
| `d` | 指定した行を削除する。 |
| `q` | 指定した行を出力してコマンドを終了する。 |
| `Q` | 指定した行でコマンドを終了する。(出力しない) |
| `eコマンド` | `コマンド`の実行結果を指定した行の前に追加する。 |

- `sed`コマンドへの入力の説明

`sed`コマンドで処理させたいテキストを渡す方法は上記のように`sed`コマンドの引数として入力ファイルのパスを指定する方法と、`sed`コマンドの標準入力にテキストを渡す方法の2種類あります。

```bash:sedコマンドの引数として処理させたいテキストが格納されたファイルパスを渡す方法
$ # sedコマンドの引数としてhoge.txtを渡す
$ sed -e 's/^/ADD /' hoge.txt
ADD top
ADD middle
ADD end
$ 
$ # プロセス置換を使用してsedコマンドの引数としてhoge.txtを渡す
$ sed -e 's/^/ADD /' <(cat hoge.txt)
ADD top
ADD middle
ADD end
$ 
```

```bash:sedコマンドの標準入力として処理させたいテキストを渡す方法
$ # catコマンドの標準出力をパイプを使用してsedコマンドの標準入力に渡す
$ cat hoge.txt | sed -e 's/^/ADD /' hoge.txt
ADD top
ADD middle
ADD end
$ 
$ # 標準入力へのリダイレクトを使用してsedコマンドの標準入力にhoge.txtの中身を渡す
$ sed -e 's/^/ADD /' <hoge.txt 
ADD top
ADD middle
ADD end
$ 
```

- `sed`コマンドの出力の説明

`sed`コマンドで処理した結果を出力する方法として標準出力に出力する、`sed`コマンドの`-i`オプションを使用して入力ファイルに出力する(書き換える)、別なファイルに出力する、の3種類があります。

```bash:hoge.txtのテキストをsedコマンドで処理して標準出力に出力する
$ sed -e 's/^/ADD /' hoge.txt
ADD top
ADD middle
ADD end
$ 
```
```bash:hoge.txtのテキストをsedコマンドで処理してhoge.txtに出力する(書き換える)
$ sed -i -e 's/^/ADD /' hoge.txt
$ cat hoge.txt
ADD top
ADD middle
ADD end
$ 
```
```bash:hoge.txtのテキストをsedコマンドで処理してpiyo.txtに出力する
$ sed -e 's/^/ADD /' hoge.txt > piyo.txt
$ cat piyo.txt
ADD top
ADD middle
ADD end
$ 
```

:::note warn

`sed`コマンドは入力されたテキストの先頭行から末尾行まで読み込んで処理していきます。

そのため、先頭行のみ抽出するような下記コマンドであっても末尾行まで読みに行くため、読み込むテキストが大きい場合は処理に時間がかかるので注意してください。

```bash:先頭行のみ抽出
$ sed -n '1p' hoge.txt 
top
$ 
```

なお、`q`コマンドや`Q`コマンドを使用すると、末尾行まで読み込まずに指定した行で読み込みを中止してくれます。

:::

本稿で紹介した使用方法は解説した内容の応用で実現していますが、`sed`コマンドそのものを理解するともっと柔軟に応用できますので興味のある方は是非`sed`コマンドのmanや解説サイトをご覧ください。

# おわりに

だいぶ文章や普段使用しなさそうな記述を削ったつもりですが、分量が多すぎて見にくいかもしれません。
~~本来はそれぞれの解説を書く予定でしたが、それは別の記事として投稿しようと思います。~~
コマンドのサンプルだけだと応用ができなくなってしまうので、できるだけ文章を削って`sed`コマンドの簡単な解説を追加しました。

また、[[Linux初心者がコマンドを使うときに知っておくと少し幸せになること]](https://qiita.com/b-mente/items/c3ea0537d377c587c8b1)でも基本的なコマンドの使い方を紹介しているので、本稿の記載で分からない部分があればそちらを確認すると解決するかもしれません。