全力で怠けたい

怠けるために全力を尽くしたいブログ。

シェル力が圧倒的に足りてないのでシェル力を養うためにシェル芸勉強会の問題に挑戦してみた。

シェル力が圧倒的に足りてないのでシェル力を養うためにシェル芸勉強会の問題に挑戦してみた。

感想から先に書いてしまうとパズル感覚で楽しいしシェル力を養うのにも役立ちそう。

自分が使ってる zsh のバージョン。

$ zsh --version
zsh 5.8 (x86_64-apple-darwin19.6.0)

はじめに

自分はシェル力が圧倒的に足りてないので勉強のためにシェル芸勉強会の問題に挑戦してみた。

一応自分がシェルを使う用途を書いておくと普段の仕事は Mac を使ってやっててちょっとしたことをやるのにちょろっとシェルを使う程度。

ちなみにシェル芸ってなに? ってところは WikipediaUSP 友の会 から引用しておく。

シェル芸とは、主にUNIXオペレーティングシステムにおいて「マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理を CLI端末へのコマンド入力一撃で終わらせること」(USP友の会会長・上田隆一による定義[9])である。この技術を持つ人物を指すシェル芸人という呼び方も存在する[10]。

古典的には、UNIX系OSの各種CUIシェル上で単一または複数のコマンドを一行のコマンドラインでをつなぎ合わせて処理をおこなう、いわゆる「ワンライナー」系の手順にあたる。

第10回シェル芸勉強会の問題に挑戦してみた

シェル芸勉強会という勉強会の存在は知ってたけどちゃんと見たことはなかったので、なんとなくキリがよさそうな 第10回シェル芸勉強会 の問題に挑戦してみた。

Q1

Question

次の数字の列を足し算してください。(できる人はなるべく変態的に)

$ echo 2 5 9 8 1 3 7 4
2 5 9 8 1 3 7 4

Answer

数値列を行に変換して awk コマンドで集計した。

$ echo 2 5 9 8 1 3 7 4 | tr ' ' '\n' | awk '{SUM+=$1}END{print SUM}'
39

別解。数値列を行に変換するとこは tr コマンドじゃなくて dango コマンドで変換した。

$ echo 2 5 9 8 1 3 7 4 | dango -w -n 1 | awk '{SUM+=$1}END{print SUM}'
39

別解その2。数値列をそのまま awk コマンドで集計した。

$ echo 2 5 9 8 1 3 7 4 | awk '{for(i=1;i<=NF;i++){SUM+=$i}}END{print SUM}'
39

Q2

Question

スペースと数字と改行を使って次のようなファイルを作り、書いた数を足し算してください。

$ cat nums 
 1

2 3 
 4 5
 6 7

8 9

Answer

数値列を行に変換して awk コマンドで集計した。

$ cat nums | tr ' ' '\n' | awk '{SUM+=$0} END{print SUM}'
45

別解。数値列を行に変換するとこは tr コマンドじゃなくて dango コマンドで変換した。

$ cat nums | dango -w -n 1 | awk '{SUM+=$0} END{print SUM}'
45

Q3

Question

文字数を数えてください。改行記号は数えないでください。

ueda@remote:~$ cat genkou
筆者は朝、目玉焼きを食べた。
昼、著者は卵がけごはんを食べた。
そして夜、著者はマンハッタンの夜景を
見ながらゆで玉子を食べた。

Answer

改行記号は tr コマンドで削除して wc -m で文字数を数えた。

$ cat genkou | tr -d '\n' | wc -m
      61

Q4

Q3 まではほとんど考えずに解けたけどこのへんからちょっと頭を使う感じになってきた。

Question

次のようなファイルを作り、ファイルの中に三個存在する文字を出力してください。

ueda@remote:~$ cat hoge
aabbcdabbcccdd

Answer

hoge ファイルの文字列を行に変換して uniq -c で数えて awk コマンドで3個存在する文字だけを表示した。

$ cat hoge | sed 's/./&\n/g' | sort | uniq -c | awk '$1==3{print $2}'
a
d

別解。文字列を行に変換するところは sed コマンドじゃなくて dango コマンドで変換した。

$ cat hoge | dango -c -n 1 | sort | uniq -c | awk '$1==3{print $2}'
a
d

Q5

Question

次のようなファイル、ディレクトリを作ってください。そして、file1, file2, file3をカレントディレクトリに移動してください。

$ mkdir -p a/b/c
$ touch a/file1 a/b/file2 a/b/c/file3
$ tree
.
└── a
 ├── b
 │   ├── c
 │   │   └── file3
 │   └── file2
 └── file1

3 directories, 3 files

Answer

find コマンドでファイルだけ探して xargs + mv でファイルをカレントディレクトリに移動した。

$ find ./* -type f | xargs -I{} mv {} ./
$ tree
.
├── a
│   └── b
│       └── c
├── file1
├── file2
└── file3

3 directories, 3 files

Q6

Question

次のようにファイルとディレクトリを作り、hogeと書いてあるファイルをディレクトリa、 それ以外のファイルをディレクトリbに振り分けてください。

$ echo hoge > file1
$ echo huge > file2
$ echo hoge > file3
$ echo hoge > file4
$ mkdir a b

Answer

while ループでそれぞれのファイルを処理して、ファイルが hoge を含んでるかは grep コマンドで判定。

$ ls file* | while read f; do grep hoge $f > /dev/null && mv $f a || mv $f b; done
$ tree
.
├── a
│   ├── file1
│   ├── file3
│   └── file4
└── b
    └── file2

2 directories, 4 files

Q7

Question

以下の9つのファイルについて、二つのファイルの組み合わせを全て列挙してください。ただし、重複してはいけません。

uedambp:~ ueda$ touch file{1..9}
uedambp:~ ueda$ ls file{1..9}
file1 file2 file3 file4 file5 file6 file7 file8 file9

Answer

2つのファイルの組み合わせを作るのがポイントかなって気がした。

少し考えたけど while ループでそれぞれのファイルとファイル群を echo $f file* したら2つのファイルの組み合わせを作るもとができるのに気がついたらあとは簡単……と思ったけど重複を弾くのはいろいろ試行錯誤することに。

$ ls | while read f; do echo $f file*; done | awk '{for(i=2;i<NF;i++){print $1,$i}}' | sort | awk '$1<$2'

Q8

シェル芸以前の問題なんだけどシェルで乱数を取得する方法が分からなかったのでググったら半分くらい答えが分かってしまった。 あと cat /dev/urandom をパイプで tr コマンドに接続したら tr: Illegal byte sequence のエラーになってどうにもできないみたいだったので、この問題だけ Virtual Box 上の Ubuntu 20.04.2 LTS で解いた。

追記

TwitterLANG=C tr 〜 したら tr コマンドのエラーが回避できると教えてもらって LANG=C tr 〜 はやっても状況は変わらなかったけど LC_CTYPE=C tr 〜 にしてみたらちゃんと動くようになった。感謝!

Question

0から999999までの数字の一様乱数を無限に出力し続けてください。

Answer

$ cat /dev/urandom | tr -dc '0-9' | fold -b 6 | sed 's/^0*//'

参考サイト

mysql コマンドで select した結果を csv にするやり方をいろいろメモ。

mysql コマンドで select した結果を csv にするやり方をいろいろメモしておく。

mysql コマンドで select した結果を csv にするやり方

mysql コマンドで select すると select の結果はデフォルトで表形式で出力するけど、表形式じゃなくて csv にしたいときとかのやり方をいろいろメモしておく。

この記事ではデータベースは MySQLサンプルのデータベース の world データベースを使う。

表形式で出力してテキストエディターで編集する (ダメ絶対)

mysql コマンドは select の結果はデフォルトでは表形式で出力するので出力結果をテキストエディターにコピペして CSV に編集していく。

すごく面倒くさい。

mysql> select * from city limit 5;
+----+----------------+-------------+---------------+------------+
| ID | Name           | CountryCode | District      | Population |
+----+----------------+-------------+---------------+------------+
|  1 | Kabul          | AFG         | Kabol         |    1780000 |
|  2 | Qandahar       | AFG         | Qandahar      |     237500 |
|  3 | Herat          | AFG         | Herat         |     186800 |
|  4 | Mazar-e-Sharif | AFG         | Balkh         |     127800 |
|  5 | Amsterdam      | NLD         | Noord-Holland |     731200 |
+----+----------------+-------------+---------------+------------+
5 rows in set (0.00 sec)

mysql コマンドはバッチモードで実行して出力を編集する

mysql コマンドは SQL をパイプで入力するとバッチモードでの動作になる。

mysql コマンドはバッチモードで動作するときは出力結果は表形式じゃなくてカラムをタブ区切りで出力するので、tr コマンドでタブをカンマに置換すると出力結果が CSV になる。

$ echo 'select * from city limit 5' | mysql -h$MYSQL_HOST -u$MYSQL_USER world | tr '\t' ','
ID,Name,CountryCode,District,Population
1,Kabul,AFG,Kabol,1780000
2,Qandahar,AFG,Qandahar,237500
3,Herat,AFG,Herat,186800
4,Mazar-e-Sharif,AFG,Balkh,127800
5,Amsterdam,NLD,Noord-Holland,731200

特定のカラムの出力を CSV にする: tr と sed を使うやり方

テーブルの特定の出力を CSV にするにはこんな感じでやる。

たとえば city テーブルの ID カラムを select するとこんな感じに出力するので、

$ echo 'select ID from city order by ID limit 5' | mysql -h$MYSQL_HOST -u$MYSQL_USER world
ID
1
2
3
4
5

tr コマンドで改行をカンマに置換する。 末尾に余計なカンマが付いてしまっているので、

$ echo 'select ID from city order by ID limit 5' | mysql -h$MYSQL_HOST -u$MYSQL_USER world | tr '\n' ','
ID,1,2,3,4,5,

sed コマンドで末尾の余計なカンマを除去する。

$ echo 'select ID from city order by ID limit 5' | mysql -h$MYSQL_HOST -u$MYSQL_USER world | tr '\n' ',' | sed '
s/,$//'
ID,1,2,3,4,5

特定のカラムの出力を CSV にする: dango を使うやり方

dango コマンドを使うと tr コマンドと sed コマンドを使ってたところが dango コマンド1個に置き換えられる。

自分がやるときはだいたいこれ。

$ echo 'select ID from city order by ID limit 5' | mysql -h$MYSQL_HOST -u$MYSQL_USER world | dango -d ','
ID,1,2,3,4,5

参考サイト

祝日かどうかを判定する shukujitsu コマンドを使ってみたら便利だったのでメモ。

祝日かどうかを判定する shukujitsu コマンドを使ってみたら便利だったのでメモしておく。

shukujitsu コマンドのバージョン。

$ shukujitsu -version
shukujitsu 0.0.4

shukujitsu コマンドの使い方

shukujitsu コマンドは指定した日付が祝日かどうかを判定して結果を表示する。

大雑把な使い方だけど、指定した日付が祝日なら日付のあとになんの祝日なのかを表示して、指定した日付が祝日じゃなかったら日付だけを表示する。

$ shukujitsu -date 2021-01-01
2021-01-01 元日

$ shukujitsu -date 2021-01-02
2021-01-02

使い方の例だけど2021年1月の祝日はこんな感じでリストできる。

$ seq 1 31 | awk '{printf("2021-01-%02d\n", $1)}' | xargs -I{} shukujitsu -date {} | awk 'NF==2'
2021-01-01 元日
2021-01-11 成人の日

2021年6月の祝日をリストしたら何も表示しなかった。切ない。

$ seq 1 30 | awk '{printf("2021-06-%02d\n", $1)}' | xargs -I{} shukujitsu -date {} | awk 'NF==2'

インストール

Go 1.16 を使ってたら go install 〜 でインストールする。 go get 〜 でインストールでもいい。

$  go install github.com/soh335/shukujitsu/cmd/shukujitsu@latest

Go 1.15 を使ってたら go get 〜 でインストールする。

$ go get github.com/soh335/shukujitsu/cmd/shukujitsu

単に実行バイナリーだけがあればいいなら https://github.com/soh335/shukujitsu/releases からダウンロードする。

使い方

引数を指定しないときはシステム日付が祝日かどうか判定する。

$ date "+%Y-%m-%d"
2021-03-14

$ shukujitsu
2021-03-14

-date オプションで祝日かどうか判定する日付を指定する。 -date オプションは日付を Go の 2006-01-02 のフォーマットつまり YYYY-MM-DD みたいに指定する。

$ shukujitsu -date 2021-01-01
2021-01-01 元日

$ shukujitsu -date 2021-01-02
2021-01-02

-quiet オプションを指定すると祝日かどうかの判定結果を表示しない。

-quiet オプションは判定結果を表示する必要がなくて shukujitsu コマンドの終了ステータスをチェックするときに使う感じ、というかたぶんこちらがよく使いそう。 shukujitsu コマンドの終了ステータスはこのあと記載していく。

$ shukujitsu -quiet
$ shukujitsu -date 2021-01-01 -quiet
$ shukujitsu -date 2021-01-02 -quiet

shukujitsu コマンドの終了ステータス

終了ステータス 意味
0 祝日
1 祝日じゃない
2 日付のパースエラー

cron と shukujitsu コマンドを組み合わせて使う

cron で 0 0 * * MON-FRI shukujitsu || echo '祝日じゃない' みたいにやると祝日じゃない平日だけ特定の処理を実行したりできて便利。

特定の日付が祝日かどうか判定する仕組み

shukujitsu コマンドは内閣府祝日の csv ファイル を公開しているのを利用している。

shukujitsu コマンドは前述の csv ファイルから祝日の定義を取り出してプログラムの中に取り込んでいる。

現時点での最新バージョンの 0.0.4 は 1955/1/1 元旦から 2022/11/23 勤労感謝までの祝日に対応しているよう。

ライブラリーとしても使える

shukujitsu コマンドはライブラリーとしても使えるというかもともとは Go で任意の日付が祝日か判定するためのライブラリーだったりする。

ライブラリーとして使うときはこんな感じで任意の日付が祝日かどうか判定できる。

if shukujitsu.IsShukujitsu(time.Now()) {
    fmt.Println("shukujitsu!")
}

参考サイト

半角カナと全角英数字をコマンド一発で焼き払うメモ。

半角カナと全角英数字をコマンド一発で焼き払うメモ。

半角カナと全角英数字は nkf コマンドの -Z オプションを使うと一発で焼き払える。

$ echo '半角カナUZEEEEE' | nkf -Z
半角カナUZEEEEE

nkf コマンドのバージョン。

$ nkf --version
Network Kanji Filter Version 2.1.5 (2018-12-15)
Copyright (C) 1987, FUJITSU LTD. (I.Ichikawa).
Copyright (C) 1996-2018, The nkf Project.

nkf コマンドは Mac なら brewbrew install nkf コマンドでインストールできる。 Ubuntu なら apt で apt install nkf でインストールできる。

Mac の sed とか tr が illegal byte sequence エラーになるときの回避方法のメモ。

Macsed とか tr が illegal byte sequence エラーになるときの回避方法のメモ。

sed とか tr が illegal byte sequence エラーになる

どんなことがおきるか

sed とか tr が illegal byte sequence エラーになってしまうことがある。

たとえば ascii 文字のうち英字と数字だけを表示するのをやってみるとこんな感じにエラーになる。 tr のほうはエラーメッセージを表示と一応は英数字を出力してるけど sed のほうは完全に沈黙してる感じ。

$ awk 'BEGIN{for(i=0;i<256;i++){printf("%c",i)}}' | tr -dc "[:alnum:]"
tr: Illegal byte sequence
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

$ awk 'BEGIN{for(i=0;i<256;i++){printf("%c",i)}}' | sed -E 's/[^a-zA-Z0-9]//g'

sed: RE error: illegal byte sequence

どうしたら回避できるか

こういうときは LC_CTYPE=C を指定して sed とか tr を実行すると illegal byte sequence エラーは回避できる。

$ awk 'BEGIN{for(i=0;i<256;i++){printf("%c",i)}}' | LC_CTYPE=C tr -dc "[:alnum:]"
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

$ awk 'BEGIN{for(i=0;i<256;i++){printf("%c",i)}}' | LC_CTYPE=C sed -E 's/[^a-zA-Z0-9]//g'

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

ただ LC_ALL が指定してあると LC_ALLLC_CTYPE より優先されるので LC_CTYPE=C を指定しても illegal byte sequence エラーになっちゃう。

$ locale | grep LC_ALL
LC_ALL="ja_JP.UTF-8"

$ awk 'BEGIN{for(i=0;i<256;i++){printf("%c",i)}}' | LC_CTYPE=C tr -dc "[:alnum:]"
tr: Illegal byte sequence
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

$ awk 'BEGIN{for(i=0;i<256;i++){printf("%c",i)}}' | LC_CTYPE=C sed -E 's/[^a-zA-Z0-9]//g'

sed: RE error: illegal byte sequence

LC_ALL が指定してあるときは LC_ALL=C を指定して sed とか tr を実行すると illegal byte sequence エラーを回避できる。

$ awk 'BEGIN{for(i=0;i<256;i++){printf("%c",i)}}' | LC_ALL=C tr -dc "[:print:]"
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

$ awk 'BEGIN{for(i=0;i<256;i++){printf("%c",i)}}' | LC_ALL=C sed -E 's/[^a-zA-Z0-9]//g'

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

Bash でタブとかの制御文字を入力するやり方のメモ。

Bash でタブとかの制御文字を入力するやり方をたまに聞かれるのでメモしておく。

Bash のバージョン。

$ bash --version
GNU bash, version 5.1.4(1)-release (x86_64-apple-darwin19.6.0)
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Bash でタブとかの制御文字を入力する

Bash でタブとかの制御文字を入力することはそんなに多くはないけどときどきはあると思うのでやり方をメモしておく。

Ctrl-v + 制御文字

Bash とかのシェルは Ctrl-v を入力してから目的の制御文字をタイプすると制御文字を入力できる。

たとえば a b みたいに ab の間にタブを入れるなら echo コマンドの引数は ', a, Ctrl-v, Tab, b, ' の順番にキーをタイプする。 タブじゃなくて他の制御文字でも同じ。

$ echo 'a    b'
a   b

$'\文字'

Bash$'\文字' をバックスラッシュ (\) でエスケープしている文字に置換する。

たとえば a b みたいに ab の間にタブを入れるなら echo コマンドの引数は $'a\tb' を文字どおりに入力する。

$'a\tb'
a   b

cron の実行日時を確認しながらスケジュールを編集できる Crontab.guru がすごく便利。

cron の実行日時を確認しながらスケジュールを編集できる Crontab.guru がすごく便利なのでメモしておく。

Crontab.guru

cron をスケジューリングするときに cron 構文を忘れててそのたびに調べてることがちょいちょいあるのだけど、cron の実行日時を確認しながらスケジュールを編集できる Crontab.guru がすごく便利なのでメモしておく。

f:id:ebc_2in2crc:20210223162127p:plain

Crontab.guru は文字どおり crontab のグルつまり達人、第一人者とか教師になってくれる Web サービス。

サービスページを表示すると ↓ みたいに書いてあるとおり cron の実行日時を確認しながら素早く記述できる。

The quick and simple editor for cron schedule expressions by Cronitor

基本的な使い方

f:id:ebc_2in2crc:20210223162149p:plain

スクショは 5 4 * * * を入力したときのもの。

cron のスケジュールを入力すると次に実行する日時を表示する。 next を押すとさらにそのあとの実行日時も表示してくれる。

フィールドに指定できる値の範囲と演算子

入力中のフィールドに指定できる値の範囲と演算子を表示してくれる。cron 構文はフィールドによって 0 始まりだったり 1 始まりするけど忘れてしまっても大丈夫。

f:id:ebc_2in2crc:20210223162202p:plain

スクショは時間を入力中のもの。

f:id:ebc_2in2crc:20210223162223p:plain

スクショは月を入力中のもの。

月は 1-12 のような数値だけじゃなくて JAN-DEC のような月の短縮表記を指定できるのが分かる。

cron の実行日時を確認しながら編集できるのがすごく便利

f:id:ebc_2in2crc:20210223162235p:plain

1-59/30 1,2 * * * みたいなのを入力するとちゃんと実行する時間を表示する。

f:id:ebc_2in2crc:20210223164647p:plain

時間は 0-23 の範囲での指定で 24 は指定できないのだけど、入力の指定が間違ってると間違ってるフィールドのとこが赤くなって実行日時が表示しなくなる。間違いにすぐ気がつけるのがすごく便利。