🗒️

シェルでこれどうやるんだっけ?ってなる自分のための備忘録

2022/05/03に公開

はじめに

忘れっぽいので、シェルでこうしたい。っていうのをまとめていく。
何かあったら適宜追加するかも。

awkでシングルクォート、ダブルクォートを扱う

参考した記事で出来たけど、ぱっと見て自分がすぐに理解できなかったので、
分解しながら考えていく
https://orebibou.com/ja/home/201703/20170303_002/

awk で ダブルクォートで囲む

ちなみに、awk のprint() は()自体が省略可能

$ cat amaimono.txt | awk '{print $1}'
apple
banana
choco

$ cat amaimono.txt | awk '{print($1)}'
apple
banana
choco

"で囲うと、文字列扱いとなる(変数として展開されない)

$ cat amaimono.txt | awk '{print "$1"}'
$1
$1
$1

"を文字列として扱いたいので、"で囲いつつ\でエスケープする

"\""  → "

$ cat amaimono.txt | awk '{print "\"" $1}'
"apple
"banana
"choco

先頭と行末でそれぞれ囲もうとすると、こうなる

$ cat amaimono.txt | awk '{print "\"" $1 "\""}'
"apple"
"banana"
"choco"

awk で シングルクォートで囲む

シングルクォートの場合若干複雑。

$ cat amaimono.txt | awk '{print "'\''" $1 }' 
'apple
'banana
'choco

文字列で扱いたい為、"で囲みつつ、'\'' でエスケープしたシングルクォートをシングルクォートで囲むことをしている。
これはawkに渡すprogram text自体がシングルクォートで囲んでいる為、\'だけだとprogram textを囲っているシングルクォートだと認識されてしまう。

そのため、program自体をシングルクォートで一旦囲み、文字としてのシングルクォートをエスケープして書いてから、更にprogramの続きを書くためのシングルクォートを書く必要がある

"'\''"'

$ cat amaimono.txt | awk '{print "'\''" $1 "'\''"}'
'apple'
'banana'
'choco'

awkで出力する区切り文字をカンマ区切りにしたい

OFSを指定する。

$ echo "a b c" | awk 'BEGIN{OFS=","} {print $1, $2, $3}'
 a,b,c

{print} だと {print $0} と解釈されるので、OFSが利かない。

参考:
https://ebc-2in2crc.hatenablog.jp/entry/2021/06/13/165226

awkの入力の区切り文字をスペース以外にしたい

FSを指定、もしくは-Fオプションを使う

 cat test2 
ばなな,100
りんご,120
ぶどう,200

$ cat test2  | awk -F, '{print $1}'
ばなな
りんご
ぶどう

$ cat test2  | awk 'BEGIN{FS=","} {print $1}'
ばなな
りんご
ぶどう

cutコマンドを使うのもあり

$ cat test2 | cut -d, -f1
ばなな
りんご
ぶどう

リダイレクトとパイプ

  • >:標準出力を指定ファイルに書き出す(ファイルが無かったら新規作成。ファイルがあったら0バイトにした上で上書き)
    • zshの場合はデフォルトで上書き防止となっている。問答無用で上書きしたい場合は、 >| で指定。
     $ echo "d e f" > hogefile
     zsh: file exists: hogefile
     $ echo "d e f" >| hogefile
     $  cat hogefile
     d e f
    
  • >>:標準出力を指定ファイルに書き出す(ファイルが無かったら新規作成。ファイルがあったら追記)
  • 2>> : 標準エラーを指定ファイルに書き出す(ファイルが無かったら新規作成。ファイルがあったら追記)
  • <:標準入力を指定する。ファイルじゃなくても、コマンドの結果を渡すこともできる
cut -d" " -f 1 <(ls -lt)
合計
-rw-rw-r--
-rw-rw-r--
-rw-rw-r--

# 以下コマンドと一緒
ls -lt | cut -d" " -f 
  • &>:標準出力、標準エラー出力を指定ファイルに書き出す(ファイルが無かったら新規作成。ファイルがあったら0バイトにした上で上書き)
aws s3 cp s3://hoge hoge &> /dev/null
  • |&:標準出力、標準エラー出力を次のコマンドの標準入力とする。2>&1 | と一緒

  • 1>&2:標準出力を標準エラーと同じ出力先に変更する

  • 2>&1:標準エラー出力を標準出力と同じ出力先に変更する

参考
https://qiita.com/progrhyme/items/e99be732c2e62d4a7641

xargs

いろいろ忘れがちなので。

$ cat << FIN | xargs -I{} sh -c 'echo hello {}'
> Alice
> Bob
> Cathy
> FIN

hello Alice
hello Bob
hello Cathy

  • I : replace-str。オプションの後に指定した文字列を、標準入力から受け取った値に置き換える

  • sh -c <文字列> : 指定した文字列のコマンドを実行する

以下は結果は同じ

$ echo alice
alice
$ sh -c 'echo alice'
alice

xargsに一連のパイプしたコマンドを渡したい場合には、sh -c <文字列> で実行する。

xargsの後にコマンドを指定するが、何も指定しないとechoコマンドが使われる。

※-pを指定すると、xargsが実行しようとするコマンドを確認することができる。
参考:
https://atmarkit.itmedia.co.jp/ait/articles/1801/19/news014.html#sample2

$ cat << FIN | xargs -I{} -p sh -c 'echo hello {}'
pipe heredoc> Alice
pipe heredoc> Bob
pipe heredoc> Cathy
pipe heredoc> FIN

sh -c 'echo hello Alice' ?...
sh -c 'echo hello Bob' ?...
sh -c 'echo hello Cathy' ?...

特定の文字列に一致するものを取得

grep -rho 'hoge' ./* --exclude=fuga
  • r:再帰
  • h:検索結果からファイル名を除外する
  • o:--only-matching。マッチした文字列のみ抽出
  • l:ファイル名のみ列挙する
  • --exclude:指定ファイルを除外する

複数の文字列を検索したい

and条件で検索するなら、パイプで繋ぐのが一番はやい。

grep hoge in.txt | grep moga

or条件なら、-e で条件を繋げる

grep -e hoge -e fuga in.txt

大量の文字列のいずれかに一致するか検索したい

量が多い場合(従業員リストからIDで検索したいなど)、パイプで繋げなくても、検索したい文字列をファイルに列挙しておき、-f オプションで指定することが可能。

$ cat test                
ばなな 100
りんご 120
ぶどう 200

$ cat pattern.txt 
100
ばなな
200

# 1行目は、ばななと100に一致している
$ grep -f pattern.txt test
ばなな 100
ぶどう 200

参考:
https://it-ojisan.tokyo/grep-f/

大量にあるファイルを一発で置換する

自分で特定のファイル名に変更するシェルコマンドをawkで組み立て、shに標準入力で渡す。

参考:
http://pixelbeat.jp/rename-bunch-of-files-with-oneline-command/

$ ls hoge.*
hoge.json  hoge.swp  hoge.txt

# コマンドを組み立て
$ ls hoge.* | awk -F"." '{print "mv "$0" moge."$2}'
mv hoge.json moge.json
mv hoge.swp moge.swp
mv hoge.txt moge.txt

# 組み立てコマンドを実行する
$ ls hoge.* | awk -F"." '{print "mv "$0" moge."$2}' | bash
$ ls moge.*
moge.json  moge.swp  moge.txt

uuid生成する

$ uuidgen 
e283483f-7638-4af4-92f3-7a97be2e6081

適当なUUIDをテストコードなどで使いたい時、改行が邪魔なので

uuidgen | tr -d '\n' | pbcopy

標準エラー出力をログに書き込む

exec コマンドを使うことで以降、そのプロセスでの標準エラー出力は指定したファイルへ書き出される。(スクリプト書くときの一番最初に書いたりする)

exec 2> /var/tmp/logfile

バッチで途中処理ファイルをtmpデータに吐き出しておき、その中身をログで確認したい場合には、
上記のexecと組み合わせつつ、cat で標準エラー出力すればログに書き出せる。

cat ${tmp}-data_log 1>&2

引数の何個目以降を使用する

functionやスクリプトで渡された引数は、$1, $2,... と取得できるが、N番目以降と取りたい場合は、${@:N}と書く

$ function args_test() {                 
function> echo "引数すべて: $@"
function> echo "引数1個目: $1"
function> echo "引数3個目以降:${@:3}"
function> }

$ args_test a b c d e   
引数すべて: a b c d e
引数1個目: a
引数3個目以降:c d e

行の操作

xargsを使う。

複数行を一行にまとめる

 % cat test 
ばなな 100
りんご 120
ぶどう 200

 % cat test| xargs
ばなな 100 りんご 120 ぶどう 200

○行ごとに一行にまとめる

xargs -L数字で、指定した行ごとに1行にまとめる

 % cat test2
Aさん
1/1うまれ
男
Bさん
2/1生まれ
女
Cさん
3/1生まれ
男

# 3行ごとに1行にまとめる(1行は3列になる)
% cat test2 | xargs -L3
Aさん 1/1うまれ 男
Bさん 2/1生まれ 女
Cさん 3/1生まれ 男

複数列を一列にする

xargs -n数字で指定

-n, --max-argsは、xargsに指定されたコマンドを1回実行する時の引数上限。

参考:
https://atmarkit.itmedia.co.jp/ait/articles/1801/19/news014.html#opt

 % cat test 
ばなな 100
りんご 120
ぶどう 200

# 1
% cat test | xargs -n1
ばなな
100
りんご
120
ぶどう
200

もしくは、trコマンドを使って、改行を削除する(最後の改行も無くなるので注意)

$ cat test |  tr -d '\n'
ばなな 100りんご 120ぶどう 200

1つ前の引数を扱う

$_ が直前のコマンドの最後の引数を格納している特殊変数

lsしたものをcatとかviしたい時や、mkdirしたものにcdしたい時などで使える

$ ls tmp-file
tmp-file
$ echo $_
tmp-file

$ mkdir hoge ; cd $_

ファイルの一部を修正して上書き

$ cat memo
りんご 100
ばなな 200
オレンジ 300

$ cat memo | sed 's/オレンジ/グレープ/' > memo_edit
$ cat memo_edit
りんご 100
ばなな 200
グレープ 300

$ mv memo_edit memo

確認手順すっ飛ばすなら、sedのiオプションで上書き可能

$ sed -i 's/オレンジ/グレープ/' memo
$ cat memo
りんご 100
ばなな 200
グレープ 300

○行ごとにファイル分割する

splitコマンドを使用。
-l で分割する行数を指定する可能。

split -l 3 -d ファイル名 [分割後のファイル名のprefix]

参考:
https://atmarkit.itmedia.co.jp/ait/articles/1711/24/news016.html

空行削除

sed '/^$/d'

seqコマンド

seq 1 10 だと、1から10まで順番になる。

2つ刻みで行きたい場合には、開始、インクリメントする値、終了の3つを指定する

2刻み

$ seq 2 2 10
2
4
6
8
10

seqコマンドのフォーマット

-f を付けることで、文字列と連結も可能

$ seq -f 'user_%g' 1 10
user_1
user_2
user_3
user_4
user_5
user_6
user_7
user_8
user_9
user_10

フォーマットで%02gなどで0埋めできる

$ seq -f 'user_%02g' 1 5
user_01
user_02
user_03
user_04
user_05

文字列数を確認する

hoge="hoge"
# -n で改行コードを無くさないと駄目
echo -n ${hoge} | wc -m

# もしくは
echo ${#hoge}

参考:
https://qiita.com/jun68ykt/items/3095b63cde309aff974d

表示するテキストの幅合わせをする

うまくインデントを合わせたい

$ cat amaimono.txt 
商品 金額 在庫数
apple 10 100
banana 200 30
choco 300 99999
$ cat amaimono.txt | column -t
商品    金額  在庫数
apple   10    100
banana  200   30
choco   300   99999

カンマ区切りの場合は、デリミタを指定する必要がある

 cat amaimono.csv | column -t
商品,金額,在庫数
apple,10,100
banana,200,30
choco,300,99999


$ cat amaimono.csv | column -t -s, 
商品    金額  在庫数
apple   10    100
banana  200   30
choco   300   99999

文字をめっちゃ生成する

同じ文字を生成する

ブレース展開で同じ文字生成

hogeの後に空白を付けている。
10個生成するのに、,が9個必要なのは注意

$ echo hoge{,,,,,,,,,}                    
hoge hoge hoge hoge hoge hoge hoge hoge hoge hoge

yesコマンドで同じ文字生成

たぶん一番手っ取り早い

$ yes hoge | head -n 10
hoge
hoge
hoge
hoge
hoge
hoge
hoge
hoge
hoge
hoge

yes [文字列]でyesの代わりに、指定した文字列を繰り返し列挙する。
そのままだとCtrl+Cで強制終了しないと止まらないので、headで欲しい分取るようにする。

参考:
https://webkaru.net/linux/yes-command/

treeコマンドで特定のファイルを除外する

-I オプション

tree -L 1 -I "target"

参考:こちらから為になったもの
https://www.oreilly.co.jp/books/9784814400485/

ターミナル操作

履歴展開

直前のコマンドを呼び出す

!! を使う

$ apt update
パッケージリストを読み込んでいます... 完了
E: ロックファイル /var/lib/apt/lists/lock をオープンできません - open (13: 許可がありません)
E: ディレクトリ /var/lib/apt/lists/ をロックできません

$  sudo !!
sudo apt update # ← シェルが履歴展開した結果が表示されて、実際にコマンドが実行される

コマンド展開としても評価できる

# おもむろに並列で投げまくりたいときとか

$ yes localhost/ping | head | tr '\n' ' '
localhost/ping localhost/ping localhost/ping localhost/ping localhost/ping localhost/ping localhost/ping localhost/ping localhost/ping localhost/ping

$ curl  --parallel --parallel-immediate --parallel-max 300  $(!!)
curl  --parallel --parallel-immediate --parallel-max 300  $(yes localhost/ping | head | tr '\n' ' ')

直前のコマンドに渡した最後の引数を展開する

!$を使う

# 編集したあと
$ vi ~/.zshrc 

# 読み込む
$ source !$
source ~/.zshrc

Discussion