Closed22

シェルワンライナーの軌跡

ぱんだぱんだ
  • bc 計算するコマンド

sed

echo '小問1'
echo 'クロロメチルメチルエーテル' | sed 's/メ/エ/'
echo '小問2'
echo 'クロロエチルエチルエーテル' | sed 's/エチルエ/エチルメ/'
echo '小問3'
echo 'クロロメチルメチルエーテル' | sed 's/メチル/エチル/g'
echo '小問4'
echo 'クロロエチルエーテル' | sed 's/エチル/&&/'
echo '小問5'
echo 'クロロメチルエチルエーテル' | sed -E 's/(メチル)(エチル)/\2\1/'
  • sedは置換コマンド
  • 基本は一致した一箇所を置換
  • -gオプションで一致箇所全て置換
  • &は検索対処の文字を再利用できる
  • -E拡張正規表現を利用

grep

  • 検索コマンド
  • -oオプションは正規表現にマッチした部分を切り出す
  • -A 数値 正規表現にマッチした後何行かを表示する

awk

  • awkはプログラミング言語でいろんな亜種がある
seq 5 | awk '/[24]/'
seq 5 | awk '$1%2==0'
seq 5 | awk '$1%2==0{printf("%s 偶数\n", $1)}'
seq 5 | awk '$1%2==0{print $1, "偶数"}$1%2{print $1, "奇数"}'
seq 5 | awk 'BEGIN{a=0}$1%2==0{print $1, "偶数"}$1%2{print $1, "奇数"}{a+=$1}END{print "合計", a}'
  • 正規表現にマッチするものを抽出できる
  • $1のようにして入力に入ってきた値を使える
  • 条件文を書くことでマッチしたものに絞れる
  • printfもしくはprintで条件にマッチしたものを出力できる
  • BEGINとENDで最初の行を処理する前と最後の行を処理した後の処理を書ける
  • awkでは変数を宣言することなく使えるので上記の例ではBEGIN句は省略可
  • {a+=1}の部分はルールがないので全行で処理される
ぱんだぱんだ
  • sort + uniqによる集計
  • xargsによる1行並べ
  • xargsの-nオプションで処理する引数の個数指定
  • xargsの-Iオプションで引数にラベルをつける
  • sedの-nオプションと/正規表現/正規表現/pを使用するとマッチする行のみ出力する
  • 画像拡張子変換に使える外部コマンドconvert
  • xargsの-Pオプションで並列実行
  • grepのRオプションで再起読み込み
  • grepのlオプションはファイル名のみ欲しい時
  • ${変数名^^}で変数の値を全て大文字(bashのバージョンが古いと使えない)
  • ${変数名^}で変数の先頭文字だけ大文字(bashのバージョンが古いと使えない)
  • sort -uでユニークにソートする
  • wc -l で行数カウント
  • grep -f - で標準入力から正規表現をよみこむ
  • grep -x 行全体がマッチしないとマッチしたことにしない
% paste <( echo aaaa | grep -o .) <( echo bbbb | grep . -o)
a	b
a	b
a	b
a	b
  • pasteコマンドで出力を並べられる
  • grep -o .はマッチした部分を改行して並べるので横文字を縦文字に表示できる
paste <(cat "$kaibun" | grep -o .) <(cat "$kaibun" | grep -o . | tac) | awk '$1!=$2{a++}END{print (a > 0 ? "回文じゃないよ" : "回文だよ") }'
  • tacは縦出力を逆に、revは横の出力を逆に
ぱんだぱんだ

ruby ワンライナー

  • rubyをワンライナーで実行する
ruby -e '<何かしらの処理>'
  • 置換 ruby -pe 'gsub(<正規表現>) { $1 + $2.upcase }'
  • -pオプションはsedのように行で処理する
  • -nオプションは-pオプションと同様行を入力として処理する
  • -lオプションはputsなどに改行を挿入する
  • -0オプションはセパレーターに改行ではなくnull文字を使うようにする。sedのzオプションみたいなやつ。
  • -rオプションはgemを使用したいときに指定
  • 入力で入ってきた行は$_で扱える
  • rubyの正規表現はonigumoという正規表現ライブラリが使われており\p{Han}とすることで漢字とマッチさせることができる。
  • gsubでパターンマッチを使うときはマッチした箇所の使用にはエスケープする
gsub(/\n([、。])/, "\\1\n")

rbコマンド

rbというコマンドが便利そうだったのでインストールした。

https://yhara.jp/2018/12/21/rb-command

パスの通った場所に数行のrubyプログラムを置くだけ。

sudo sh -c 'cat > /usr/local/bin/rb <<"EOF"
#!/usr/bin/env ruby
File.join(Dir.home, '.rbrc').tap { |f| load f if File.exists?(f) }

def execute(_, code)
  puts _.instance_eval(&code)
rescue Errno::EPIPE
  exit
end

single_line = ARGV.delete('-l')
code = eval("Proc.new { #{ARGV.join(' ')} }")
single_line ? STDIN.each { |l| execute(l.chomp, code) } : execute(STDIN.each_line, code)
EOF'
  • ホームディレクトリに.rbrcという設定ファイル?があれば読み込む
  • 指定できるオプションはlのみ
  • -lオプションがあれば行で処理する
  • 標準入力の値(行)に対してrubyの関数を実行する
  • -lオプションがある場合は各行の値は文字列として、オプションがなければEnumeratorオブジェクトとして扱われる
// "Hello".lengthが実行されているn
% echo Hello | rb -l 'length'
5
// ["Hello"].firstが実行されている
% echo Hello | rb 'first'    
Hello
ぱんだぱんだ

#34

やまだ 山田
がんばる 頑張る
ばくはつする 爆発する
はげしい 激しい

これを漢字(ふりがな)のようにワンライナーで置換する。

ruby -lne 'a=$_.match(/^(.*)(.*) (\p{Han}*)(\2)$/);puts "#{a[3]}(#{a[1]})#{a[2]}"' ../shellgei160/qdata/34/furigana.txt

#35

カタカナ5文字になったらアウト。その直前までの文字と文字数を出力。
.*とすると最長一致になるので.*?として最短一致。

% cat ../shellgei160/qdata/35/speech.txt | ruby -lne 'a=$_.match(/(^.*?\p{Katakana}{4})(?=[ア-ン])/);puts "#{a[1].length} #{a[1]}"'
25 21世紀に入ってからのIT業界を中心としたパラダイ
4 ジャスト
45 個人間であらゆるアセットをシェアするビジネスが注目を浴びており、共有経済、いわゆるシェアリ
22 顧客体験の高品質化、満足度、いわゆるサティス

#36

かっこが対応しているやつの文字だけ抽出する
とりあえず見やすいようにかっこの終わりで改行
sedの拡張がMacで使えなかったのでrubyのif文に正規表現でマッチするかを見てる
\g<1>が正規表現の名前付き逆参照で、再帰的にチェックしてる(雰囲気で理解)

cat ../shellgei160/qdata/36/message.txt | sed 's/)(/)\n(/g' | ruby -ne 'puts $_ if /^(\(\g<1>\)|[^()]+)$/' | tr -d '()' | xargs | tr -d ' '

# 37

連続した文字を抽出
rubyでやろうとしたけどうまくできなかたので諦めてgnugrepをインストール

私は私は今、オーストラリアに
いるのですが、特に観光もせず、
部屋でシェル芸シェル芸の問題
問題を考えていますす。人それ
ぞれ、人生いろいろですよね。
cat ../shellgei160/qdata/37/diarydiary.txt | tr -d '\n' | ggrep -oE '(.+)\1'

# 38

普通にsedで置換

$ x='() { :;}; echo vulnerable' bash -c &amp;amp;amp;amp;amp;amp;quot;echo this is a test&amp;amp;amp;amp;amp;amp;quot; 
vulnerable
this is a test
% cat ../shellgei160/qdata/38/this_is_it.txt | sed -E 's/\&(amp;)+quot;/"/g'
$ x='() { :;}; echo vulnerable' bash -c "echo this is a test" 
vulnerable
this is a test

# 39

30文字以上になったら折り返す
awkで頑張る

cat ../shellgei160/qdata/39/bash_description.txt | sed 's/ / \n/g;s/$/ /'| awk '{L+=length};L>31{print "";L=length}{printf $0}' | awk 'sub(/ $/,"")'

# 40

gemでzen_to_iを使う
gsubで改行+句読点を句読点+改行に置換する

cat ../shellgei160/qdata/40/kanjinum.txt | ruby -rzen_to_i -0ne 'print $_.zen_to_i.gsub(/\n([、。])/, "\\1\n")'  
私が小学1年生の時は、
47都道府県の位置
と名前を全て覚えるくらいに
物覚えは良かったですが、
テストで100点満点を
取り、親から5000000000000000円を
プレゼントされることは
ありませんでした。
 
ぱんだぱんだ

#41

markdownの注釈のペアが存在しないものを一覧表示
とりあえず拡張grepでタグを抽出
目印と注釈文がわかりやすいようにsedで加工
あとはsortとawkしたあとにuniqでペアが存在しないやつを一覧表示
uniqの-f1で一列目を無視、-uオプションが最初から一つしかない行を出力

ggrep -oE '\[\^.*\]:?' | sed 's/\]$/\] 目印/' | sed 's/\]:$/\] 注釈/' | sort | awk '{print $2,$1}' | uniq -f1 -u

# 42

markdownの連番をちゃんと連番にする問題
awkで連番正規表現にマッチしたらカウントしていたカウンタで置き換えてprint
#が出てきたらカウンタリセット
最後に連番以外の部分を!/正規表現/で表示

awk '/^[0-9]\./{a++;$1=a".";print}/^#/{a=0}!/^[0-9]\./'

# 43

[4] トップページ | gihyo.jp, 技術評論社
https://gihyo.jp/

[3] シェル芸 | 上田ブログ
https://b.ueda.tech/?page=01434

[2] くんすとの備忘録
https://kunst1080.hatenablog.com/

[1] 日々之迷歩
https://papiro.hatenablog.jp/

[5] 俺的備忘録
https://orebibou.com/

これを番号順にソートする
3行ずつ記載されているのでとりあえず全行に目印つける
でソートする。ソートをそのまますると微調整がめんどくさくなるのでsオプションで安定ソート。kオプションで1列目を指定してソート
最後にsedで目印除去

awk 'NR%3==1{k=$1};{print k, $0}' | sort -s -k1,1 | sed 's/....//'

# 44

シェルスクリプトが含まれている行の行末に@を挿入する問題
一旦シェルスクリプトの前後に目印で%をつける
sedの拡張がMacで使えなかったのでrubyのgsubで置換
sedは%がある行の行末に@をつけて、最後に%決しておわり

ruby -0lne 'puts $_.gsub(/(シ\n?ェ\n?ル\n?ス\n?ク\n?リ\n?プ\n?ト)/, "%\\1%")' | sed '/%/s/$/ @/;s/%//g' 

# 45

uniqの-Dが使えなくてguniqとしてインストールした
最初のawkで空行が消えて、null文字と行番号を付与
ソートして一回しかでない行は取り除く
最後のawkが想定通りにならないのはMacの標準awkだと挙動が違うのかもしれないけどけっこうはまったので飛ばした

awk 'NF{print NR, "\0"$0}' | sort -k2,2 | guniq -f 1 -D | sort -k1,1 -g | awk -F '\0' 'n+1!=$1{print t,"\0",ns;t=ns=""}{n=$1;t=t$2;ns=ns n}' 
ぱんだぱんだ

# 46

漢字に権利(けんり)のようにルビをつける。
mecabのEオプションは最後のEOFの出力を省く。
awkで漢字とふりがなを抽出、nkfでカタカナをひらがなに
rubyのgsubで置換してsedで余計なごみを削除

mecab -E '' | awk -F'[\t,]' '{print $1","$(NF-1)}' | nkf -h | ruby -lne 'puts $_.gsub(/(.*),(.*)\1/, "(\\2)\\1")' | sed 's/(\**)//' | xargs | tr -d ' '

# 47

前月比の列を追加する

awk -F',' '{printf $0","} NR > 1{rate=$2/last*100-100"%"; if(rate > 0)printf "+"; print rate}NR==1{print "*"} {last=$2}'
2017/01,108192,*
2017/02,134747,+24.5443%
2017/03,120420,-10.6325%
2017/04,147368,+22.3783%
2017/05,262456,+78.0957%
2017/06,280741,+6.96688%
2017/07,315083,+12.2326%
2017/08,522489,+65.8258%
2017/09,489003,-6.40894%
2017/10,729017,+49.0823%
2017/11,987173,+35.4115%
2017/12,1025320,+3.86427%

# 48

プロセス一覧からCPU使用率を集計して1番使っているユーザーを表示

ps aux | awk 'NR>1{p[$1]+=$3;n[$1]++}END{for(i in p) print p[i], n[i], i}' | sort -nrk 1,2
23.9 232 yamanakajunichi
10.5 2 _windowserver
0.9 86 root
0.8 4 _driverkit
0.4 2 _coreaudiod

# 49

topコマンドのログからCPU使用率が最も高いコマンドを集計して抽出する

ggrep -E '^ *[0-9]+ ' | awk '{x=$9;for(i=1;i<12;i++)$i="";a[$0]+=x}END{for(k in a)print a[k], k}' | sort -k1,1nr | head -n 5

# 50

2つのファイルを連結させて商品の売上金を計算する
joinで連結するときのkeyは2列では無理なので1列に加工して個数を集計
joinしていい感じにする
joinは引数に2つのファイルを受け付けるが、<()でプロセス置換を使いsedの結果をファイル入力としている
2つ目の引数には-を使うことで、前段のコマンド結果、つまり標準入力を受け付ける
columnコマンドは整形

cat ../shellgei160/qdata/50/sales | awk '{a[$3$4]+=$5}END{for(k in a)print k, a[k]}' | sort | join <(sed 's/ //' ../shellgei160/qdata/50/stones_master) - | awk '{print $2, $3*$4}' | column -t
ぱんだぱんだ

#51

ファイルの結合
awkのsprintf 0埋めできる
joinの-a 1は結合のキーがマッチしなかったときに一つ目のファイルをそのまま結合する

cat ../shellgei160/qdata/51/scores.txt | awk '{$1=sprintf("%03\d", $1);print}' | sort | join -a 1 ../shellgei160/qdata/51/students.txt - | awk 'NF==2{print $0, 0}NF==3'

# 52

% cat ../shellgei160/qdata/52/data_U 
* A B
X 4 2
Y 3 1
% cat ../shellgei160/qdata/52/data_V
* A B C
X 7 6 -1
Y 9 8 -2

これら二つのクロス集計表を結合する。

awk 'FNR==1{$1="";h=$0}FNR!=1{print FILENAME, $0, h, NF-1}' ../shellgei160/qdata/52/data_U ../shellgei160/qdata/52/data_V | awk '{for(i=NF-$NF;i<NF;i++)print $1,$2,$i,$(i-$NF)}' | sed 's/.*data_//'
U X A 4
U X B 2
U Y A 3
U Y B 1
V X A 7
V X B 6
V X C -1
V Y A 9
V Y B 8
V Y C -2

# 53

joinの-a 1 -a 2はSQLのフルジョインのようなもの
joinの-oで出力フォーマット指定
joinの-eで値がなかったときに代わりに出力する値を指定

% awk '{print $2, $1}' ../shellgei160/qdata/53/devicelist.txt | sort | join -a 1 -a 2 -o '1.2 0 2.2' -e @ - <(sort ../shellgei160/qdata/53/measurement.txt) | sort
01 xxxx.0c4d.1c45 1914
02 xxxx.0d46.f3c2 @
03 xxxx.0d17.73a6 2275
04 xxxx.0d81.33b8 @
05 xxxx.0d17.9658 @
06 xxxx.0c4d.095c 3235
07 xxxx.0a69.b711 3119
08 xxxx.0d81.1da2 @
09 xxxx.0fff.d828 3618
10 xxxx.0d17.7478 3443
@ xxxx.0d81.33a8 1607
@ xxxx.17d0.2c07 3431

# 54

% cat ../shellgei160/qdata/54/fruits.json | jq
{
  "Fruits": [
    {
      "Name": "Apple",
      "Quantity": 3,
      "Price": 100
    },
    {
      "Name": "Orange",
      "Quantity": 15,
      "Price": 110
    },
    {
      "Name": "Mango",
      "Quantity": 100,
      "Price": 90
    },
    {
      "Name": "Banana",
      "Quantity": 6,
      "Price": 100
    },
    {
      "Name": "Kiwifruit",
      "Quantity": 40,
      "Price": 50
    }
  ]
}

上記jsonをFruitsの各要素ごとにjsonファイルで切り出す
jq使いたくなるが意外と難しいのでrubyでやる

cat ../shellgei160/qdata/54/fruits.json | ruby -r json -e 'JSON.load(STDIN)["Fruits"].each{|fruit| File.open(fruit["Name"]+".json","w"){|file| file.write(JSON.pretty_generate(fruit))}}'

# 55

$ cat qdata/55/watch_log.json | head -n 1 | jq
{
  "timestamp": "2020-01-18 18:06:52",
  "output": "total 9888204\ndrwxr-xr-x  19 uesugi staff        608  1 12 23:22 .\ndrwxr-xr-x+ 86 uesugi staff       2752  1 18 18:06 ..\n-rw-r--r--   1 uesugi staff      10244  1  7 00:57 .DS_Store\n-rw-r--r--   1 uesugi staff  417254625  9 22 16:09 201812.tar.gz\n-rw-r--r--   1 uesugi staff   54958837  9 22 16:10 201901.tar.gz\n-rw-r--r--   1 uesugi staff  532549401  9 22 16:10 201902.tar.gz\n-rw-r--r--   1 uesugi staff  399341189  9 22 16:11 201903.tar.gz\n-rw-r--r--   1 uesugi staff  180073487  9 22 16:11 201904.tar.gz\n-rw-r--r--   1 uesugi staff  599281846  9 22 16:11 201905.tar.gz\n-rw-r--r--   1 uesugi staff   75347322  9 22 16:11 201906.tar.gz\n-rw-r--r--   1 uesugi staff  271311958 11  3 19:47 201907.tar.gz\n-rw-r--r--   1 uesugi staff  263584337 11  3 19:48 201908.tar.gz\n-rw-r--r--   1 uesugi staff 4961687746 12 12 21:39 201909.tar.gz\n-rw-r--r--   1 uesugi staff 2326560921 12 25 07:46 201910.tar.gz\ndrwxr-xr-x  19 uesugi staff        608 12 21 11:38 201911\ndrwxr-xr-x  22 uesugi staff        704 12 31 00:00 201912\ndrwxr-xr-x  21 uesugi staff        672  1 18 00:00 202001\nlrwxr-xr-x   1 uesugi staff         31  1 12 23:22 Project -> /Users/uesugi/Dropbox/Project\n-rw-r--r--   1 uesugi staff      20119  1 18 17:04 memo.txt\n"
}

上記のようなJSONのoutputの差分を表示する
sedの2,$pで2行目から最後までまで2回ずつ表示
sedの$dで最後の行を削除
pasteの-d ,でデリミタにカンマを指定してるので2行をカンマ区切りで一行で表示
次のsedで[]でくくる
whileループで変数lに行データを格納
jqで2つのデータのtimestampを並べる
diffにはプロセス置換で二つのデータのJSONのoutput部分を入力
むず

cat qdata/55/watch_log.json | sed '2,$p;$d' | paste -d , - - | sed 's/.*/[&]/' | while read -r l;do jq -r '"diff: \"\(.[0].timestamp)\" \"\(.[1].timestamp)\""' <<<$l; diff <(jq -r '.[0].output' <<<$l) <(jq -r '.[1].output' <<<$l);done
ぱんだぱんだ

#56

アプリA: アクセス分析API, 交通情報API, 人事情報API
アプリB: 受注API, 交通情報API, 顧客情報API
アプリC: 受注API, メールAPI, 住所情報API
アプリD: 人事情報API, メールAPI
アプリE: 受注API, 交通情報API, 顧客情報API
住所情報API: 月, 水, 金
顧客情報API: 水, 日
交通情報API: 土
受注API: 火
アクセス分析API: 木
メールAPI: 金, 土
人事情報API: 月

上記ファイルをjoinする
最初のsedでsedの命令文を作る。区切り文字は/ではなく;を使用してる
次のsed -f - で標準入力のsedコマンドをファイルに対して実行
あとはごにょごにょ
曜日のソートは一旦数字に置換してソートしたら元に戻す。なるほどなるほど
ちなみにsedのyは単純な文字列変換に使う。sは正規表現

$ cat qdata/56/service_stop_weekday.txt | sed -E 's;(.*):(.*);s/\1/\2/g;g' | sed -f - qdata/56/service_depend_list.txt | tr -d :, | awk '{for(i=2;i<=NF;i++)print $i,$1}' | sort -u | awk '{a[$1]=a[$1]" "$2}END{for(k in a)print k":"a[k]}' | sed 'y/月火水木金土日/1234567/' | sort | sed 'y/1234567/月火水木金土日/' | sed 's/ /, /g;s/,//'
月: アプリA, アプリC, アプリD
火: アプリB, アプリC, アプリE
水: アプリB, アプリC, アプリE
木: アプリA
金: アプリC, アプリD
土: アプリA, アプリB, アプリC, アプリD, アプリE
日: アプリB, アプリE

# 57

markdownのテーブル表示の縦棒をそろえる

% cat ../shellgei160/qdata/57/table.md | sed 's/|/ & /g' | column -t | sed 's/|  /|/g;s/  |/|/g'
|AAA  |BBB|CCC|
|---  |---|---|
|1    |123|4  |
|10000|1  |64 |
|3    |3  |3  |

# 58

桁区切りにカンマを使ってるcsvファイルの数値の和を求める

% cat ../shellgei160/qdata/58/num.csv | tr , ' ' | xargs -n 1 | sed 's/ //g' | xargs | tr ' ' + | bc

rubyでやってみた

% cat ../shellgei160/qdata/58/num.csv | tr , ' ' | xargs -n 1 | sed 's/ //g' | ruby -rbigdecimal -lne 'puts ARGF.map{|n| BigDecimal(n)}.sum.to_s("F")'                        
1235361.19999999999999999999999

# 59

csvの行数が違う行を出力
rubyのCSVライブラリを使用

% cat ../shellgei160/qdata/59/data.csv | ruby -rcsv -e 'CSV.parse(STDIN) {|row| p row.length}' | awk '$1==2{print NR}'

# 60

2017-2-24からその年末までのプレミアムフライデーを抽出
例のごとくgdateを使う(-dオプションを使うため)

% seq 0 365 | xargs -I@ gdate '+%F %a' -d '2017-2-24 @day' | grep '^2017' | grep '金$' | tac | guniq -w7 | tac
2017-02-24 金
2017-03-31 金
2017-04-28 金
2017-05-26 金
2017-06-30 金
2017-07-28 金
2017-08-25 金
2017-09-29 金
2017-10-27 金
2017-11-24 金
2017-12-29 金
ぱんだぱんだ

# 61

Mac標準のfindコマンドだと-daystartオプションはない
現在日時の先週に更新されたファイル一覧

find . -daystart -mtime -$(( 8 + $(gdate '+%w') )) -mtime +$(gdate '+%w') -type f

# 62

祝日リストを使って2019-01-01から2021-12-31のリストを作成する

nkfでとりあえず読めるように表示(-wLux でとりあえずutf8で)
tailで2行目以降、つまり先頭行とばす
teipは指定の列にのみコマンドを適用する。-d,で区切り文字にカンマを指定し、-f 1で1列目にコマンドを適用。
awkで絞ってcatで日付のシーケンスと結合、あとはごにょごにょ

nkf -wLux | tail -n +2 | teip -d, -f 1 -- gdate -f- '+%Y-%m-%d' | awk -F- '$1>=2019&&$1<2022' | cat - <(dseq 2019-01-01 2021-12-31 | sed 's/$/,@/') | sort -r | guniq -w10 | tac

# 63

5週ある月を抽出

% dseq 2021-01-01 2021-12-31 | gdate -f- | awk '$1=="日"{a[$2]++}END{for(k in a)print k, a[k]}' | awk '$2==5' | sort -n
1 5
5 5
8 5
10 5

# 64

第三火曜日を抽出

% dseq 2021-01-01 2021-12-31 | gdate -f- '+%m %d %a' | grep '火$' | awk '{a[$1]=a[$1]","$0}END{for(k in a)print a[k]}' | sed 's/^,//' | awk -F, '{print $3}' | sort -n
01 19 火
02 16 火
03 16 火
04 20 火
05 18 火
06 15 火
07 20 火
08 17 火
09 21 火
10 19 火
11 16 火
12 21 火

# 65

62で使った祝日表を使って2019年の休日の数を数える
ひたすら置換

%  cat ../shellgei160/qdata/62/syukujitsu.csv | nkf -wLux | tail -n +2 | teip -d, -f 1 -- gdate -f- '+%Y-%m-%d %a' | awk -F- '$1==2019' | cat - <(dseq -f '%Y-%m-%d %a' 2019-01-01 2019-12-31 |sed -e 's/Mon/月/' -e 's/Tue/火/' -e 's/Wed/水/' -e 's/Thu/木/' -e 's/Fri/金/' -e 's/Sat/土/' -e 's/Sun/日/' | sed 's/$/,@/') | sort -r | guniq -w10 | sed 's/,/ /' | awk '$2=="土"||$2=="日"||$3!="@"{print $0}' | awk -F'-' '{a[$2]++}END{for(k in a)print k"月", a[k]}' | sort
01月 10
02月 9
03月 11
04月 10
05月 12
06月 10
07月 9
08月 10
09月 11
10月 10
11月 10
12月 9
ぱんだぱんだ

# 66

指定の日時のMTGが何回もリスケになりました

nextwd

標準入力で受け付けた日時の翌週の指定の曜日の日付を出力するコマンドを作成する。

#!/bin/sh

nextwd() {
  read d

  date -d '' > /dev/null 2>&1
  if [ "$?" -eq 0 ];then
    date -d "$d $((7 - $(date -d $d +%w) + $(date -d $1 +%w))) day" +%F
    exit 0
  fi
  
  if command -v gdate > /dev/null; then
    gdate -d "$d $((7 - $(gdate -d $d +%w) + $(gdate -d $1 +%w))) day" +%F
    exit 0
  fi

  echo 'not found gdate command or not use `-d` option.'
  exit 1
}

nextwd "$@"
chmod +x ./nextwd.sh
sudo mv ./nextwd.sh /usr/local/bin/nextwd

% echo 2017-09-18 | nextwd Tue     
2017-09-26

回答

echo 2017-09-18 | nextwd Tue | nextwd Fri | xargs -I@ gdate -d '@ yesterday' +%F
2017-10-05

# 67

20190101 たまごかけごはん
20190102 納豆ごはん
20190105 焼肉
20190106 断食
20190107 焼肉
20190108 たまごかけごはん
20190110 ミートボールスパ
20190111 ニシンのパイ
20190113 断食
20190114 焼肉

曜日ごとにメニューを書き出す

cat ../shellgei160/qdata/67/dinner | teip -f 1 -- gdate -f- '+%F %a' | awk '{print $3 > $2}'

# 68

利用できるUnix時刻の最大値を求める

f=0;t=$(bc <<< 2^90);while [ $(bc <<< $t-$f) != 1 ];do m=$(bc <<< "($f+$t)/2"); echo $m; gdate -d @$m && f=$m || t=$m;done

# 69

うるう秒のあった日の列挙
以下が正解らしいがMacだとrightプレフィックスのタイムゾーンが使えなそう

printf "%s 9 1sec ago\n" {1970..2017}-{01,07}-01 | TZ=right/Japan gdate -f- | grep :60

# 70

テキストをエクセル方眼紙にするために変換

cat ../shellgei160/qdata/70/excel_hogan.txt | sed 's/./&_/g' | sed 's/"/""""/g' | sed 's/,/","/g' | tr _ , | nkf -sLwx > hoge.txt
ぱんだぱんだ

文字コード

uniname

Unicodeの文字列を解析するコマンド

brew install uniutils
echo a | uniname

nkf

utf8 -> sjis

nkf -sLwx

  • -s sjisへの変換
  • -Lw 改行をLF -> CRLF
  • -x 半角カタカナ->全角を抑止する

もしutf8->sjisでうまくいかないことがあったら-Sをつけてsjisであることを明記するとうまくいくかも

sjis -> utf8

nkf -wLux

  • -w utf8への変換
  • -Lu 改行をCRLF->LF
ぱんだぱんだ

#71

各文字のバイト数を出力する

% cat ../shellgei160/qdata/71/uni.txt | grep -o . | while read -r s;do echo -n "$s";echo -n $s | wc -c;done 
a       1
±       2
運       3
🎂       4

#72

絵文字をUnicodeで出力する

echo -e '\U1F363\U1F37A'
🍣🍺

# 73

windows環境で作成したzipファイルの文字化けをなおす

# 74

日付に元号をつける
元号の合字のUnicodeが存在する
令和: U+32FF`` 平成: U+337B```

awkの-F[^0-9]を指定すると区切り文字を正規表現で指定することになり、この場合、数字以外の任意文字が区切りとなり、結果として数字だけを抽出できる、みたい

cat ../shellgei160/qdata/74/days.txt | awk -F'[^0-9]' '{printf "%d %02d %02d\n", $1, $2, $3}' | awk '{if($1$2<"201905"){a="337B";$1-=1988}else{a="32FF";$1-=2018}print "echo -e \\\\U"a,$1"年",$2"月",$3"日"}' | bash | sed 's/ 1年/ 元年/' | tr -d ' ' | sed 's/年0/年/;s/0月/月/'
㍻31年3月03日
㋿元年1月10日
㋿2年8月01日

# 75

絵文字を消去する
nkfに2回通すだけ
絵文字はUnicodeにしか存在しないのでsjisにすると絵文字が消去される。
その後、utf8に戻すと絵文字が消えている

cat ../shellgei160/qdata/75/minutes.txt | nnkf -s | nkf -w

rb使った別解

% cat ../shellgei160/qdata/75/minutes.txt | rb 'each{|row|puts row.gsub(/[^#*\P{Emoji}]\u200d?/,"")}'

[^#*\P{Emoji}]が二重否定になっていて絵文字以外の文字に#とを加えたものを否定することで絵文字がマッチする。\p{Emoji}で絵文字にマッチできるがや#といった一部の記号も含まれてしまっているので二重否定を使用している。
\u200dゼロ幅接合しと呼ばれる特殊なメタ文字で絵文字同士を結合させる。
以下のようにメタ文字を消去すると絵文字が分解される

% echo 👨<200d>👩<200d>👧<200d>👦
👨‍👩‍👧‍👦

% echo 👨<200d>👩<200d>👧<200d>👦 | sed s_$(echo -ne \\U200d)__g
👨👩👧👦
ぱんだぱんだ

#76

少し前のMacのファイルシステムは**HFS+**というものが採用されており、簡単に言うと濁音や半濁音の点や丸を分けることができる。見た目はわからないけど

なので以下のようにdiffをとると差分がでない

diff ../shellgei160/qdata/76/mac_ls_old.txt ../shellgei160/qdata/76/mac_ls_new.txt
1,3c1,2
< ポンセ.txt
< バナザード.txt
< パチョレック.txt
---
> ポンセ.txt
> パチョレック.txt
nkf -Z4 ../shellgei160/qdata/76/mac_ls_old.txt | nkf | diff - ../shellgei160/qdata/76/mac_ls_new.txt 
2d1
< バナザード.txt

上記のようにnkf -Z4で半角に戻し、もう一度nkfを通し全角にすることで差分がちゃんと出る

# 77

文字コードが異なるファイル群から検索する
ggrep -aは非UTF8の文字を検索するためのオプション。-Hは検索結果にファイル名を表示する。

% ls ../shellgei160/qdata/77/meme_* | xargs -I@ sh -c 'ggrep -aH . @ | nkf' | grep 山田 | sed 's_../shellgei160/qdata/77/__' | sed 's/:.*//'
meme_euc
meme_utf8

# 78

漢字を抽出して常用漢字以外を抽出する
文字列から正規表現にマッチした箇所を抽出するにはsliceでできるが複数マッチにはscanを使う
先頭要素にマッチした箇所全部入ってるのでuniqで重複箇所を削除しておく
grep -fで常用漢字表を検索条件にして検索した結果を反転することで常用以外の漢字を出力

% cat ../shellgei160/qdata/78/sample_novel.txt | rb -l 'scan(/\p{Han}/).uniq' | ggrep -vf ../shellgei160/qdata/78/jouyou_kanji.txt
智
皰
噛
云
鴎

# 79

文字化けをなおす
nkfの自動判別でだめなのでiconvを使用する
iconv -lで対応している文字コードを一覧出力し、iconvに通す。
エラーにならなかったものからひらがな、カタカナ、漢字が含まれるものを抽出し、再度iconvに通す
これでうまくいくはずなんだけど想定通りにならなかった
iconvの挙動が解答と違いそう

% iconv -l | while read c;do iconv -f $c ../shellgei160/qdata/79/message.txt &> /dev/null | ggrep -qP '\p{Hiragana}|\p{Katakana}|\p{Han}' && echo $c && iconv -f $c ../shellgei160/qdata/79/message.txt;done 

バイナリ操作

% echo 🍣🍺 | iconv -f UTF-8 -t UTF-32 | xxd
00000000: 0000 feff 0001 f363 0001 f37a 0000 000a  .......c...z....
  • 絵文字をUTF-8からUTF-32に変換してxxdに通す
  • 行頭の00000000:オフセットと呼ばれるもので当該の行がどの位置のデータに相当するかを表す
  • .......c...z....は実際の文字だが文字化けしてるのでほとんど情報はもたない
  • 寿司のコードポイントはU+1F363
  • このデータを0001F363とするか63f30100とするかはCPUが先に読む方を下からか上からかという話と関係があり、どちらからか読むかをバイトオーダーという。
  • 下から読むのをリトルエンディアン、上から読むのをビッグエンディアン と呼ぶ。
  • UTF-32やその他のいくつかの種類のテキストデータは、バイトオーダーを明示するためにBOMという特別なバイト列が先頭に記述されている。
  • 0000 feffの部分がBOM。この場合、高い方からでビッグエンディアン
  • 以下のようにするとリトルエンディアン
% echo 🍣🍺 | iconv -f UTF-8 -t UTF-32LE | xxd
00000000: 63f3 0100 7af3 0100 0a00 0000            c...z.......

# 80

2進数の文字を元に戻す
obase=16で出力を16進数、ibase=2で入力を2進数にしてbcに通す
xxdの-rはリバースでデコードするため、16進数の文字をデコードし元の文字を表示する

% cat ../shellgei160/qdata/80/zeroone | sed 's/^/obase=16;ibase=2;/' | bc | xxd -p -r
不労所得
ぱんだぱんだ

#81

BOMが含まれていれば[BOM]を挿入する。
BOMは3バイトで0xEFBBBFなので置換して再度xxdでデコードする

% cat qdata/81/bom.txt | xxd -p | sed "s/^efbbbf/$(echo -n '[BOM]' | xxd -p)/" | xxd -p -r
[BOM]ボムボムプリンおいしい

# 82

でかいファイルを分割する。1000mごとに分割。

% gsplit -b 1000 -d -a 2 qdata/82/image.bmp image.bmp.

-dで連番suffix指定、-a 2でsuffixの長さ指定

# 83

SJISで書かれたシェルスクリプトを実行する

% ./qdata/83/soleil.bash | iconv -f SJIS -t UTF8
親戚のャ激Cユちゃんは撫
iconv: (stdin):1:23: cannot convert

SJISから変換してもうまく表示できない

SJISは2バイトで日本語を表示する。このとき0x5cはASCIIコードで半角のバックスラッシュと一致する。そのため、例えば「ソ」は0x835cとなっているため2バイト目のコードがバックスラッシュとして認識され無視される。このような問題が発生する文字はダメ文字と呼ばれ、5c問題と言われている。

この解答はバックスラッシュを増やして置換することで解決できる。

% cat ./qdata/83/soleil.bash | sed 's/\\/\\\\/g'  > new_soleil.bash

# 84

# 85

ぱんだぱんだ

#90

基数の違う数字の計算

% printf "0x%x\n" $(( 4#12 + 8#34 + 16#56 ))
0x78

4進数、8進数、16進数を算術式展開で計算し、printfで16進数として出力

# 91

2,3,5,7の数字を使ってその辺の長さの三角形を作るときの組み合わせが何通りか。

% echo {2,3,5,7}{2,3,5,7}{2,3,5,7} | xargs -n 1 | awk '$1<=$2&&$2<=$3' FS= | awk '($1+$2)>$3' FS= | wc -l
      14

awkでFS=とするとセパレーターに空文字を指定したことになり一文字ずつ変数に格納されることになる。

# 92

0.01mmの厚さの紙を半分に折り曲げていき1000kmを超えるには何回折り曲げればいいか。

% yes | awk '0.01*(2^NR)>1000^3{print NR;exit}'

yesコマンドは永遠にyを出力する。
パイプで繋いだawkではこのyは使わず行番号を使い、指数計算をして1000kmを超えたらprintしてexit

# 93

2019年1 月1日0時0分0秒以後、初めて素数になるとき

% seq 0 inf | sed 's/.*/2019-01-01 00:00:00 & sec/' | gdate -f - '+%Y%m%d%H%M%S' | factor | awk 'NF==2{print $2;exit}'

0から無限に整数を出力してdateに通せするような文字列をsedで生成して、最後は素因数分解。

# 94

0から100まででn = a^2 + b^2 + c^2 + d^2を満たす組み合わせを全てのnに対して表示する。

とりあえず、a,b,c,dで一つでも10以上があると100を超えてしまうので0-9の範囲でa,b,c,dの組み合わせ全通りを考えてそこからnが0-100のものを抽出する方向で考える。

% seq -w 0 9999 | awk '{print $0, $1*$1+$2*$2+$3*$3+$4*$4}' FS= | sort -k2,2n | uniq -f 1 | head -n 101

# 95

ぱんだぱんだ

# 96

% awk '{filename = FILENAME;sub(".*/", "", filename);print $0, filename}' ./qdata/96/user* | sort | awk '{a[$1]=a[$1]" "$2}END{for(k in a)print k, a[k]}' | awk 'NF==4{print $0}'
2019/07/10  user1 user2 user3
2019/07/15  user1 user3 user4

# 97

% cat qdata/97/tate.txt
このたびの私の寝坊及び早弁について
とっても反省しておりますので、
給与よんばいで許してください。
許せみんな。

縦読みするとたてよみになる列のはじめの行番号と列番号を出力する

% cat qdata/97/tate.txt | rb 'a=map{|l|l.chomp.split("").concat([""]*20)[0..20]}.transpose.map{|l|l.join}' | rb -l 'i=index(/たてよみ/);"#{i+1} #{$.}" if i' | awk NF

# 98

commコマンドでプロセス置換で入力した二つのファイルの共通する行、片方にしかない行を表示できる。 1列目がlist1にしかない値、2列目がlist2にしかない値、3列目が共通する値

% comm <(sort qdata/98/list1) <(sort qdata/98/list2) 
 シァル芸
		シェノ芸
		シェル芸
		シェレ芸
	シュル芸
		ンェル芸

この値はタブで区切られてるのでタブを区切り文字に配列にしてごにょごにょ

% comm <(sort qdata/98/list1) <(sort qdata/98/list2) | rb -l 'a=split("\t");i=a.size;file=i==3?"common":"oneside";File.open(file, "a"){|f| f.puts "#{a[i-1]}"}'  
% head common oneside 
==> common <==
シェノ芸
シェル芸
シェレ芸
ンェル芸

==> oneside <==
シァル芸
シュル芸

#99

ポーカーの役でフラッシュの行を見つける

% cat qdata/99/cards.txt | rb 'select {|l| /^([♥♦♣♠]) \d+ \1 \d+ \1 \d+ \1 \d+ \1 \d+$/.match(l)}'
♣ 7 ♣ 8 ♣ 9 ♣ 11 ♣ 13
♥ 8 ♥ 9 ♥ 10 ♥ 11 ♥ 12
♠ 9 ♠ 10 ♠ 11 ♠ 12 ♠ 13

フルハウス

% cat qdata/99/cards.txt | rb 'select {|l| /^. (\d+)( . \1){1,2} . (\d+)( . \3){1,2}$/.match(l)}'
♣ 5 ♦ 5 ♣ 11 ♠ 11 ♥ 11
♠ 3 ♣ 3 ♥ 3 ♣ 11 ♦ 11
♥ 2 ♦ 2 ♠ 13 ♣ 13 ♥ 13

# 100

しりとりになるように出力

% join -j9 qdata/100/shiritori.txt{,} | ggrep '\(.\) \1' | gtsort 2>/dev/null
けんこう
うしみつどき
きゅうけい
いちょう
うがい
いんどあ
あけがた

join -j9は9列目の値でjoinだけど、9列目がない場合はキーなしでジョインする
そのあとにgrepの正規表現でしりとりになってるやつを抽出

ぱんだぱんだ

# 101

% cat qdata/101/alphabet_connection                                            
b c f i j k l p e q r u w a y z

上記ファイルを連続したファルファベットを````a-c```のように加工して出力。

% cat qdata/101/alphabet_connection | tr ' ' '\n' | sort | comm - <(echo {a..z} | tr ' ' '\n') | awk -F"\t" '{print $3}' | ruby -0777 -pe 'gsub(/([a-z])(\n[a-z])*\n([a-z])/m, "\\1-\\3")' | xargs
a-c e-f i-l p-r u w y-z

rubyの-0777sed -zの代わりで、入力レコードのセパレーターを変更している。全行にまたいで置換するため。-nではなく-pなのは置換結果を出力するため。

# 102

ぱんだぱんだ

# 108

ログファイルからプロセスキルされたプロセスを探して集計する

% gzcat syslog.gz | grep Killed | awk '{print $10}' | tr -d '()' | awk '{a[$1]+=1}END{for(k in a)print k, a[k]}' 
superapp 9

最後の集計はsortしてuniqでもいいけど件数が多くなるとawkの方が早い

% gzcat syslog.gz| grep invoked | sed -E 's/(.*) invoked .*/\1/' | sed 's/^.*[0-9]] //' | sort | uniq -c | sort -r
   3 apache2
   2 gmain
   1 tmux: server
   1 systemd-network
   1 lsb_release
   1 apport

tmux: serverというプロセス名がやっかいなのでsedで前後削除してプロセス名だけ抽出する。

# 109

/etc配下の同じファイル名のファイル

% sudo find /etc/ -type f | sudo xargs md5sum | awk '{a[$1]=a[$1]" "$2}END{for(v in a)print v, a[v]}' | awk 'NF>2'

md5でハッシュ化してawkの連想配列に格納して重複してるやつだけ出力

# 110

/var/logs配下のディレクトリごとのファイル数を集計

% sudo find /var/log -type d | while read d;do echo -n $d" ";find "$d" -type f -maxdepth 1 | wc -l;done
ぱんだぱんだ

# 134

pythonのファイルでインデントが4の倍数でない行を出力

awkのmatch関数で先頭空白の行にマッチさせる。マッチした長さはRLENGTHという変数に格納される。awkのif文は0がtrueでそれ以外はfalseとされるため、4の倍数以外のときにprintが実行される。

% cat 134/hoge.py | awk 'match($0, /^ +/){if(RLENGTH%4)print NR}' 
5

# 135

% cat 135/sample.lisp 
; n!(階乗)を返す
(defun fact (n) (if (<= n 1) 1 (* n (fact (- n 1)))))

; n番目のフィボナッチ数を返す
(defun fib (n) (if (<= n 1) n (+ (fib (- n 1) (fib (- n 2)))))

; 1からnまでの総和を返す
(defun sum1 (n) (if (<= n 1) n (+ n (sum1 (- n 1))))

;; 実行
(format t "fact:~D~%fib:~D~%sum1:~D"
  (fact 5)
  (fib 5)
  (sum1 5))

このLispファイルの関数定義のかっこの数があってない関数名を出力

% cat 135/sample.lisp | grep '^(defun' | awk '{if(gsub(/\(/, "&", $0) != gsub(/\)/, "&", $0)){print $2}}'
fib
sum1

# 136

コードを入れ替える。

% cat 136/somecode.c | ruby -0777 -pe 'gsub(/(int b.+)(void a.+)(main.+)/m, "\\2\\1\\3")'
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void a()
{
	int i = 0, j = b();
	for (; i < j; i++){
		puts("a");
	}
}

int int b()
{
	return rand()%10;
}

main(int argc, char const *argv[])
{
	srand(time(NULL));
	a();
	return 0;
}

# 137

コードをフォーマットする。

% cat 137/fib.c | sed 's/[{}]/\n&\n/g' | cat -s | ruby -ne 'i ||= 0;i -= 1 if $_.match(/^}/);print "\t" * i;puts $_;i += 1 if $_.match(/^{/)'

#include <stdio.h>

int fib (int n)
{
	if(n <= 1)
	{
		return n;
	}
	return fib(n-1)+fib(n-2);
}

int main (void)
{
	int i;for(i=0;i<10;i++)
	{
		printf("%d\n",fib(i));
	}
	return 0;
}

最初のsedで{}の前後で改行。rubyで{}の数をカウントしながらタブを行頭に入れる。

ぱんだぱんだ

# 138

% cat 138/browser.csv 138/os.csv 138/service.csv | ruby -lne 'BEGIN{a={}};$_.split(",").each{|val| a[$.]||=[];a[$.] << val};END{a[1].each{|br|a[2].each{|os|a[3].each{|sv|puts "#{br} #{os} #{sv}"}}}}' | grep -v 'IE [macOS|Linux]' | grep -v 'Safari [Windows|Linux]'

rubyで連想配列に格納することでos, ブラウザ,サービスごとに分けてループして全組み合わせ作る。
あとはgrepで排除したい組み合わせを指定。

# 139

% echo https://{example,cc.bb.aa.example.com,bb.aa.example.com,aa.example.com}/{A,A/B,A/B/C} | sed 's/ /\n/g'
https://example/A
https://example/A/B
https://example/A/B/C
https://cc.bb.aa.example.com/A
https://cc.bb.aa.example.com/A/B
https://cc.bb.aa.example.com/A/B/C
https://bb.aa.example.com/A
https://bb.aa.example.com/A/B
https://bb.aa.example.com/A/B/C
https://aa.example.com/A
https://aa.example.com/A/B
https://aa.example.com/A/B/C

# 140

ぱんだぱんだ

# 147

% cat page | ggrep -zoP '<td[^<]*>.*?</td>' | tr \\0 \\n | sed -E 's/<[^<]*>//g' | mecab | awk '{a[$1]++}END{for(k in a)print k, a[k]}' | sort -k2,2nr | head -n 5
EOS 195
シェル 84
芸 78
が 74
月 69

grepでtdタグだけ抽出して、念のため-zオプションで改行が含まれていても1行にするようにして、みやすいようにnull文字を改行に戻して、タグをsedではずして、mecabで形態素解析してawkで集計

# 148

路線情報APIから指定の路線の駅名を出力

% curl http://file.ueda.tech/eki/l/11303.json | jq '.station_l[].station_name' 
"川崎"
"尻手"
"矢向"
"鹿島田"
"平間"
"向河原"
"武蔵小杉"
"武蔵中原"
"武蔵新城"
"武蔵溝ノ口"
"津田山"
"久地"
"宿河原"
"登戸"
"中野島"
"稲田堤"
"矢野口"
"稲城長沼"
"南多摩"
"府中本町"
"分倍河原"
"西府"
"谷保"
"矢川"
"西国立"
"立川"
"八丁畷"
"川崎新町"
"小田栄"
"浜川崎"

# 149

天気予報

% curl wttr.in/Tokyo   
Weather report: Tokyo

      \   /     Sunny
       .-.      19 °C          
    ― (   ) ―   → 17 km/h      
       `-’      10 km          
      /   \     0.0 mm         
                                                       ┌─────────────┐                                                       
┌──────────────────────────────┬───────────────────────┤  Wed 22 Nov ├───────────────────────┬──────────────────────────────┐
│            Morning           │             Noon      └──────┬──────┘     Evening           │             Night            │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│     \   /     Sunny          │     \   /     Sunny          │    \  /       Partly cloudy  │  _`/"".-.     Patchy rain po…│
│      .-.      19 °C          │      .-.      19 °C          │  _ /"".-.     19 °C          │   ,\_(   ).   19 °C          │
│   ― (   ) ―   ↗ 18-24 km/h   │   ― (   ) ―   → 21-27 km/h   │    \_(   ).   ↗ 5-7 km/h     │    /(___(__)  ↗ 5-7 km/h     │
│      `-’      10 km          │      `-’      10 km          │    /(___(__)  10 km          │      ‘ ‘ ‘ ‘  10 km          │
│     /   \     0.0 mm | 0%    │     /   \     0.0 mm | 0%    │               0.0 mm | 0%    │     ‘ ‘ ‘ ‘   0.0 mm | 71%   │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
                                                       ┌─────────────┐                                                       
┌──────────────────────────────┬───────────────────────┤  Thu 23 Nov ├───────────────────────┬──────────────────────────────┐
│            Morning           │             Noon      └──────┬──────┘     Evening           │             Night            │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│     \   /     Sunny          │    \  /       Partly cloudy  │     \   /     Clear          │     \   /     Clear          │
│      .-.      19 °C          │  _ /"".-.     19 °C          │      .-.      19 °C          │      .-.      20 °C          │
│   ― (   ) ―   → 21-27 km/h   │    \_(   ).   → 29-37 km/h   │   ― (   ) ―   ↗ 37-47 km/h   │   ― (   ) ―   ↗ 43-55 km/h   │
│      `-’      10 km          │    /(___(__)  10 km          │      `-’      10 km          │      `-’      10 km          │
│     /   \     0.0 mm | 0%    │               0.0 mm | 0%    │     /   \     0.0 mm | 0%    │     /   \     0.0 mm | 0%    │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
                                                       ┌─────────────┐                                                       
┌──────────────────────────────┬───────────────────────┤  Fri 24 Nov ├───────────────────────┬──────────────────────────────┐
│            Morning           │             Noon      └──────┬──────┘     Evening           │             Night            │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│     \   /     Sunny          │     \   /     Sunny          │     \   /     Clear          │     \   /     Clear          │
│      .-.      19 °C          │      .-.      17 °C          │      .-.      +14(10) °C     │      .-.      +13(9) °C      │
│   ― (   ) ―   → 63-78 km/h   │   ― (   ) ―   → 69-81 km/h   │   ― (   ) ―   → 79-91 km/h   │   ― (   ) ―   → 74-85 km/h   │
│      `-’      10 km          │      `-’      10 km          │      `-’      10 km          │      `-’      10 km          │
│     /   \     0.0 mm | 0%    │     /   \     0.0 mm | 0%    │     /   \     0.0 mm | 0%    │     /   \     0.0 mm | 0%    │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
Location: 東京都, 日本 [34.2255804,139.294774527387]

Follow @igor_chubin for wttr.in updates

# 150

ソフトウェアデザインの2022年で品切れの号を出力

% curl -s https://gihyo.jp/magazine/SD/backnumber | grep -B3 品切 | grep '^<h3' | sort | sed 's/<[^>]*>//g' | grep 2022
Software Design 2022年5月号
Software Design 2022年6月号
Software Design 2022年7月号
Software Design 2022年8月号
Software Design 2022年9月号
Software Design 2022年10月号
Software Design 2022年11月号
Software Design 2022年12月号

# 151

駅情報APIと地理情報APIを使って山手線の駅を標高昇順で表示

% curl -s http://file.ueda.tech/eki/l/11302.json | ruby -rjson -0777 -ne 'JSON.parse($_)["station_l"].each{|row|puts row["station_name"], row["lon"], row["lat"]}' | xargs -n3 sh -c 'printf "$0 ";curl -s "https://cyberjapandata2.gsi.go.jp/general/dem/scripts/getelevation.php?lon=$1&lat=$2&outtype=JSON" | jq ".elevation"' | sort -k2,2n
有楽町 2.8
品川 3.1
高輪ゲートウェイ 3.1
御徒町 3.3
田町 3.4
新橋 3.6
東京 3.6
大崎 3.8
秋葉原 3.8
五反田 3.9
浜松町 4.3
神田 4.6
田端 6
日暮里 6.3
鶯谷 8.9
上野 11.3
西日暮里 12.4
渋谷 15.5
駒込 16.6
大塚 19.1
恵比寿 20.4
巣鴨 21.9
高田馬場 22.3
目白 25
原宿 28.4
目黒 29.7
池袋 32.5
代々木 34.9
新大久保 35
新宿 37.5

# 152

HTTPステータスコードで出力文字を変える

% while sleep 5;do curl -I -s example.com | head -n 1 | awk '$2==200{print "Success!"}$2!=200{print "Fail!!"}';done
Success!

# 153

telnetで通信する

% ( printf 'HEAD / HTTP/1.1\nHost: www.google.co.jp\n\n';sleep 1) | telnet www.google.co.jp 80
Trying 2404:6800:4004:823::2003...
Connected to www.google.co.jp.
Escape character is '^]'.
HTTP/1.1 200 OK

レスポンスが返ってくる前に処理が終了してしまうのでsleepで1秒くらい待つ。

HTTPSの場合はopensslを使う。

% ( printf 'HEAD / HTTP/1.1\nHost: www.google.co.jp\n\n';sleep 1) | openssl s_client -connect www.google.co.jp:443 -quiet -no_ign_eof

# 155

HTTPレスポンスを返すサーバーをワンライナーで立てる。そしてレスポンスのデータサイズと異なるContent-Lengthを指定する。

% (echo -e "HTTP/1.1 200 OK\nContent-Length: 5";echo;printf test) | nc -N -l 8080
ぱんだぱんだ

# 156

ドメインの一覧から複数IPを持つドメインのみ抽出する。

% cat shellgei160/qdata/156/domains.txt | xargs dig +noall +answer | awk '{print $1}' | uniq -d | sed 's/\.$//'

# 157

pingのパケット解析をする。

# 160

サーバー証明書から有効期限を取得して表示する。

% openssl s_client -connect example.com:443 < /dev/null 2>/dev/null | sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' | openssl x509 -text | grep -A 1 'Not Before'
            Not Before: Jan 13 00:00:00 2023 GMT
            Not After : Feb 13 23:59:59 2024 GMT
このスクラップは5ヶ月前にクローズされました