全力で怠けたい

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

Arch Linux でジョブをスケジュール実行するには systemd のタイマーが使える。

はじめに

Arch Linux でジョブをスケジュール実行するには systemd のタイマーが使える。systemd のタイマーを使ってジョブをスケジュール実行する方法をメモしておく。

自分はジョブをスケジュール実行するときは cron を使うことが多いけど Arch Linux はデフォルトでは cron はインストールされない。 cron は cronie とかの実装があるので自分でインストールしたら使えるようになるけど、systemd のタイマーを使うと簡単にジョブをスケジュール実行することができる。

systemd のタイマーの使い方

systemd のタイマーを使うにはサービスのユニットファイルとタイマーのユニットファイルを作っていく。

とりあえずサービスのユニットファイルは hello world/tmp/helloworld.log に追記して、startend を標準出力に出力するだけのシンプルなものに。

あとサーブスのユニットは有効にする必要はないので [Install] セクションは記載しなくてよい (有効にするのはタイマーのユニットなので)

$ cat /etc/systemd/system/helloworld.service
[Unit]
Description=hello world
RefuseManualStart=no
RefuseManualStop=no

[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c 'echo start; echo "hello world" >> /home/svc1/helloworld.log; echo end'

タイマーのユニットファイルはこんな感じに記載する。 タイマーのユニットファイルは拡張子を .timer にしてサービスのユニットファイルは拡張子を .service にするけど、2つのユニットファイルのベースファイル名は同じにしておく。

OnCalendar=minutely を指定してタイマーが1分ごとに動くようにしている。 タイマーのオプションは systemd.timer(5) に、あとカレンダーイベントとタイムスパンの構文は systemd.time(7) に書いてある。

$ cat /etc/systemd/system/helloworld.timer
[Unit]
Description=Run Hello World minutely

[Timer]
OnCalendar=minutely
Persistent=true

[Install]
WantedBy=timers.target

ユニットファイルを作成したらタイマーのユニットを開始と自動起動を有効にする。

# タイマーのユニットを開始する
$ sudo systemctl start helloworld.timer

# タイマーのユニットの自動起動を有効にする
$ sudo systemctl enable helloworld.timer
Created symlink /etc/systemd/system/timers.target.wants/helloworld.timer → /etc/systemd/system/helloworld.timer.

$ systemctl status helloworld.timer
● helloworld.timer - Run Hello World minutely
     Loaded: loaded (/etc/systemd/system/helloworld.timer; disabled; vendor preset: disabled)
     Active: active (running) since Thu 2021-09-16 21:00:32 JST; 32s ago
    Trigger: n/a
   Triggers: ● helloworld.service

Sep 16 21:00:32 localhost systemd[1]: Started Run Hello World minutely.

/tmp/helloworld.log を見てると1分ごとに hello world が追記していく。 タイマーのユニットがちゃんと1分ごとにサービスのユニットを起動している。

$ tail -f /tmp/helloworld.log
hello world
hello world
hello world
hello world

journalctl コマンドを実行するとサービスのログを表示する。

$ journalctl -e -u helloworld.service
Sep 16 21:01:41 localhost systemd[1]: Starting hello world...
Sep 16 21:01:41 localhost bash[1999]: start
Sep 16 21:01:41 localhost bash[1999]: end
Sep 16 21:01:41 localhost systemd[1]: helloworld.service: Deactivated successfully.
Sep 16 21:01:41 localhost systemd[1]: Finished hello world.

ユニットファイルの設定を変えたら sudo systemctl daemon-reload コマンドを実行して設定を反映するのは普通のサービスと同じ。

$ sudo systemctl daemon-reload

タイマーの一覧を表示する

起動しているタイマーの一覧は systemctl list-timers コマンドを実行して表示する。

$ systemctl list-timers
NEXT                        LEFT         LAST                        PASSED    UNIT                         ACTIVATES  >
Thu 2021-09-16 20:55:00 JST 8s left      Thu 2021-09-16 20:54:05 JST 46s ago   helloworld.timer             helloworld.>
Fri 2021-09-17 00:00:00 JST 3h 5min left Thu 2021-09-16 11:15:00 JST 9h ago    shadow.timer                 shadow.serv>
Fri 2021-09-17 20:20:44 JST 23h left     Thu 2021-09-16 20:20:44 JST 34min ago systemd-tmpfiles-clean.timer systemd-tmp>

3 timers listed.
Pass --all to see loaded but inactive timers, too.

参考サイト

シェルでセマンティックバージョンをソートする Go のコードスニペットのメモ。

たまにシェルでセマンティックバージョンをソートすることがあるので Go でのコードスニペットをメモしておく。

コードはこんな感じ。エラー処理は適当。

package main

import (
    "bufio"
    "fmt"
    "os"
    "sort"

    "github.com/Masterminds/semver"
)

func main() {
    var a []string
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        s := scanner.Text()
        a = append(a, s)
    }
    if err := scanner.Err();  err != nil {
        panic(err)
    }
    if len(a) == 0 {
        return
    }

    versions := make([]*semver.Version, len(a))
    for i, s := range a{
        v, err := semver.NewVersion(s)
        if err != nil {
            panic(err)
        }
        versions[i] = v
    }

    sort.Sort(semver.Collection(versions))
    for _, v := range versions {
        fmt.Println(v)
    }
}

こんな感じで使う。

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

$ cat versions.txt | go run main.go
1.0.0-alpha.1
1.0.0-beta.1
1.0.0-rc.1
1.0.0

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

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

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

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

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

はじめに

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

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

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

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

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

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

だいぶ前だけど第12回シェル芸勉強会の問題に挑戦してみたので今回は 第13回シェル芸勉強会 の問題に挑戦してみた。

Q1

Question

次のようにShift JISのファイルを作り、Shift JISで「きく」と書いてあるファイルを探すワンライナーを考えてください。(答えは「b」ですね。)

uedambp:q1 ueda$ echo あいうえお | nkf -xLws > a
uedambp:q1 ueda$ echo かきくけこ | nkf -xLws > b
uedambp:q1 ueda$ echo さしすせそ | nkf -xLws > c

Answer

「きく」を検索するファイルは Shift_JISエンコーディングしているのを UTF-8 に変換して grep してみた。

$ ls | while read f; do cat $f | nkf -Sw | grep 'きく' >/dev/null && echo $f; done

grep の出力は /dev/null にリダイレクトして捨てるようにしてるけど、解答例を見たら grep-q オプションを使ってた。 便利そうなので覚えておく。

ls | while read f; do cat $f | nkf -Sw | grep -q 'きく' && echo $f; done
b

Q2

Question

次のようにディレクトリa,b,c,dに1,2,...,9というファイルがあります。各ディレクトリ内のファイル数をワンライナーで数えてください。

uedambp:q2 ueda$ tree
.
├── a
│   ├── 1
│   ├── 2
│   └── 3
├── b
│   ├── 4
│   └── 5
├── c
└── d
 ├── 6
 ├── 7
 ├── 8
 └── 9

Answer

$ ls | while read f; do echo "$f $(ls $f | xargs -n1 | wc -l)"; done
a/        3
b/        2
c/        0
d/        4

Q3

Question

今度は次のような配置でファイル1,2,...,9が置かれているときに、ワンライナーでa、cの下のファイルの総数をカウントしてください(ディレクトリを除く)。つまりaなら5個、cなら4個が正解です。

uedambp:q3 ueda$ tree
.
├── a
│   ├── 1
│   ├── 2
│   ├── 3
│   └── b
│   ├── 4
│   └── 5
└── c
 └── d
 ├── 6
 ├── 7
 ├── 8
 └── 9

Answer

find コマンドを -type f オプションを指定してファイルだけ検索して uniq -c コマンドでディレクトリごとのファイルの数を数える感じ。

$ ls | while read f; do find $f -type f | awk -F/ '{print $1}' | uniq -c; done
   5 a
   4 c

こちらは解答例。自分は ls コマンドを余分に実行しちゃってるけど解答例のほうは無駄がなくて分かりやすくてタイプ数も少ない。

$ find . -type f | awk -F/ '{print $2}' | uniq -c
   5 a
   4 c

Q4

Question

まず、次のように8桁日付のファイルを作ります。 曜日別にディレクトリを作り、その中に当該するファイルを放り込んでください。

uedambp:q4 ueda$ seq -w 1 31 | xargs -I@ touch 201401@
uedambp:q4 ueda$ ls
20140101 20140107 20140113 20140119 20140125 20140131
20140102 20140108 20140114 20140120 20140126
20140103 20140109 20140115 20140121 20140127
20140104 20140110 20140116 20140122 20140128
20140105 20140111 20140117 20140123 20140129
20140106 20140112 20140118 20140124 20140130

Answer

$ ls | while read f; do wd=$(echo $f | LANG=C gdate -f - '+%a'); mkdir $wd; mv $f $wd; done

Q5

Question

以下のようにa,b,cというディレクトリを作り、その下に「{a,b,c}数字」というファイルを作ります。ファイル名の1文字目とディレクトリ名が一致するようにファイルを移動してください。

uedambp:q5 ueda$ tree
.
├── a
│   ├── a01
│   └── b01
├── b
│   ├── a02
│   ├── a03
│   └── c01
└── c
 └── a04

Answer

$ find . -type f | awk -F/ '{print substr($3,1,1), $0}' | while read d f; do mv $f $d; done
$ tree
.
├── a
│   ├── a01
│   ├── a02
│   ├── a03
│   └── a04
├── b
│   └── b01
└── c
    └── c01

Q6

Answer

次のようにディレクトリa, b, cの下に、8桁日付のファイルをいくつか置きます。 各ディレクトリの最新日付のファイルをカレントディレクトリ(a,b,cのあるディレクトリ)にコピーしてください。各ディレクトリの最新ファイルの日付はそれぞれ違い、コピーの際に衝突しないこととします。

uedambp:q6 ueda$ tree
.
├── a
│   ├── 20130120
│   ├── 20140901
│   └── 20141021
├── b
│   ├── 20131011
│   └── 20140202
└── c
 ├── 20110202
 ├── 20130224
 └── 20141224

Answer

ls コマンドはファイル名をソートしてから出力するので各ディレクトリを ls して最後の出力が最新日付のファイル。 tail コマンドで ls コマンドの最後の出力だけを取得してコピーする。

$ ls | while read f; do ls $f | tail -n 1 | xargs -I{} cp "$f{}" .; done
$ ls
20140202  20141021  20141224  a/        b/        c/

Q7

Question

Q6について、適当にファイルをtouchします。今度はタイムスタンプが最新のファイルを、a, b, cそれぞれからカレントディレクトリにコピーしてください。コピーの際にタイムスタンプを変えない事。

Answer

適当にファイルを touch する。

$ touch a/20140901
$ touch b/20131011
$ touch c/20141224

ls コマンドの -t オプションと cp コマンドの -p オプションを使う。 自分はどちらのコマンドもたしかいい感じのオプションがある気がするけどよく分からないので manual を見ながらやった。

$ ls | while read f; do ls -t $f | head -n 1 | xargs -I{} cp -p "$f{}" .; done

Q8

次のように5個ファイルを作ります。file1をfile2, file2をfile3, file3をfile4, file4をfile5, file5をfile1にmvしてください。

uedambp:q8 ueda$ for i in 1 2 3 4 5 ; do echo $i > file$i ; done
uedambp:q8 ueda$ head *
==> file1 <==
1

==> file2 <==
2

==> file3 <==
3

==> file4 <==
4

==> file5 <==
5

Answer

コピー元とコピー先のファイルが循環するのがポイント。

$ ls | sort -r | awk 'BEGIN{d="tmp"}{print $1,d;d=$1}END{print "tmp",d}' | while read s d; do mv $s $d; done

$ head *
==> file1 <==
5

==> file2 <==
1

==> file3 <==
2

==> file4 <==
3

==> file5 <==
4

参考サイト

Arch Linux のパッケージのダウングレードは downgrade コマンドを使うとすごく楽。

はじめに

Arch Linux のパッケージは pacman で管理するけどダウングレードは downgrade コマンドを使うとすごく楽なのでメモしておく。

downgrade コマンドを使わなくても自分で古いパッケージを Arch Linux Archive から検索とダウンロードしてダウングレードすることもできるけど、個人的には downgrade コマンドを使うのがずっと楽だと感じてる。

downgrade のインストール方法

AUR のパッケージは base-devel グループがインストールされているのを前提としているので先にインストールしておく。

$ sudo pacman -S --needed base-devel

downgrade のリポジトリをクローンして makepkg -si コマンドを実行すると、makepkg コマンドが自動でソースコードをダウンロード、pacman で依存関係を解決、コンパイルしてパッケージ化してパッケージをインストールする。

$ git clone https://aur.archlinux.org/downgrade.git
$ cd downgrade
$ makepkg -si

downgrade の使い方

パッケージのダウングレードは downgrade <パッケージ> コマンドでやることができる。

downgrade コマンドを試すために pacman で Go の最新版をインストールしておいて、このあと downgrade していく。 go version コマンドを実行すると Go のバージョンが 1.16.6 であることが表示する。

$ go version
go version go1.16.6 linux/amd64

sudo downgrade go を実行するとダウングレード先のバージョンが表示してどのバージョンにダウングレードするかのプロンプトが表示する。

$ sudo downgrade go
Available packages (core):

    1)  go  2  1.11.5  1  remote
    2)  go  2  1.11.5  2  remote
    3)  go  2  1.11.5  3  remote

# 省略

   44)  go  2  1.16.3  1  remote
   45)  go  2  1.16.4  1  remote
   46)  go  2  1.16.5  1  remote
+  47)  go  2  1.16.6  1  remote
+  48)  go  2  1.16.6  1  /var/cache/pacman/pkg

select a package by number: 

今回は Go の 1.16.6 の一つ前のバージョンの 1.16.5 にダウングレードしていく。

1.16.5 のところの左側に表示している 46 を入力して enter キーを押すとパッケージの取得が始まるって処理を継続してよいかのプロンプトが表示する。 処理を継続してよいなら Y を入力していく。

   46)  go  2  1.16.5  1  remote
+  47)  go  2  1.16.6  1  remote
+  48)  go  2  1.16.6  1  /var/cache/pacman/pkg

select a package by number: 46
:: Retrieving packages...
 go-2:1.16.5-1-x86_64                         14.1 MiB  2.88 MiB/s 00:41 [####------------------------------------]  10%
loading packages...
warning: downgrading package go (2:1.16.6-1 => 2:1.16.5-1)
resolving dependencies...
looking for conflicting packages...

ダウングレードが終わると Go のパッケージを IgnorePkg に追加するかのプロンプトが表示する。 IgnorePkg に追加するなら y を入力して IgnorePkg に追加しないなら N を入力する。

(1/1) checking keys in keyring                                           [########################################] 100%
(1/1) checking package integrity                                         [########################################] 100%
(1/1) loading package files                                              [########################################] 100%
(1/1) checking for file conflicts                                        [########################################] 100%
:: Processing package changes...
(1/1) downgrading go                                                     [########################################] 100%
:: Running post-transaction hooks...
(1/1) Arming ConditionNeedsUpdate...
add go to IgnorePkg? [y/N] N

ダウングレードが終わってから go version コマンドを実行すると Go のバージョンが 1.16.5 であることが表示する。 downgrade コマンドを使うとこんな感じにパッケージをダウングレードできるので、自分で古いパッケージを Arch Linux Archive から検索とダウンロードして……とやるよりもずっと楽にダウングレードができる。

$ go version
go version go1.16.5 linux/amd64

参考サイト

巨大なファイルを S3 にアップロードするのが `Part number must be an integer between 1 and 10000, inclusive` のエラーになるときにやったことのメモ。

巨大なファイルを S3 にアップロードするのが Part number must be an integer between 1 and 10000, inclusive のエラーになるときにやったことをメモしておく。

なにをしたらどんなことがおきるか

100GB くらいのファイルを aws s3 cp コマンドで S3 にアップロードしたらこんなエラーになった。

An error occurred (InvalidArgument) when calling the UploadPart operation: Part number must be an integer between 1 and 10000, inclusive

AWS CLI のバージョン。

$ aws --version

どうしたか

aws s3 cp の公式ドキュメントを見てみた。

aws s3 cp の公式ドキュメントは「ストリームを S3 にアップロードしててストリームのサイズが 50GB より大きい場合は ストリームの予想サイズを ----expected-size オプションに指定しないと失敗することがあるよ」と書いているので、 --expected-size= 107374182400 みたいに --expected-size を指定したらエラーにならなくなった。

たぶんだけどストリームを S3 にアップロードするときはチャンク数が最大でも 10000 の制限がありそう。 --expected-size オプションでストリームの予想サイズを指定するとストリームをチャンクに分割するときにチャンク数が 10000 を超えないようにチャンクサイズを決めてそう。

This argument specifies the expected size of a stream in terms of bytes. Note that this argument is needed only when a stream is being uploaded to s3 and the size is larger than 50GB. Failure to include this argument under these conditions may result in a failed upload due to too many parts in upload.

参考サイト

GitHub に登録している公開鍵は https://github.com/<username>.keys から取得できる。

GitHub に登録している公開鍵は https://github.com/<username>.keys から取得できる。

たとえばブラウザーhttps://github.com/torvalds.keys を表示すると Linus Torvalds 氏の公開鍵が取得できる。

これだけ。


自分は AWS の EC2 のインスタンスを立てたら curl とか wget でチームメンバーの公開鍵を GitHub からダウンロードして $HOME/.ssh/authorized_keys に突っ込んだりしてる。 たまに公開鍵を登録していないメンバーがいると「ワイだけ EC2 にログインできないんだけど」みたいなことがあるかもしれないけど、そもそも EC2 を使う機会自体が減ってきていてあまり困ることはなさそうなので、そんなには気にしていない。

cat コマンドはファイルと標準入力を連結したり連結する順番を指定できる。

cat コマンドは複数のファイルだけじゃなくて標準入力も連結できるのをメモしておく。

cat コマンドはファイルの内容を標準出力に表示するのに使うことが多い気がするけど、たいがいは単一のファイルの内容を標準出力に出力してる気がする。 実は cat コマンドは man page を見ると concatenate and print files みたいに書いてあって、ファイルを表示したり複数のファイルを連結するのに使える。

複数のファイルを連結するのはこんな感じに使える。

# 1つめのファイル
$ cat abc.txt
a
b
c

# 2つめのファイル
$ cat xyz.txt
x
y
z

# 2つのファイルを連結する
$ cat abc.txt xyz.txt
a
b
c
x
y
z

cat コマンドの引数のファイルの並び順を入れ替えると cat コマンドが標準出力に出力する順番も入れ替わる。

$ cat xyz.txt abc.txt
x
y
z
a
b
c

あと cat コマンドは標準入力から読み込んで標準出力に出力することができる。

こんな感じで標準入力から読み込んだものを abc.txt の内容より前に出したり abc.txt の内容のあとに出すことができる。 データの入力元が標準入力からとファイルからとの2つがあるとき、cat コマンドを使うとどちらの入力を先に出力するかを制御できる。

$ seq 1 3 | cat - abc.txt
1
2
3
a
b
c

$ seq 1 3 | cat abc.txt -
a
b
c
1
2
3