シェル力が圧倒的に足りてないのでシェル力を養うためにシェル芸勉強会の問題に挑戦してみた。
シェル力が圧倒的に足りてないのでシェル力を養うためにシェル芸勉強会の問題に挑戦してみた。
感想から先に書いてしまうとパズル感覚で楽しいしシェル力を養うのにも役立ちそう。
自分が使ってる zsh のバージョン。
$ zsh --version zsh 5.8 (x86_64-apple-darwin19.6.0)
はじめに
自分はシェル力が圧倒的に足りてないので勉強のためにシェル芸勉強会の問題に挑戦してみた。
一応自分がシェルを使う用途を書いておくと普段の仕事は Mac を使ってやっててちょっとしたことをやるのにちょろっとシェルを使う程度。
ちなみにシェル芸ってなに? ってところは Wikipedia の USP 友の会 から引用しておく。
シェル芸とは、主に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 で解いた。
追記
Twitter で LANG=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!") }
参考サイト
半角カナと全角英数字をコマンド一発で焼き払うメモ。
半角カナと全角英数字をコマンド一発で焼き払うメモ。
カタカナは半角だと忌み嫌われるのに数字は半角にしろ全角の数字は滅べと言われるのはなぜなのか。
— 🥔えび🦐➕️🍞➕️🔥➡️🍤 (@ebc_2in2crc) March 7, 2021
半角カナと全角英数字は 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 なら brew で brew install nkf
コマンドでインストールできる。
Ubuntu なら apt で apt install nkf
でインストールできる。
Mac の sed とか tr が illegal byte sequence エラーになるときの回避方法のメモ。
Mac の sed とか 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_ALL
は LC_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
みたいに a
と b
の間にタブを入れるなら echo コマンドの引数は '
, a
, Ctrl-v
, Tab
, b
, '
の順番にキーをタイプする。
タブじゃなくて他の制御文字でも同じ。
$ echo 'a b' a b
$'\文字'
Bash は $'\文字'
をバックスラッシュ (\
) でエスケープしている文字に置換する。
たとえば a b
みたいに a
と b
の間にタブを入れるなら echo コマンドの引数は $'a\tb'
を文字どおりに入力する。
$'a\tb' a b
cron の実行日時を確認しながらスケジュールを編集できる Crontab.guru がすごく便利。
cron の実行日時を確認しながらスケジュールを編集できる Crontab.guru がすごく便利なのでメモしておく。
Crontab.guru
cron をスケジューリングするときに cron 構文を忘れててそのたびに調べてることがちょいちょいあるのだけど、cron の実行日時を確認しながらスケジュールを編集できる Crontab.guru がすごく便利なのでメモしておく。
Crontab.guru は文字どおり crontab のグルつまり達人、第一人者とか教師になってくれる Web サービス。
サービスページを表示すると ↓ みたいに書いてあるとおり cron の実行日時を確認しながら素早く記述できる。
The quick and simple editor for cron schedule expressions by Cronitor
基本的な使い方
スクショは 5 4 * * *
を入力したときのもの。
cron のスケジュールを入力すると次に実行する日時を表示する。 next
を押すとさらにそのあとの実行日時も表示してくれる。
フィールドに指定できる値の範囲と演算子
入力中のフィールドに指定できる値の範囲と演算子を表示してくれる。cron 構文はフィールドによって 0 始まりだったり 1 始まりするけど忘れてしまっても大丈夫。
スクショは時間を入力中のもの。
スクショは月を入力中のもの。
月は 1-12
のような数値だけじゃなくて JAN-DEC
のような月の短縮表記を指定できるのが分かる。
cron の実行日時を確認しながら編集できるのがすごく便利
1-59/30 1,2 * * *
みたいなのを入力するとちゃんと実行する時間を表示する。
時間は 0-23
の範囲での指定で 24
は指定できないのだけど、入力の指定が間違ってると間違ってるフィールドのとこが赤くなって実行日時が表示しなくなる。間違いにすぐ気がつけるのがすごく便利。