Open31

shell備忘録

jnuankjnuank

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

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

特定の文字列から始まるものを列挙する

たとえば、特定フォルダ下でどんなファイルをよく参照しているかなど

grep -rho '/hoge/.*' ./*  | awk '{print $1}' | LANG=C sort -k1,1 -u
jnuankjnuank

forとwhileとサブシェル

fotとwhileはどちらともサブシェル扱いだと思っていたけど、どうやら違うっぽい。

for文で途中で抜ける場合は、breakを使用する。

※zshの場合は、whileでexitしたらターミナルがクローズしたので、サブシェルで動いてないのかも

参考
https://qiita.com/doranekohc/items/3a047972b71bb442ccc2

for

$ for times in $(seq 1 3); do echo 1; exit 2; done ; echo $?
1
logout

while

seq 1 3 | while read line; do echo $line ; exit 100 ; done ; echo $?
1
100

aws cli のリトライ処理をwhileで実装する

seq 1 3 | while read times; do
  SEND_SUCCEED=10

  aws s3 cp hoge s3://${s3_bucket}/hoge
  [ $(plus ${PIPESTATUS[@]}) -eq 0 ] && exit ${SEND_SUCCEED}
  sleep 10
done
# while終了時ステータスチェック
[ $? != ${SEND_SUCCEED} ] && exit 1

jnuankjnuank

testコマンドの拡張 [[

bashの拡張記述

日付や数値比較などが、-lt-gt じゃなくて、不等号が使える。
<=>= は使えないので注意。

[[ 文字列 =~ 正規表現 ]]
文字列が正規表現に一致すれば真。
[[ 文字列1 < 文字列2 ]]
現在のロケールの辞書順で文字列1が文字列2よりも前なら真。
[[ 文字列1 > 文字列2 ]]
現在のロケールの辞書順で文字列1が文字列2よりも後なら真。

参考:
https://fumiyas.github.io/2013/12/15/test.sh-advent-calendar.html

if [[ "${today}" < 20210101 ]]; then
jnuankjnuank

サブシェル バックグラウンド実行

シェル、コマンドの後ろに &

$ (for times in $(seq 2 2 10); do echo ${times}; sleep 1; done) &   (for times in $(seq 1 2 9); do echo ${times}; sleep 1; done) ;
[1] 7838
2
1
3
4
6
5
7
8
10
9
[1]+  Done                    ( for times in $(seq 2 2 10);
do
    echo ${times}; sleep 1;
done )

seqコマンド

スタート、ステップ エンド

$ seq 2 2 10
2
4
6
8
10
jnuankjnuank

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
jnuankjnuank

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 <文字列> で実行する

jnuankjnuank

ヒアドキュメント

いろいろ勘違いしていたけど、ヒアドキュメントは、 << 終端文字列 。標準入力として扱われる。

なので、標準入力先のコマンドは、受け取れればなんでも良い。

cat << FIN
heredoc> Alice
heredoc> Bob
heredoc> Cathy
heredoc> FIN
Alice
Bob
Cathy
cut -d. -f1 << FIN
heredoc> a.bc
heredoc> bfdfd.ww
heredoc> 123.4666
heredoc> FIN
a
bfdfd
123
grep 'a' << FIN
heredoc> alice
heredoc> bob
heredoc> cathy
heredoc> FIN
alice
cathy

標準入力したコマンドが何かしら標準出力するなら、パイプだったりリダイレクトを繋げても良い。

参考
http://everything-you-do-is-practice.blogspot.com/2017/11/bash-zsh.html

jnuankjnuank

aws s3 api で存在確認

aws s3api head-object --bucket ${bucket} --key ${filepath}  &> /dev/null

&> は、標準出力と標準エラーのどちらも同じ場所にリダイレクトする意味

存在している場合は、jsonが標準出力で返ってきて、存在していない場合は、標準エラーが返ってくる。
ステータスコードは存在していたら、0。存在してなかったらそれ以外。

標準出力、標準エラーどちらとも要らないので、&> /dev/null で捨てる。

|& で標準出力、標準エラーどちらとも標準入力として渡すことも可能

aws s3api head-object --bucket ${bucket} --key ${filepath}  |& cat > /dev/null

パイプ、リダイレクト参考
https://qiita.com/harasakih/items/868a850fcdc99a2c37b0

aws s3 api参考
https://cloudpack.media/37449

jnuankjnuank

プロセスをN秒ごとに監視

hoge.sh がちゃんと動いているか監視したい

`watch -d -n 5 'ps auxw | grep hoge.sh'`
  • n:数字を指定すると、その秒数ごとにコマンドを実行
  • d:前回のコマンド実行結果とdiffが出ている場合はハイライト表示
jnuankjnuank

1つ前の引数を扱う

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

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

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

$ mkdir hoge ; cd $_
jnuankjnuank

shellというか、vimだけど。

数字にカーソル合わせた後、control + x  → デクリメント
数字にカーソル合わせた後、control + a  → インクリメント

jnuankjnuank

dateコマンド

$ date "+%Y%m%d"
20210120
$ date "+%y%m%d"
210120
jnuankjnuank

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

$ 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
jnuankjnuank

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

パイプで繋ぐのが一番はやい

grep hoge in.txt | grep moga

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

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

jnuankjnuank

指定日より古いファイルを削除する

/var/tmp などにゴミが溜まってきているときによくやる

# 前日分までのファイルの詳細を列挙する
$ find /var/tmp/ -mtime +1 -ls

# 怖いので一回期待する値がxargsに渡っているか確認
$ find /var/tmp/ -mtime +1 | xargs echo


# 前日分までのファイルを削除する
$ find /var/tmp/ -mtime +1 | xargs rm 

jnuankjnuank

リダイレクトとパイプ

  • >:標準出力を指定ファイルに書き出す(ファイルが無かったら新規作成。ファイルがあったら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:標準エラー出力を標準出力に変更する

jnuankjnuank

コマンド置換

$() で囲ったコマンドの結果を展開してくれる。

# crontabのバックアップの名前で現在の日付使いとか

crontab -l > crontab.$(date "+%Y%m%d%H%M")

自分は<() でコマンドを囲ったときの違いと混乱する時があるけど、

コマンドの結果をただ展開するだけなのが、$()で囲うパターン。
コマンドの結果を標準入力として渡すのが、<() で囲うパターンという認識。

jnuankjnuank

順位付け

awk使う。

[root@67d6d46b0970 linux]# ls -lt  | awk 'BEGIN{num=0} {num++; print num, $0}'
1 total 116
2 -rw-r--r-- 1 root root  576 Feb  2 14:40 README.md
3 -rwxr-xr-x 1 root root 8880 Feb  2 14:30 head
4 -rw-r--r-- 1 root root  731 Feb  2 14:30 head.c
5 -rwxr-xr-x 1 root root 8656 Jan 24 09:11 catstd
6 -rw-r--r-- 1 root root  404 Jan 24 09:11 catstdio.c
7 -rw-r--r-- 1 root root   30 Jan 24 09:10 data3
8 -rw-r--r-- 1 root root   16 Jan 24 09:07 data2
9 -rwxr-xr-x 1 root root 8712 Jan 24 09:00 cat
10 -rw-r--r-- 1 root root 4096 Jan 24 07:56 data
11 -rwxr-xr-x 1 root root 8784 Jan 16 02:19 wc
12 -rw-r--r-- 1 root root  980 Jan 16 02:19 wc.c
13 -rw-r--r-- 1 root root 1018 Jan 16 01:41 cat.c
14 -rwxr-xr-x 1 root root 8448 Jan 15 14:44 bell
15 -rw-r--r-- 1 root root  104 Jan 15 14:44 bell.c
16 -rwxr-xr-x 1 root root 8448 Jan 15 12:59 args
17 -rw-r--r-- 1 root root  195 Jan 15 12:59 args.c
18 -rw-r--r-- 1 root root   95 Jan 15 12:50 hello.c

nlコマンドでいい

$ echo echo "aaa bbb ccc" | xargs -n1 | nl -n ln
1     	aaa
2     	bbb
3     	ccc
jnuankjnuank

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

exec コマンドを使うことで、以降の標準エラー出力が指定したファイルへ書き出される。

exec 2> /var/tmp/logfile

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

cat ${tmp}-data_log 1>&2
jnuankjnuank

行の操作

複数行を一行にまとめる

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

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

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

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

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

複数列を一列にする

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

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

yesコマンドで欲しい文字列の繰り返し

$ yes hoge
hoge
hoge
hoge
...
jnuankjnuank

awkで区切り文字をカンマ区切りにしたい

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

{print} だと {print $0} と解釈されるから、OFSが利かないっぽい

jnuankjnuank

○行おきにレコード出力

$ cat test
aaa,bbb,ccc
ddd,eee,fff
ggg,hhh,iii
jjj,kkk,lll

$ cat test | awk  'BEGIN{FS=","} NR%2==0  {print $1}'
ddd
jjj
jnuankjnuank

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'

jnuankjnuank

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

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

$ 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
jnuankjnuank

grepでの複数行検索

grep -f ファイル名で、検索対象に対して、指定したファイルの中にある値でgrepしてくれる。

$ cat targetfile 
hoge
moge
koge
tige

$ cat grepfile  
hoge
moge
tige
aaa
bbb

$ cat targetfile| grep -f grepfile 
hoge
moge
tige

# こちらと同等
$ cat targetfile| grep -e hoge -e moge -e tige
hoge
moge
tige


ヒアドキュメントでも可能


$ cat targetfile | grep -f <(cat << FIN
pipe cmdsubst heredoc> tige
pipe cmdsubst heredoc> hoge
pipe cmdsubst heredoc> moge
pipe cmdsubst heredoc> FIN
pipe cmdsubst> )
hoge
moge
tige

jnuankjnuank

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

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

jnuankjnuank

標準入力から受け取ったものをvimで開く

cat test.txt | vi -

pbpaseteと一緒に使ったり、取ってきたログをvimで見たりしつつ修正して保存するときに効果的

pbpaste | vi -