たいちょーの雑記

ぼくが3日に一度くらい雑記をかくところ

雑記 2020-11-09

ボタン押せないマン

毎日朝と夜に押さないといけないボタンがある。このボタンを押すようになってからしばらく経つが定期的に、それも結構な頻度で忘れる。 推し忘れたことによる不都合は他のところで補填できるので、まぁいいんだけど「またやってしまった」と毎回落ち込む。 何も対策していないわけじゃなくて、ボタンを押すことを今日のTODOにしてみたりとか、まぁなんかいろいろやった。でもダメだった。意識ではもうどうにもならないので、機械に任せるのがいいんだろうなあ

実は割と運動をしている。でもムキムキになるような運動はしていない。ので体型は普通。 ストレッチも毎日している。少しずつ柔らかくなっているけど、日中の体勢が悪いせいか腰痛とかは治らない。長座体前屈が良くできるようになる一方である。

おわり

久しぶりに書いたけど結構書くことあるなあ

第50回シェル芸勉強会に参加しました

参加しました

今回もリモート開催でした。毎度書きますけど、やっぱり問題の辛さに呻く声とかが聞こえなくて寂しいですね

YouTubeの配信はこちら

今回も時間中に解答したものと、これを書くと同時にPowerShellでの解答も書いていこうと思います。できるのか…俺に…?

Q1

un.txt.gzから、UNITY, UNKO, unity, unkoの数をそれぞれなるべく短時間で数えてください。 単語の途中に改行が入っているものも数えてください。

問題の意味は簡単に理解できるので、パッと思いつくような書き方で行くなら

$ zcat un.txt.gz | tr -d \\n | grep -io -e "UNKO" -e "UNITY" | sort | uniq -c | sort -rn

ところが、これでは時間がかかりすぎるということで、速度の改善が必要とのこと。最初はzgrepで改行を巻き込んだ検索をしようとしました

$ zgrep -oiP "(U\n?N\n?K\n?O\n?|U\n?N\n?I\n?T\n?Y\n?)" ./un.txt.gz | sort | uniq -c | sort -rn

これだとうまく引っかけられなかったので、長考。解答例では以下のように aだけの行を削ってから検索すればよいとのこと

zgrep -vP "^a+$" *a/*50/un.txt.gz | tr -d \\n | grep -ioP "un(ko|ity)" | sort | uniq -c | sort -rn

ところがこれは

aaaaaaaaaaaaaaaaun
aaaaaaaaaaaaaaaaaa
koaaaaaaaaaaaaaaaa

というデータにぶつかったときに正しくなくなる。(参考)

それを踏まえてもう一度考える。

$ time zcat ./un.txt.gz| sed -E 's/a+/a/g' | tr -d \\n | grep -ioP "UN(KO|ITY)" | sort | uniq -c | sort -rn 
      3 unko
      3 unity
      2 UNKO
      2 UNITY
zcat ./un.txt.gz  5.39s user 0.83s system 61% cpu 10.102 total
sed -E 's/a+/a/g'  7.67s user 2.43s system 99% cpu 10.102 total
tr -d \\n  0.03s user 0.24s system 2% cpu 10.102 total
grep -ioP "UN(KO|ITY)"  1.23s user 0.00s system 12% cpu 10.190 total
sort  0.00s user 0.00s system 0% cpu 10.189 total
uniq -c  0.00s user 0.00s system 0% cpu 10.189 total
sort -rn  0.00s user 0.00s system 0% cpu 10.188 total

連続するaを1文字のaにしてから改行削除、grepで絞り込みをしてみた。これだと10秒ぐらい。もっと速い解答もTwitterにあるけど、僕のはこれとしておきます。

続いてPowerShellです。

$ (7z e ./un.txt.gz -so |%{ $_ -replace "a+","a" }| Get-Unique) -join "" | Select-String -AllMatches "UN(KO|ITY)" | %{$_.Matches.Value} | Group-Object -CaseSensitive

Count Name                      Group
----- ----                      -----
    2 UNITY                     {UNITY, UNITY}
    3 unko                      {unko, unko, unko}
    2 UNKO                      {UNKO, UNKO}
    3 unity                     {unity, unity, unity}

やることは同じです。PowerShellではgrepの代わりにSelect-Stringをよく使いますが、grep-oに相当する機能を単独で持ちません。Select-StringMatchInfoというオブジェクトを出力するので、そこからマッチした部分(MatchInfo.Matches.Value)を取り出すことでgrep -o相当のことができます。あとはGroup-Objectで数え上げればOKですね。

Q2

un.txt.gzでaのみが書いてある行の行数をなるべく早くカウントしてください。

とのこと。

$ zgrep -cP "^a+$" *a/*50/un.txt.gz
$ rg -cz "^a+$" *a/*50/un.txt.gz

このどちらかがまあ割と高速。解説することがないですね。

PowerShellでの解答も書いていこうと思います。

$ 7z e ./un.txt.gz -so | group

これで出るんじゃないかな・・・(手元じゃ終わらなかったのでわからない)

Q3

catコマンドを遅くする問題

cat ./un.txt > /dev/null

こう書くと、一瞬で終わるのだけど、このコマンドの前に何かを仕込んで遅くするというもの

$ function cat() { \cat $1 | \cat }
$ cat a > /dev/null

単純に二段重ねにしたら遅くなるよねって感じですね。解答例としては、/proc/sys/vm/drop_cachesをいじってやるというもの。勉強になるなあ

$ echo 3 | sudo tee /proc/sys/vm/drop_caches
$ cat a > /dev/null

Q4

1から1億までの数字をシャッフルして2列にしたデータ(ファイル名はaにしましょう)を作ってください。速いワンライナーを考えてください。

とのこと。最初に自分が書いたのがこれ

$ seq 1000000 | paste -d' ' - - | teip -f1 shuf | teip -f2 shuf > a'

xargsでは永遠に終わらないだろうなと思っていたので、paste、シャッフルは後続のteipに任せることに。これ結構いいと思ったんですが…

マシンがやられてしまった。物理ボタンぶち押して再起動しました。悲しい。

再起動中TLを見ているとshuf-iオプションというのがあるのを知ってへぇと思いました

$ shuf -i 1-10
8
3
7
2
9
10
1
6
4
5

PowerShellでの解答も書いていこうと思います。

$ 1..100000000 | Sort-Object { Get-Random } | %{[PSCustomObject]@{k=$_}} | Format-Wide -Column 2

横に並べるって言うのが中々大変でした。Format-Wideでそのようなことができるのですが、一度PSCustomObjectにして置く必要があるんですねこれが。あとこれも全然終わりません。悲しい。どうしたらいいんだ

一応、Get-Content-ReadCountを使えばファイルの中身を分割できるので、少しずつなら出力できますね。ただし、区切られた区間でのみのシャッフルになりますが

$ 1..100000000 > nums
$ Get-Content ./nums -ReadCount 10000 | %{ $_ | Sort-Object{Get-Random} } | %{[PSCustomObject]@{k=$_}} | Format-Wide -Column 2

この問題の出力は次の問題で使うので、aというファイルに書き出しておきます。

Q5

cat a | sort -k2,2n > b を出力内容を変えずに高速化する問題。

これは知識問題。気づけるかな?という問題でした。catから始めていますが、sortに直接渡した方がCPU使用率に差がでるとのこと。そういえばcatからおもむろに始めることが多いので、知らないうちにワンライナーが遅くなっちゃってるかもですね。勉強になる

Q6

出題時マシン再起動中でした(前問を試していたら死んだ)。問題を見る限り、teipが使えそうと思っていたらTLにもあったのでさすがteipだなあと思いました

復活して書いたのがこれです。

$ awk '$1%2==$2%2==1' a | teip -og \\d+ -- factor | awk 'NF==4' > c

前段では、両方とも奇数なカラムだけを取り出して前処理、次に-og \\d+で数値だけなカラムに対してfactorを適用します。あとは awkで切り出しですね。

PowerShellでは、なんだろう…teipが使えないので、ForEach-Objectfactorするとかかな…?

Q7

aに対して二つの操作をする問題。

  • 行頭に A, B, C, ... , Z, A, ...を付ける
  • 行頭のA-ZでGroupByする

これ、最中は同時にやるもんだと思っていたので以下の解答をしました

$ awk '{print $1"_"$2}' a | paste -d' ' -{,,,,,,,,,,,,,,,,,,,,,,,,,} | rs -T| tr -d _ | paste <(echo {A..Z} | fmt -1) -'

26個なのは決まっているので、pasteで横に26個並べてから rs -Tで転地。あとは行頭に A-Zを付けただけですね。まぁこれは題意とは違うのでダメなのですが・・・

Q8

Q7について、さらに各行の数字を小さい順にソートするという条件をつけてください。ans2というファイルに保存します。

Q5の解きなおしをしていたらマシンが死んだので再起動していました。本当に悲しい。

LT

今回はLTしました。以前のエントリで書いたワンライナーC#ocsと、カラム選択を短縮できるselの紹介です。

ocsの方はまぁ良かったんですが、selの発表中に、すでにselfという同じ感じのあるよというナイフを突きつけられてめちゃめちゃ焦りました。

一応冷静にselfselの比較をしていたんですが、selには入力と出力のデリミタを指定できる、sed -iみたいな上書きができるなどの優位点があります。

速度的にはチューニング後のselHaskell版のselfで大体同じ。若干selfの方が速いです。ただしこれは、selの入力デリミタを固定文字(スペース)としたときなので、正規表現で分割するときは負けてしまいます。

あとはselgo getするだけ、selfPythonの特定バージョン以上が必要。もしくは、ghcなどでHaskell版をコンパイルする必要があるなどが違いですかね。

正直 sel 1 2 3 みたいな使い方をしたいだけならどちらでもよいので、好きな方を使おうって感じですね。今回のことでパフォーマンスチューニングに興味も沸いたのでselを爆速にしてやりたいと思っております。

まとめ

今回もとても楽しかったです!あととっても疲れました!!Q8のあたりとかマシンもぼくも疲労困憊でした!次回も楽しみです!ありがとうございました!

ワンライナー中にC#を書きたかったのでocsというコマンドを作った

作りました

github.com

経緯

自分はC#が好きなので、シェルでワンライナーを描いているときもふと、C#が使いたくなります。そういう時はMonoのREPLを使ったりしていたんですが、各行に対してC#を適応したいとき、書くコードが増えてしまうのでイマイチでした。

$ seq 10 | csharp -e 'string s;while((s=Console.ReadLine())!=null)Console.WriteLine(int.Parse(s) * 10)'

こんなの書いているうちに日が暮れてしまうので、大体awkとかですますのですが、やはりワンライナー中にC#を書きたいんですよね。ええ。え!?PowerShell!?なにそれ!!!

というわけでawkっぽく使えるC#ラッパーとしてocsを作りました。アイデアrbopyと同じです。

インストール

dotnetがインストールされているならクローンしてdotnet publishしてもいいですが、ビルド済みのバイナリをReleaseにアップロードしているのでそれをダウンロードするのがいいかと思います

それでもビルドしたい人

$ git clone https://github.com/xztaityozx/ocs
$ cd ocs
# win向け、linuxならlinux-x64, macOSならosx-x64
$ dotnet publish --self-contained true -c Release -r win-x64 -p:PublishSingleFile=true -p:PublishReadyToRun=true -o ./bin

$ ./bin/ocs --help #バイナリはこれ

zinitで管理する

zinit ice from"gh-r" as"program" pick"*/ocs"
zinit light xztaityozx/ocs

使い方

まぁawkと似た感じです

echo a b c | ocs '{println(F[1], F[2])}'
a b

ブロックに対するパターンも使えます

seq 10 | ocs 'int.Parse(F0)%2==0{ println(F0) }'
2
4
6
8
10

本当はパターンだけを書けるようにしたかったんですけど、パーサーを書くのに疲れてしまいました。Issueにはするので、そのうち追加するかもしれないです。

# こうしたかった
seq 10 | ocs 'int.Parse(F0)%2==0'
2
4
6
8
10

パターンにはboolを返す式であればどんな式でも書けます(パーサーが壊れてなければですが)

$ seq 10 | awk '{print $1%2 ? "" : $1}' | ocs 'F0.Any(){println(F0)}'
2
4
6
8
10

BEGINENDも使えます

seq 100 | ocs 'BEGIN{var sum=0}{sum+=i(F0)}END{println(sum)}'
5050

usingしたいなら-I, --importsを使います。

ocs -ISystem.IO '{ ... }'
ocs -ISystem.IO,System.Text '{ ... }'

フィールドセパレータも指定できます。

$ cat csv | ocs -F, '{...}'

# 正規表現もいける
$ echo 1abc2ebc3dbc | ocs -F'.bc' '{println((F[1], F[2], F[3]))}'
(1, 2, 3)

技術的な話

今回はC#コードの実行にRoslynのScripting APIを使いました

github.com

ocsがやっていることはC#内でC#のソースを生成して、をコンパイルして、実行している。だけですね。生成されたコードを出力するオプションも用意しているので、試しにocsが生成するコードを見てみます

$ seq 100 | ocs --show 'BEGIN{var sum=0}{sum+=i(F0)}END{println(sum)}'
[ 14:36:14 | Information ] Generated Code
var sum=0;
using(Reader) while(Reader.Peek() > 0) {
F0 = Reader.ReadLine();
sum+=i(F0);
}
println(sum);

5050

こんな感じです。内部で処理するだけなのでインデントもくそもないですが、読みやすくするとこうですね

var sum=0;
using(Reader) while(Reader.Peek() > 0) {
  F0 = Reader.ReadLine();
  sum+=i(F0);
}
println(sum);

それぞれwhileの前か後か中かにコードが展開されるだけですね。F0やReaderなんかは予約されている変数です。この辺はREADMEを読んでもらうことにします。

パターン付きのアクションはどうなるんでしょう。

$ seq 100 | ocs --show 'BEGIN{int a,b}{a+=i(F0)}F0.Length==2{b+=i(F0)}END{println((a,b))}'
[ 14:39:12 | Information ] Generated Code
int a,b;
using(Reader) while(Reader.Peek() > 0) {
F0 = Reader.ReadLine();
a+=i(F0);
if(F0.Length==2){b+=i(F0);};
}
println((a,b));

(5050, 4905)
int a,b;
using(Reader) while(Reader.Peek() > 0) {
  F0 = Reader.ReadLine();
  a+=i(F0);
  if(F0.Length==2){b+=i(F0);};
}
println((a,b));

こうですね。パターンを条件としたifが生成されます。とっても単純ですね。

おわり

とりあえずやりたかったことはできたのでうれしいです。ひとつ難点として挙げられるのはバイナリサイズがめちゃめちゃデカいことですね。Releaseにアップロードしているものは40MBぐらいなのでwgetするのに若干時間がかかるんですよね。まぁ仕方ないんですが・・・。バイナリサイズを落とすコンパイルとかも試したんですが、ocsが動かなくなるのであきらめました。悲しいのだ

まぁ良かったら使ってやってください

第49回シェル芸勉強会に参加しました

前回に続きリモートシェル芸勉強会でした。今回は問題は簡単だけど解答を変態にしなさいという感じらしい。変態じゃないのでわからんね。

問題と解答はこちら。当日のライブストリームはこちら

今回もbash/zshPowerShellの解答を書いていきます。PowerShellの方は変態度があまり高くないです。

Q1

「あ」と出力する問題。簡単にいくなら echo あとするわけですが、これでは変態度が低い。らしい。

$ seq 3000000 | factor | awk 'NF==2' | awk 'NR==1||NR==2||NR==182043{print $2}' | paste -sd '*' | sed 'iobase=16' | bc | sed 's/./\L&/g' | xxd -ps -r

はい。最中は提出できなかった・・・(凝りすぎた)

$ echo -n あ | xxd --ps | xargs -I@ printf "%d
" 0x@
14909826

「あ」を10進数で表すと14909826なので、これを素因数分解します。

$ echo -n あ | xxd --ps | xargs -I@ printf "%d
" 0x@ | factor
14909826: 2 3 2484971

これらの数値をseq | factorから取り出して行きます。

$ seq 3000000 | factor | awk 'NF==2' | grep -nP "^(2484971|2|3):"
1:2: 2
2:3: 3
182043:2484971: 2484971

1と2と182043行目に欲しい数字があるので、これを取り出します。

$ seq 3000000 | factor | awk 'NF==2' | awk 'NR==1||NR==2||NR==182043{print $2}'
2
3
2484971

これを*で連結して、評価してxxdで元に戻します。

PowerShellでの解答も行きます

@(2,3,29,71) | ForEach-Object { $agg=1 } { $agg*=$_ } { [char]$agg }
あ

まぁこれもやってること同じです。

Q2

ls -lを使わず、ファイル + タイムスタンプを表示する問題。ls -l が使いたい。 これはstatを使えばいいということですねえ

$ mkdir aaaa && cd aaaa && touch {a,b,c,d}
$ git init && git status -s | awk '{print $NF}' | xargs -I@ bash -c 'echo @ $(stat -c %y @)'

ファイルのリストアップにgit statusを使ったのですが、普通にecho *とかでもよかったですね。

PowerShellでの解答も行きます。

$ gi ./* |%{ $_.Name +" "+ $_.LastWriteTime  }

giGet-Itemへのエイリアスです。パイプで渡ってきたオブジェクトからプロパティを取り出しているだけですね。

Q3

seq 30からはじめて、3のつく数値だけを出力する問題。いつもはgrep 3で終わるけれど、これは変態度が低いんですね。慣れてきました。

$ seq 30 | perl -nle 'print if /3/'
$ seq 30 | csharp -e 'var x="";while((x=Console.ReadLine()) != null) if(x.Contains("3")) Console.WriteLine(x);'
3
13
23
30

解説するまでもないですね。どちらも愚直な実装だと思います

PowerShellでの解答も行きます。こっちもSelect-String 3とするだけなんですが、変態度がどうのこうの。

$ 1..30 | ?{[System.Text.RegularExpressions.Regex]::new("3").Match($_).Success}
3
13
23
30

?Where-Objectへのエイリアスです。ブロックを評価してtrueなものだけを通します。[System.Text.RegularExpressions.Regex]正規表現を扱うクラスです。これの正規表現な文字列を与えて、Matchメソッドで$_を評価していきます。今回は単に3と渡しているので3が含まれているとマッチするんですね。

Q4

echo 3+4+5+6からはじめて式を評価する問題。普通にやるならbcに渡せばいいですね。

$ echo 3+4+5+6 | bc
18

でもこれは変態的でないので、なんとか変態的にしていきます

$ echo 3+4+5+6 | tr + \n | jq -s add
18

trを使って+を改行に置換し、jqで足し算します。このjqでの足し算好きなのでよく使います。

PowerShellでの解答も行きます

$ echo 3+4+5+6 | iex
18

iexInvoke-Expressionへのエイリアスです。渡された文字列を評価します。まぁevalだと思っていいと思います。PowerShellからみると普通ですね。ちょっとjq的な答えも考えてみます。

$ (echo 3+4+5+6) -split '\+' | measure -sum | %{$_.Sum}
18

measureMeasure-Objectへのエイリアスです。パイプラインで渡ってきたりしてものをなんかいろいろ数え上げてくれたりします。オプションなしだと個数だけを数えますが、-sumを付けると、総和を計算してくれます。

Q5

echo てぶくろからはじめてろくぶてを出力する問題。普通はrevすればいいですね

$ echo てぶくろ | rev
ろくぶて

でもこれは変態的でないので、なんとか変態的にしていきます

$ echo てぶくろ | grep -o . | sed 's/./"&"/' | paste -sd , | sed -E 's/.+/[&]/' | jq reverse --indent 0 | tr -d ',"[]'
ろくぶて

jqreverseを使って反転させることにしました。jqJSONを扱うツールなのでてぶくろJSONの配列に加工します。

$ echo てぶくろ | grep -o . | sed 's/./"&"/' | paste -sd , | sed -E 's/.+/[&]/'
["","","",""]

これをjq reverseで反転させます

$ echo てぶくろ | grep -o . | sed 's/./"&"/' | paste -sd , | sed -E 's/.+/[&]/' | jq reverse --indent 0
["","","",""]

いらないところをtrで削って終わりですね。

PowerShellでの解答も行きます・・・が、まぁこれは普通だな・・・

$ echo てぶくろ | %{[System.Linq.Enumerable]::Reverse($_) -join ""}
ろくぶて

普通過ぎるので別の方法をとります。せっかくPowerShellでは.NETのクラスが使えるので、それを使ってみたいと思います。

$ (echo てぶくろ) -split "" | % {$st=New-Object System.Collections.Stack} {$st.Push($_)} {$st -join ""}
ろくぶて

System.Collections.Stackです。説明するまでもないですがStackを扱うオブジェクトです。Stackに順番に入れて取り出せば逆順になりますね。

Q6

数字を使わず素数を10個ほど出力する問題。数字を使うなら seq inf | factor | awk 'NF==2{print $NF}' | headこんな感じですね

c++を使ったりしてエラトステネスを実装しようかと思ったんですが、単純に2 3 5 7みたいな感じで出力すればいいんだなあってなりました。もちろん数字のところは難読化とかが必要ですが。頭…柔らかくしていこうな!

Q7

数字を使わずに0~9を出力する問題。普通なら seq 0 9でいいですね

$ seq $? $((-~-~-~-~-~-~-~-~($$/$$)))
0
1
2
3
4
5
6
7
8
9

$?は前のコマンドの終了値です。前のコマンドが正常終了していれば$?は0になります。$((-~-~-~-~-~-~-~-~($$/$$)))はややこしく見えますが、単項演算子を連続でつけているだけです。-は負に、~はビット反転です。$$/$$のうち$$はプロセス番号です。プロセス番号をプロセス番号で割ると1なのでこの式は-~-~-~-~-~-~-~-~1を評価することになります。この結果は9になるので、seq 0 9が評価されますね

別解も用意しました。これは前回の勉強会で出た問題の再利用です。詳しくは説明しませんが、スクリプト再帰している感じですね。

$ echo 'echo $((SHLVL-$$/$$)); bash -c $'$? > x && chmod +x x && ./x | head

PowerShellでの解答も行きます

$ [int]!$?..("やっほーうんこだよ").Length
0
1
2
3
4
5
6
7
8
9

やっほーうんこです。せっかくPowerShellなので、PowerShellにあるものでやってみました。

$ 0..9
0
1
2
3
4
5
6
7
8
9

これで範囲内の数値を出力できます。こいつを数字を使わずにやります。まず0は[int]!$?です。$?bash/zshなどとおなじで前のコマンドの終了ステータスが入っています。正常ならTrueが入っているので、!Falseにしてintにキャストすると0になります。

9のほうは計算で出してもいいんですが、せっかくオブジェクトにメソッドが生えているので使いたいです。ちょうど「やっほーうんこだよ」が9文字なのでこれを使いました。Lengthで長さを取得できます。めでたく9が得られました。

Q8

unkoファイルは以下のようになっています

んこうんこううんうこうんんこうこんんこう
んこここんこうこうんこうこうんんこうんこ
うこううんうここうこんんんうんううんんこ
んんんこここううううんこんこんうこうこう
んこうんんんううこううううこうここうんん
んここうこんうここうんううんこうんこうう
うこうんここここんんうこここここここんこ
うこうこここうんんううううんここうこんう
ここうんうんんんんこうこんううんんこんう
んうううここんここここううこんんんうんう
んこうんんこんううんうんこんんうんこんん
んんうここうこううこううんんうこうこうん
こううこううここんんこんううんうんんんう
こうこうんんんこんこんんううんんんこんこ
んんんこうんうこうここうんこここうんこん

ここからうんこが含まれる行番号だけを出力する問題です。ただし、ワンライナー中に日本語を使ってはいけないとのこと。今回はうんこないと思ったけどやはり

ちなみにどんな感じにうんこが分布しているかはこんな感じ

$ cat unko | grep -n うんこ
1:んこうんこううんうこうんんこうこんんこう
2:んこここんこうこうんこうこうんんこうんこ
4:んんんこここううううんこんこんうこうこう
6:んここうこんうここうんううんこうんこうう
7:うこうんここここんんうこここここここんこ
8:うこうこここうんんううううんここうこんう
11:んこうんんこんううんうんこんんうんこんん
15:んんんこうんうこうここうんこここうんこん

でも「うんこ」を使っているのでダメですね。

$ cat unko | awk "/$(uconv -x hira <<< unko)/{print NR}"
1
2
4
6
7
8
11
15

uconv -x hira <<< unkounkoをひらがな読みに変換します。これはまさかなんですがちゃんと「うんこ」とでます。うれしいですね。というわけでこのワンライナーは以下のようになります

$ cat unko | awk '/うんこ/{print NR}'

はい。

PowerShellでの解答も行きます

$ cat unko | sls ((@(58,135,71) |%{[char]($_+12300)}) -join "") | %{$_.LineNumber}
1
2
4
6
7
8
11
15

うむ・・・。なんか面白い文字の難読化を見つけたいですね

LT

ocsというopyのC#版みたいなのを用意していたんですが、いろいろ事故ってLTできませんでした。モノ自体は完成しているので、そのうちブログ書きますね

終わりに

今回は問題としては簡単だったのか、いろんな解答が見られてとても面白かったです。ただなんかやっぱり疲労感は感じますね。次回も楽しみです!!!開催ありがとうございました!!!

第48回シェル芸勉強会に参加しました

しました

今回は前回に続きリモート開催ということで、おうちからYouTube越しに参加。サテライトとは違い自分のうめき声しか聞こえないのでちょっと寂しいですね

問題と解答例はここ。今回はbashというかプロセスが云々の問題が多いようなので、PowerShellの解答はすくなめ

Q1

以下のようなスクリプトlinenoがあります

echo $LINENO

これを使って、呼び出し元のLINENOを出力する問題。

そもそも$LINENOは、行番号が格納されている変数。なので、これを普通に実行すると 1 が出力されて終了する。コマンドラインからecho $LINENOすると、現在操作しているシェルの行番号が出力される。これをスクリプトを使って出力したい。

$ eval "$(cat ./lineno)"

はい。読みだしてそれをevalすればよい。cat を使わない方法としては

$ eval "$(< ./lineno)"

と書けるらしい。便利。

Q2

echo ${!BASH*} とすると、BASHで始まる変数が展開される一覧にある BASH_VERSION の中身を出力する問題。

$ echo ${!BASH*} | awk '{print $NF}' | xargs -I@ bash -c "echo \${@}"

最初に答えたのはこれ。$BASH_VERSION は最後に現れるので、$NFxargsechoを作ってbashに渡す。これを別の書き方をすると

$ echo ${!BASH*} > /dev/null && eval "echo \${$_}"

${!BASH*} は展開されるので $_BASH_VERSIONが入る。echo \${$_} という文字列を作る。これはecho ${BASH_VERSION}になるのでevalすれば欲しい文字列が得られる。

$ echo ${!BASH*} > /dev/null && echo ${!_}

TL眺めてていいなあと思ったのはこれ。${!_}で変数関節展開をして出力する。最中は思いつかなかったなぁ。

Q3

sleepをプロセス番号1のプロセスの下に3つぶら下げる問題

これは最中に解けなかった。やり方としては、子プロセスより親プロセスが先に死ぬと、子プロセスが親プロセスの親プロセスにぶら下がるようになるのを利用する。つまり、sleep より先に呼び出した bash が死ぬようにすればいい。

$ ( sleep 100 | sleep 100 | sleep 100 & )

sleep をバックグラウンドで動かしておく。呼び出し元のサブシェルはそのまま終了するのでsleepinitとかにぶら下がるようになる。ということらしい

Q4

今使っている端末上でecho $$して1を出力する問題。$$はプロセス番号を表す。なのでinitとかsystemdになろうって感じかなあ。と思ったらそういうことではないらしい

$ sudo unshare --fork --pid --mount-proc bash -c 'echo $$'

unshareを使って名前空間を分割。そこで bash -c 'echo $$' を実行すると、分割された名前空間bashがプロセス番号1として起動するので echo $$ で1が出力される。名前空間の分割はDockerでも使われている技術。SD誌を読もう!!!!!!

Q5

勝手にプロンプトにunkoと入力されるようにする問題。

なんだこの問題…と思っていたら時間が終わってた。解答としては exec でSTDINを奪うというもの。killしないと止まらないので危険シェル芸

Q6

$SHLVLseqコマンドを作る問題。

$ echo 'echo $SHLVL; bash -c "$0"' > s
$ chmod +x s
$ ./s | head

echo $SHLVLで数字を出しつつ、$0再帰する。うむ

Q7

sleepを三代ぶら下げる問題。Q3の応用

$ ( ( ( sleep 1000 ) & exec sleep 1000 ) & exec sleep 1000 ) &

なんでできたかわからん。簡単に言うと

sleep 100 &
exec sleep 100

というようなシェルスクリプトを実行すると

bash
 + sleep
    + sleep 

という風になる。このうち真ん中のsleepexecbashからsleepに変えられたもの。これを三段にするなら、これを呼び出すやつが呼び出したあと、exec sleepすればよい

bash -c 'sleep 100 & exec sleep 100' &
exec sleep 100

これでOK。これをサブシェルを使ってワンライナーにしたのが上の解答ということ

Q8

プロセスツリーで二分木を作る問題。forkbombに怯えてしまったのでやらなかったけど、実はこの問題が一番シェル芸っぽい問題とのこと

二つのプロセスを作るには

(bash|bash)

とすればいい。これを繰り返せばいいので以下のようにする

$ echo bash{,,,} | sed 's/bash/(&|&)/g' | sed 's/ /|/g;asleep 1000'
(bash|bash)|(bash|bash)|(bash|bash)|(bash|bash)
sleep 1000

これを bash に評価させる。この時&しておく。

$ echo bash{,,,} | sed 's/bash/(&|&)/g' | sed 's/ /|/g;asleep 1000' | bash &
$ ps --forest
  339 pts/0    00:00:00  \_ bash
  400 pts/0    00:00:00      \_ bash
  401 pts/0    00:00:00      |   \_ bash
  403 pts/0    00:00:00      |   |   \_ bash
  413 pts/0    00:00:00      |   |   |   \_ sleep
  404 pts/0    00:00:00      |   |   \_ bash
  402 pts/0    00:00:00      |   \_ bash
  405 pts/0    00:00:00      |   |   \_ bash
  407 pts/0    00:00:00      |   |   \_ bash
  406 pts/0    00:00:00      |   \_ bash
  410 pts/0    00:00:00      |   |   \_ bash
  411 pts/0    00:00:00      |   |   \_ bash
  408 pts/0    00:00:00      |   \_ bash
  409 pts/0    00:00:00      |       \_ bash
  412 pts/0    00:00:00      |       \_ bash

おお、forkbombに怯えることなく二分木が出来た。すごいぜこれは

LT

今回は参加しませんでした。配信設備がない…ぐぬぬ。まあネタもなかったんですが…

LTをしてくださったお二人はどちらも超良かったのでぜひアーカイブをご覧ください。

終わりに

前回はフル参加できなかったので久しぶりのシェル芸勉強会でした。いつもとは少し毛色が違う問題があってとても楽しかったです!ありがとうございました!!!

雑記 2020-05-06

雑記

動かした方が痛くないなぁ

置き配

はじめて置き配を経験した。8時ぐらいに目覚めてメールを確認したら、「置き配しました☆ミ」というメッセージが写真付きで届いていた。その写真には我が家の玄関に荷物が置いてある様が写っていた。すげー時代だなぁと思った。

Surface3のOSをWindowsにもどした

もどした。このために16GBのUSBメモリも購入した。まぁ前から欲しかったからいいのだけど。
Windowsに戻した感想は、やはりというかなんというかWindows機だよな!って感じ。Linuxを入れていた時はWi-Fiがすぐ死んだり、バッテリー情報が取得できなかったり、ゴーストタッチが発生したりしてたんだけど、そういうこともなく元気に動いてくれて私は嬉しい。用途はドット絵とYouTubeだからセットアップもすぐ終わった。セキュアブートは戻してないのでそれだけはやっておかないとね。

ところでこれはもうスペックの問題だと思うんだけど、TweetDeckがめちゃめちゃ重い。Twitter離れが加速する。新しいSurfaceが発売されたら買おうかなぁ…

アニメを見た

アニメを見るときってめちゃくちゃエネルギーを使うので、普段は見ない。見ても1本だが、それはプリキュアに割いているので他のは見ることができない。ところが、このGWは手首が痛いWeekだったので、手を動かさずできることとしてアニメを見ようとなった。選んだのは完全未履修のエヴァバビロニアエヴァYouTubeで公開されている分。バビロニアAmazon Primeでみた。エヴァはよくわからなかった。バビロニアは金ぴかがカッコよかった。ただどちらも作中でめちゃめちゃ人が死ぬので、次はゆるふわなアニメにしたいと思う。

おわり

なんだよGW…はは…まだ始まったばかりだろ…?な?いかないでくれよ…なぁ…!

雑記 2020-05-01

雑記

いてて

腱鞘炎

仕事柄というか趣味のせいだかわからないけど腱鞘炎になりました。こまったなぁ

家具屋

近くに家具屋がある。部屋に照明がなかったので買いに行った。ついでにキッチンマットとかメタルラックなんかを買い足した。当然車なんて高価なものは持っていないから持ち帰りは手。重いからしんどいというより、箱がでかすぎて持てないっていうね

おわり

owari