全力で怠けたい

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

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

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

問題はすごく工夫してあってパズル感覚で解けて楽しいしシェル力を養うのにも役立ちそう。

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

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

はじめに

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

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

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

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

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

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

少し前に第10回シェル芸勉強会の問題に挑戦してみたので今回は2つあとの 第12回シェル芸勉強会 の問題に挑戦してみた。 実は第10回シェル芸勉強会のあとは第11回シェル芸勉強会が開催しているようだけど、問題がスライドでしか公開してなくて入力データとかを転記するのが手間がかかりそうなのでやってない。

Q1

Question

次のように、画面にバッテンを描いてください。(この出力例の大きさは21x21です。)

x x
 x x 
 x x 
 x x 
 x x 
 x x 
 x x 
 x x 
 x x 
 x x 
 x 
 x x 
 x x 
 x x 
 x x 
 x x 
 x x 
 x x 
 x x 
 x x 
x x

Answer

大きさが決まってるのでベタに sed で特定行を編集してみた。

$ seq 21 | sed 's/.*/x x/' | sed '2,20 s/^/ /' | sed '11 s/x.*/x/'
x x
 x x
 x x
 x x
 x x
 x x
 x x
 x x
 x x
 x x
 x
 x x
 x x
 x x
 x x
 x x
 x x
 x x
 x x
 x x
x x

Q2

Question

小問1

次のechoの出力から回文を完成させてください。

ueda@remote:~$ echo たけやぶ
###このようにワンライナーで出力を作る###
ueda@remote:~$ echo たけやぶ | ...
たけやぶやけた

小問2

次のファイルの各行について回文を完成させてください。

ueda@remote:~/tmp$ cat kaibun 
たけやぶ
わたしまけ

Answer

小問1

awk で元の文字列を print してから元の文字列の長さ - 1から文字列の先頭まで逆順に print してみた。

$ echo たけやぶ | sed 's/./& /g' | awk '{printf $0}{for(i=NF-1;i>0;i--){printf $i}}' | tr -d ' '
たけやぶやけた

別解。awk だけでゴリゴリとやってみた。

$ echo たけやぶ | awk '{printf $0}{for(i=length-1;i>0;i--){printf substr($0, i, 1)}}'

小問2

小問1 のやつを while で回してみた。

$ cat kaibun | while read f; do echo $f | sed 's/./& /g' | awk '{printf $0}{for(i=NF-1;i>0;i--){printf $i}}' | tr -d ' '; echo ''; done;
たけやぶやけた
わたしまけましたわ

Q3

個人的には第11回シェル芸勉強会の問題のなかで一番難しかったかも。 南武線の駅名のリストをネットから探すのが大変だった。

Question

ウェブ等からデータを取得して南武線の駅名のリストを作ってください。

Answer

南武線の駅名のリストは Wikipedia を使わせてもらった。 sed で HTML をゴリゴリと編集してみた。

$ cat station | \
        grep '<td>' | \
        grep -oE 'title="([^駅"]+駅)"' | \
        sed -n '/川崎/,$p' | \
        sed -n '1,/立川/p' | \
        sed 's/title="//' | \
        sed 's/"//'
川崎駅
京急川崎駅
尻手駅
矢向駅
鹿島田駅
平間駅
向河原駅
武蔵小杉駅
武蔵中原駅
武蔵新城駅
武蔵溝ノ口駅
溝の口駅
津田山駅
久地駅
宿河原駅
登戸駅
中野島駅
稲田堤駅
京王稲田堤駅
矢野口駅
稲城長沼駅
南多摩駅
府中本町駅
分倍河原駅
西府駅
谷保駅
矢川駅
西国立駅
立川駅

Q4

こちらもネットからデータを持ってくるやつ。

Q3 よりは情報は見つけやすかったけどデータを HTML とか PDF で公開するの本当にやめてほしい。できれば JSON せめて XMLCSV で公開してほしい。

Question

北から順(正確には都道府県番号順)に並べてください。

ueda@remote:~/tmp$ cat pref 
鹿児島県
青森県
大阪府
群馬県

Answer

総務省CSV を公開してるのを使わせてもらった。CSV ファイル一つだけで総務省が好きになった。

総務省が公開してる CSV はこんな感じのフォーマットになってる。

ken-code,sityouson-code,tiiki-code,ken-name,sityouson-name1,sityouson-name2,sityouson-name3,yomigana
1,0,1000,北海道,,,,ほっかいどう
1,100,1100,北海道,札幌市,,,さっぽろし
1,101,1101,北海道,札幌市,,中央区,ちゅうおうく

総務省が公開してる CSV は文字エンコーディングShift_JIS なのが惜しいけど nkfUTF-8 に変換していろいろやってる。

$ curl -s https://www.soumu.go.jp/main_content/000608358.csv | \
        nkf -Sw | \
        grep -f pref | \
        awk -F, '{print $4}' | \
        uniq
青森県
群馬県
大阪府
鹿児島県

別解。国土交通省が公開してる やつ を使わせてもらった。

国土交通省が公開してるやつはこんな感じの HTML になってて機械にやさしくない作りになってる。 ちなみにタイトルは <title></title> になってて機械だけじゃなくて人間にもやさしくない。

  <tr> 
    <td>01</td><td>北海道</td>
  <td>25</td><td>滋賀県</td>
</tr>

sed でゴリゴリと編集してみた。

$ curl https://nlftp.mlit.go.jp/ksj/gml/codelist/PrefCd.html | \
        grep -f pref | \
        grep '<td>' | \
        sed -E 's@</?td>@ @g' | \
        awk '{print $1, $2}' | \
        sort | \
        awk '{print $2}'

Q5

Question

各行の数字を大きい順にソートしてください。

ueda@remote:~/tmp$ cat input 
A 31 1234 -42 4
B 10 31.1 -34 94

Answer

一番左の A と B がソートしないように大きな値にしておいて各列を行にして sort してみた。

$ cat input | while read f; do echo $f | awk '$1="99999999" $1{print}' | sed 's/ /\n/g' | sort -nr | tr '\n' ' ' | sed 's/99999999//'; echo ''; done
A 1234 31 4 -42
B 94 31.1 10 -34

Q6

Question

次のファイルについてグラフを作ってください。

ueda@remote:~/tmp$ cat num 
5
3
4
10
2

このような出力を作ります。

 5 *****
 3 ***
 4 ****
10 **********
 2 **

Answer

awk でベタにやってみた。

$ cat num | awk '{printf("%2d ", $1)}{for(i=0;i<$1;i++){printf("*")}print ""}'

Q7

Question

Q6のグラフを次のように縦にしてください。 (多少ズレてもよしとします。)

 * 
 * 
 * 
 * 
 * 
* * 
* * * 
* * * * 
* * * * *
* * * * *
5 3 4 10 2

Answer

なぜか解く気力がわかなかったので解答例を記載しておく。

ueda@remote:~/tmp$ cat num | 
awk '{printf $1" ";for(i=0;i<$1;i++){printf "* "}
for(i=$1;i<=15;i++){printf "_ "};print ""}' |
 awk '{for(i=1;i<=NF;i++){a[NR,i]=$i}}
END{for(i=1;i<=15;i++)
{for(j=1;j<=NR;j++){printf a[j,i]" "}print ""}}' | 
tac | sed -n '/\\*/,$p' | tr _ ' '

Q8

次のデータは、何かの試合の結果ですが、各チームが何勝何敗だったかを集計してください。引き分けは無いと仮定して構いません。

ueda@remote:~/tmp$ cat result 
A-B 1-2
B-A 3-1
C-A 1-0
B-C 5-4
C-B 2-1

Question

Answer

awk でベタベタにやってみた。

$ cat result | sed 's/-/ /g' | awk '{if($3>$4){w[$1]++; l[$2]++}else{w[$2]++; l[$1]++}}END{for(k in w){t[k]=k}; for(k in l){t[k]=k}; for(k in t){printf("%s %d win, %d lose \n", k, w[k], l[k])}}'
A 0 win, 3 lose
B 3 win, 1 lose
C 2 win, 1 lose

参考サイト

pixela4go が v1.4.0 にバージョンアップしました。

pixela4go が v1.4.0 にバージョンアップしました。

v1.4.0 は context に対応するバージョンアップです。

v1.4.0 アップデート内容

context に対応

context に対応しています。

context に対応しているメソッドは WithContext サフィックスがメソッド名に付いてます。たとえば既存のメソッド名が Create() なら context に対応しているメソッドは CreateWithContext() で、既存のメソッドが Update() なら UpdateWithContext() です。

context に対応しているメソッドはこんな感じで使います。

ctx, cancel := context.WithTimeout(context.Background(), 1 * time.Second)
defer cancel()
result, err := client.User().CreateWithContext(ctx, uci)

既存のメソッドは引き続き使えます。 既存のメソッドは context に対応しているメソッドのラッパーになっています。

つまりこのコードは

result, err := client.User().Create(uci)

このコードと同じように動きます。

result, err := client.User().CreateWithContext(context.Background(), uci)

現場からは以上です。

sort コマンドでバージョン番号をソートするメモ。

sort コマンドでバージョン番号をソートするやり方のメモ。

このメモは Ubuntu 20.04.2 LTS の sort コマンドと macOS の sort コマンドを使ってる。

Ubuntu と sort コマンドのバージョン。

$ cat /etc/os-release | grep -w VERSION
VERSION="20.04.2 LTS (Focal Fossa)"

$ sort --version
sort (GNU coreutils) 8.30
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://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.

Written by Mike Haertel and Paul Eggert.

macOS と sort コマンドのバージョン。

$ sw_vers | grep Product
ProductName:    macOS
ProductVersion: 11.2.3

$ sort --version
2.3-Apple (106)

sort コマンドでバージョン番号をソートする

バージョン番号がバラバラに入ってるデータをバージョン番号でソートすることがときどきある。 こんな感じのバージョン番号がファイルに入ってたら 1.0.0, 1.9.0, 1.10.0 の順にソートしたいけど、単純に sort コマンドでソートすると 1.1.0, 1.10.0, 1.9.0 みたいに 1.10.01.9.0 よりも前にきてしまう。

$ cat versions.txt
1.0.0
1.10.0
1.9.0

$ cat versions.txt | sort
1.0.0
1.10.0
1.9.0

解決策: -V, --version-sort オプションを指定する

sort コマンドは -V オプションか --version-sort オプションを指定すると入力データを自然なバージョン番号の順にソートする。らく。

$ cat versions.txt | sort --version-sort
1.0.0
1.9.0
1.10.0

sort コマンドはバージョン番号が rc とか alpha を含むときには無力

sort コマンドの -V オプションと --version-sort オプションはバージョン番号が rc とか alpha みたいなプレリリースのバージョンを含んでいるときはちゃんとソートできない。

$ cat versions.txt
1.0.0
1.0.0-beta.1
1.0.0-rc.1
1.0.0-alpha.1

$ cat versions.txt | sort --version-sort
1.0.0
1.0.0-alpha.1
1.0.0-beta.1
1.0.0-rc.1

# こんな感じにソートしてほしい
# 1.0.0-alpha.1
# 1.0.0-beta.1
# 1.0.0-rc.1
# 1.0.0

MySQL 5.7 を EC2 にインストールする手順のメモ。

何番煎じかとは思うけど MySQL 5.7 を EC2 にインストールする手順をメモしておく。

EC2 は Amazon Linux 2 AMI を使ってる。

$  cat /etc/system-release
Amazon Linux release 2 (Karoo)

MySQL 5.7 を EC2 にインストールする手順

MySQL 5.7 を EC2 にインストールすることがちょいちょいあるけど毎回微妙に手順を忘れてたり調べたりして面倒くさいので手順をメモしておく。

以後の手順は EC2 にログインしてやっていく。

前準備

とりあえず yum update コマンドを実行してあと mariadb は削除しておく。

$ sudo yum update -y
$ sudo yum remove -y mariadb-libs

MySQLリポジトリyum に追加

yum install コマンドで MySQLリポジトリyum に追加していく。

MySQLリポジトリの URL は https://dev.mysql.com/get/<rpm ファイル名> になるのだけどこの URL の調べ方を書いておく。

rpm のファイル名は https://dev.mysql.com/downloads/repo/yum/Red Hat Enterprise Linux 7 / Oracle Linux 7 (Architecture Independent), RPM Package のところつまり RHEL7 のダウンロードボタンを押すとログインするかサインアップするかのページが表示するので、ページの下のほうの No thanks, just start my download. のリンク先が MySQLリポジトリの URL になる。

$ sudo yum install -y https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm

$ yum list installed | grep mysql
mysql80-community-release.noarch      el7-3                            installed

MySQL 5.7 を有効にする

MySQLリポジトリyum に追加するとデフォルトは MySQL 8.0 が有効になっているので sudo yum-config-manager コマンドで MySQL 8.0 は無効にして MySQL 5.7 を有効にしていく。

$ yum repolist all | grep -E "mysql[0-9]+-community/x86"
mysql55-community/x86_64             MySQL 5.5 Community Server     無効
mysql56-community/x86_64             MySQL 5.6 Community Server     無効
mysql57-community/x86_64             MySQL 5.7 Community Server     無効
mysql80-community/x86_64             MySQL 8.0 Community Server     有効:    229

$ sudo yum-config-manager --disable mysql80-community
$ sudo yum-config-manager --enable mysql57-community

$ yum repolist all | grep -E "mysql[0-9]+-community/x86"
mysql55-community/x86_64             MySQL 5.5 Community Server     無効
mysql56-community/x86_64             MySQL 5.6 Community Server     無効
mysql57-community/x86_64             MySQL 5.7 Community Server     有効:    484
mysql80-community/x86_64             MySQL 8.0 Community Server     無効

MySQL 5.7 のインストールと起動

yum install コマンドで MySQL をインストールして systemctl start mysqld コマンドで mysqld を起動する。 あと systemctl enable mysqld コマンドで mysqld がシステム起動時に起動するようにしておく。

$ sudo yum install -y mysql-community-server

$ sudo systemctl start mysqld
$ systemctl status mysqld.service
● mysqld.service - MySQL Server
   Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)
   Active: active (running) since 土 2021-04-03 05:52:47 UTC; 13s ago
     Docs: man:mysqld(8)
           http://dev.mysql.com/doc/refman/en/using-systemd.html
  Process: 3895 ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid $MYSQLD_OPTS (code=exited, status=0/SUCCESS)
  Process: 3846 ExecStartPre=/usr/bin/mysqld_pre_systemd (code=exited, status=0/SUCCESS)
 Main PID: 3900 (mysqld)
   CGroup: /system.slice/mysqld.service
           └─3900 /usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid

$ sudo systemctl enable mysqld

MySQL のバージョンを確認しておく。 mysql --version コマンドを実行すると 5.7.33 が表示しててちゃんと MySQL 5.7 が動いているよう。

$ mysql --version
mysql  Ver 14.14 Distrib 5.7.33, for Linux (x86_64) using  EditLine wrapper

root ユーザーのデフォルトのパスワードの調べ方

MySQL の root ユーザーのデフォルトのパスワードは MySQL のログに記載しているのでログを見たら root ユーザーのパスワードが分かる。

$ sudo grep 'temporary password' /var/log/mysqld.log
2021-04-03T05:52:45.375037Z 1 [Note] A temporary password is generated for root@localhost: LTaGlt2Q?>u3

あとは mysql コマンドで MySQL に接続して適当にパスワードを変えておく (間違ってもこの例みたいな適当なパスワードにしてはいけない)

$ mysql -uroot -p'LTaGlt2Q?>u3'

mysql> set password for root@localhost='abCD1234@';
Query OK, 0 rows affected (0.00 sec)

この記事で書いたやつを全部やるスクリプト

この記事で書いたやつを全部やるスクリプトを Gist に置いといた => https://gist.github.com/ebc-2in2crc/d5c0976b4f039764e780049822acc329

こんな感じで sh に流し込んだら全部やってくれる。

$ curl https://gist.githubusercontent.com/ebc-2in2crc/d5c0976b4f039764e780049822acc329/raw/4b44907d376e95843bdd635021761be73f56c5b3/install-mysql5.7-on-ec2.sh | sh

以上。

参考サイト

Docker のコンテナ ID だけを表示する --quiet オプションを知ったメモ。

Docker のコンテナ ID だけを表示する --quiet オプションを知ったメモ。

Docker のコンテナ ID だけを表示する

Docker のコンテナ ID だけを表示する --quiet オプションを知ったのでメモしておく。

Docker のコンテナはコンテナ ID を指定して stop したり restart することが多いけどコンテナ ID を調べようとして docker container ls コマンドを実行するとコンテナ ID 以外にもいろいろ情報が表示する。 対話的にやってるときはそれでいいけどシェルスクリプトとかはコンテナ ID 以外は必要としないのでちょろっと工夫してコンテナ ID だけ取得しているのだけど docker container ls コマンドのヘルプを見てたらそのへんの工夫は全然必要ないことが分かったのでメモしておく。

最初に ls するコンテナを起動しておく。

$ docker container run --name my-httpd -dit -p 8080:80 httpd
f5e4bb31377d45d6478444480f6b6503e692536151a80bbc3cac606b7f63f2e7

普通に docker container ls コマンドを実行するとコンテナ ID 以外にもいろいろ情報が表示する。

$ docker container ls
CONTAINER ID   IMAGE     COMMAND              CREATED         STATUS         PORTS                  NAMES
f5e4bb31377d   httpd     "httpd-foreground"   3 seconds ago   Up 3 seconds   0.0.0.0:8080->80/tcp   my-httpd

自分はだいたいはコンテナ ID だけ取得するのでコンテナ名で grep して awk で1つめのフィールドだけ表示する、というかしてた。

$ docker container ls | grep my-httpd | awk '{print $1}'
f5e4bb31377d

docker container ls コマンドの --filter オプションを指定すると grep する必要がなくなって --quiet オプションを指定すると awk する必要がなくなり、タイプ数も少し少なくなる。

$ docker container ls --filter "name=my-httpd" --quiet
f5e4bb31377d

docker container ls コマンドの --filter オプションと --quiet オプションはショートオプションが用意されているのでそれぞれショートオプションの -f-q を指定するとさらにタイプ数が少なくなる。らく。

$ docker container ls -q -f "name=my-httpd"
f5e4bb31377d

以上。

入力行を指定幅で折り返す fold コマンドの使い方のメモ。

入力行を指定幅で折り返す fold コマンドの使い方のメモ。

このメモは Ubuntu 20.04.2 LTS の Docker コンテナ上の fold コマンドを使ってる。

Ubuntu, bash, fold コマンドのバージョン。

$ cat /etc/os-release | grep -w VERSION
VERSION="20.04.2 LTS (Focal Fossa)"

$ bash --version
GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 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.

$ fold --version
fold (GNU coreutils) 8.30
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://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.

あと macOS でデフォルトでインストールされている fold コマンドを使う箇所もある。

macOSbash のバージョン。

$ sw_vers | grep Product
ProductName:    macOS
ProductVersion: 11.2.3

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

fold コマンドの使い方

fold コマンドは入力行を指定幅で折り返すコマンド。

自分は普段そんなに使わないんだけど圧倒的に不足しているシェル力を養うために シェル芸勉強会 の問題集を解くときに使ったりするので使い方をメモしておく。

--help オプション: ヘルプ

--help オプションを指定すると fold コマンドの使い方を表示する……んだけど man fold が使えないときにしか使わない。

$ fold --help
Usage: fold [OPTION]... [FILE]...
Wrap input lines in each FILE, writing to standard output.

With no FILE, or when FILE is -, read standard input.

Mandatory arguments to long options are mandatory for short options too.
  -b, --bytes         count bytes rather than columns
  -s, --spaces        break at spaces
  -w, --width=WIDTH   use WIDTH columns instead of 80
      --help     display this help and exit
      --version  output version information and exit

GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
Report fold translation bugs to <https://translationproject.org/team/>
Full documentation at: <https://www.gnu.org/software/coreutils/fold>
or available locally via: info '(coreutils) fold invocation'

基本的な使い方

基本的には fold <ファイル> みたいにして使う。

$ cat message.txt 
0123456789

$ fold -w 2 message.txt 
01
23
45
67
89

fold - みたいに <ファイル> のところを - にするとファイルから読み込むのではなくて標準入力から読み込む。

$ echo '0123456789' | fold -w 2 -
01
23
45
67
89

<ファイル> のところは何も指定しなかったら標準入力を読み込む。 先行するコマンドの出力をパイプで fold コマンドに接続するなら fold - みたいにするよりも fold みたいにしちゃうことが多い気がする。

$ echo '0123456789' | fold -w 2
01
23
45
67
89

-w, --width オプション: 指定幅で折り返す

fold コマンドはデフォルトは 80 の幅で入力を折り返すけど -w オプションか --width オプションを指定すると入力を指定幅で折り返す。 ちなみに macOS にインストールされている BSD 版の fold コマンドはショートオプションの -w オプションはサポートしてるけどロングオプションの --width オプションはサポートしていない。

入力幅が10でデフォルト幅の80に収まるので折り返さない。

$ echo '0123456789' | fold
0123456789

入力幅が10で -w 10 の幅の10に収まるので折り返さない。

$ echo '0123456789' | fold -w 10
0123456789

入力幅が10で -w 9 の幅の9に収まらないので収まらない分の 9 が折り返す。

$ echo '0123456789' | fold -w 9
012345678
9

入力幅が10で -w 3 の幅の3に収まらないので収まらない分の各行がそれぞれ3の幅で折り返す。

$ echo '0123456789' | fold -w 3
012
345
678
9

-s, --spaces オプション

fold コマンドはデフォルトは単に幅で入力を折り返すけど -s オプションか ---spaces オプションを指定すると空白を考慮して折り返す。 英語の文章とかを読むときに便利かもしれない。 ちなみに macOS にインストールされている BSD 版の fold コマンドはショートオプションの -s オプションはサポートしてるけどロングオプションの --spaces オプションはサポートしていない。

入力幅が5で -w 5 の幅の5に収まらないので折り返すけど、 345 の途中と 678 の途中で折り返しちゃう。

$ echo '012 345 678 9' | fold -w 5
012 3
45 67
8 9

入力幅が5で -w 5 の幅の5に収まらないので折り返すけど、 -s を指定してるので 345 の途中では折り返さないで 345 の次の空白で折り返す。678 も同じようになる。

$ echo '012 345 678 9' | fold -w 5 -s
012 
345 
678 9

指定した幅よりも長い単語があるときは単語の途中でも折り返す。

$ echo '012' | fold -w 2 -s
01
2

-b, --bytes オプション: 幅じゃなくてバイト数で折り返す

fold コマンドはデフォルトは幅で入力を折り返すけど -b オプションか ---bytes オプションを指定するとバイト数で折り返す。 ちなみに macOS にインストールされている BSD 版の fold コマンドはショートオプションの -b オプションはサポートしてるけどロングオプションの --bytes オプションはサポートしていない。

タブ文字を含む文字列を fold コマンドで折り返すとこんな感じになる。

$ echo $'01234\t56789' | fold -w 4
0123
4
    
5678
9

-b オプションを指定すると 0123 の4バイトで折り返して $'4\t56' の4バイトで折り返す。

$ echo $'01234\t56789' | fold -w 4 -b
0123
4   56
789

タブ文字はいろいろ扱いにくさがあると思うのでタブそのものに意味がないなら適当にスペースに変換しちゃってもいいと思う。

expand コマンドでタブ文字をスペース1つに展開しちゃうとか。

$ echo $'01234\t56789' | expand -t 1 | fold -w 4
0123
4 56
789

tr コマンドでタブ文字をスペース1つに置換しちゃうとか。

$ echo $'01234\t56789' | tr $'\t' ' ' | fold -w 4
0123
4 56
789

以上。

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

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

問題はすごく工夫してあってパズル感覚で解けて楽しいしシェル力を養うのにも役立ちそう。

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

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

はじめに

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

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

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

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

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

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

少し前に第10回シェル芸勉強会の問題に挑戦してみたので今回は1つ前の第9回シェル芸勉強会の問題に挑戦してみた。

Q1

Question

まず、次のようにファイルを作ってください。

$ touch apple avocado banana cinnamon melon
$ ls
apple avocado banana cinnamon melon

「a,b,c,m」というディレクトリを作って、1文字目が対応するファイルをそれぞれのディレクトリに移動してください。

###こうなったらOK###
$ ls *
a:
apple avocado
 
b:
banana
 
c:
cinnamon
 
m:
melon

Answer

cut -c1 で単語の1文字目を強引に抜き出してるけどあとで解答例を見たら ${f:0:1} みたいに Bash のパラメーター展開を使っててスマートな感じだった。

$ ls | while read f; do d=$(echo $f | cut -c1); mkdir -p $d; mv $f $d; done
$ ls *
a:
apple    avocado

b:
banana

c:
cinnamon

m:
melon

Q2

Question

まず、次のように名前にスペースが入ったファイルを作ります。

$ touch "私は 蟹" "オシャレな 蟹" "足が 10本"
$ ls -l
total 0
-rw-r--r-- 1 ueda staff 0 2 14 11:22 私は 蟹
-rw-r--r-- 1 ueda staff 0 2 14 11:22 足が 10本
-rw-r--r-- 1 ueda staff 0 2 14 11:22 オシャレな 蟹

このままでは何かと扱いづらいので、間にアンダーバーを入れて次のように名前を変更してください。

$ ls -l
total 0
-rw-r--r-- 1 ueda staff 0 2 14 11:25 私は_蟹
-rw-r--r-- 1 ueda staff 0 2 14 11:25 足が_10本
-rw-r--r-- 1 ueda staff 0 2 14 11:25 オシャレな_蟹

Answer

$ ls | while read f; do mv "$f" "$(echo $f | tr ' ' '_')"; done

Q3

Question

ディレクトリを適当に作って、20140101から20141231まで、日付に対応したファイルを作って下さい。各ファイルの中には各日付に対応するdateコマンドの出力を書き込んで下さい。

ワンライナーが思いつかない場合は、とりあえず手作業でやってみてください。)

###こんな感じでどうぞ###
uedambp:20140214USPSTUDY ueda$ ls -l | head
total 1460
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140101
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140102
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140103
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140104
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140105
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140106
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140107
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140108
-rw-r--r-- 1 ueda staff 28 2 14 10:23 20140109
uedambp:20140214USPSTUDY ueda$ cat 20140101
水 1 1 00:00:00 JST 2014

Answer

Mac の date コマンドはつらみがあるので Virtual Box 上の Ubuntu 20.04.2 LTS で解いた。

これ date コマンドで日付を相対指定するのがスマートだと思うんだけど 20140101 から 20141231 まで数値のまま繰り返し処理して強引に日付を作ってみた。

$ seq 0 $(echo '20141231-20140101' | bc) | awk '{print 20140101+$1}' | while read d; do date -d $d && date -d $d > $d; done

Q4

Question

次のように4個ファイルを作って、a_ramenとa_curry、b_appleとb_tomatoのファイルの中身を入れ替えてください。

$ echo カレー > a_ramen
$ echo ラーメン > a_curry
$ echo トマト > b_apple
$ echo リンゴ > b_tomato
###(余談)各ファイルと中身は次のようにgrepで確認できる###
$ grep "" *
a_curry:ラーメン
a_ramen:カレー
b_apple:トマト
b_tomato:リンゴ

Answer

少しちょろっと考えたけど分からなかったので解答例を見た。 解答例を分解しながら少しずつ書いてみる。

grep "" * でそれぞれのファイルとファイル内容の組み合わせを作って、

$ grep "" * 
a_curry:ラーメン
a_ramen:カレー
b_apple:トマト
b_tomato:リンゴ

tr ':' ' ': を削除して、

$ grep "" * | tr ':' ' '
a_curry ラーメン
a_ramen カレー
b_apple トマト
b_tomato リンゴ

xargs -n 4a_* のファイル同士の組み合わせを作って、

$ grep "" * | tr ':' ' ' | xargs -n 4
a_curry ラーメン a_ramen カレー
b_apple トマト b_tomato リンゴ

awk '{print $1,$4,$3,$2}' でファイルとファイル内容の組み合わせを入れ替えて、

$ grep "" * | tr ':' ' ' | xargs -n 4 | awk '{print $1,$4,$3,$2}'
a_curry カレー a_ramen ラーメン
b_apple リンゴ b_tomato トマト

awk '{print "echo",$2,">",$1}' でコマンド文字列を作って、

$ grep "" * | tr ':' ' ' | xargs -n 4 | awk '{print $1,$4,$3,$2}' |
    xargs -n 2 | awk '{print "echo",$2,">",$1}'
echo カレー > a_curry
echo ラーメン > a_ramen
echo リンゴ > b_apple
echo トマト > b_tomato

コマンド文字列を sh に流し込んで実行する。

$ grep "" * | tr ':' ' ' | xargs -n 4 | awk '{print $1,$4,$3,$2}' |
    xargs -n 2 | awk '{print "echo",$2,">",$1}' | sh
$ grep "" *
a_curry:カレー
a_ramen:ラーメン
b_apple:リンゴ
b_tomato:トマト

Q5

Question

各月ごとにtar.gzファイルにしてください。

###こんな感じで###
uedambp:20140214USPSTUDY ueda$ ls *.tar.gz
201401.tar.gz 201404.tar.gz 201407.tar.gz 201410.tar.gz
201402.tar.gz 201405.tar.gz 201408.tar.gz 201411.tar.gz
201403.tar.gz 201406.tar.gz 201409.tar.gz 201412.tar.gz

Answer

これ質問は元ネタのファイルはどこにあるのかまったく書いてないけど問題の流れから Q3 で作った日付のファイルをやってみた。

$ ls | cut -c1-6 | uniq | xargs -I{} sh -c "tar cvzf {}.tar.gz {}*"

Q6

Question

次のようなディレクトリ・ファイル操作を行って下さい。

###小問1: ディレクトリを作る###
 ~/a/a/a/.../a/a/ (aが百個)
###小問2: ファイルを作る###
 ~/a/a/a/.../a/a/b (aが百個、bはファイル)
###小問3: ~/a/a/a/.../a/a/の底に移動###
uedambp:a ueda$ pwd
/Users/ueda/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a

Answer

小問1

$ seq 100 | sed 's/.*/a/' | tr '\n' '/' | xargs mkdir -p

小問2

$ seq 100 | sed 's/.*/a/' | tr '\n' '/' | xargs -I{} touch {}b

小問3

$ find . -name a | tail -n 1 | while read f; do cd $f; done

Q7

Question

先ほど作ったファイルbを、50番目のaディレクトリに移動して下さい。

↓うまくできたかどうかの確認方法

###~から50番目のaに移動###
uedambp:~ ueda$ for a in {1..50} ; do cd a ; done
###bがあるか確認###
uedambp:a ueda$ pwd
/Users/ueda/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a
uedambp:a ueda$ ls
a b
###さらに50個aを下る###
uedambp:a ueda$ for a in {1..50} ; do cd a ; done
uedambp:a ueda$ pwd
/Users/ueda/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
/a/a
###なにもない###
uedambp:a ueda$ ls
uedambp:a ueda$ 

Answer

少しちょろっと考えたけど分からなかったので解答例を見た。

こういう問題を考える人って頭がいいなーって思う。

$ seq 150 | sed 's/100/&\nb ./' | sed -E 's/^[0-9][0-9]*/a/' | tr '\n' '/' | sed 's/^/mv /' | sh

$ for a in {1..50} ; do cd a ; done
$ pwd
/path/to/shell-gei-study/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
$ ls
a/ b

$ for a in {1..50} ; do cd a ; done
$ pwd 
/path/to/shell-gei-study/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a

$ ls

Q8

Question

先ほど作ったディレクトリについて、rm -rを使わずに~/a以下のディレクトリを消去してください。

Answer

問題自体はそんなに難しくないけどディレクトリをガーッと消す操作をやるのはちょっと怖かった。

$ seq 100 | tail -r | awk '{for(i=0;i<$1;i++){printf("a/")}; print ""}' | sed 's/.*/rm -f &b; rmdir &/' | sh

参考サイト