サクッとUbuntu (WSL)でzstd(ZStandard)を使う方法

ZStandardがzipやgzより圧縮率、速度どちらでも優れている（※1）ということで使おうとしたときに、もっとズボラに使いたかった（※2）ので何とかした備忘録です。
前半は普通の使い方、後半はズボラに使えるよう工夫した例です。

※1：どうやら、ファイル数が多いとtarがボトルネックになり、zipの方が速い事もあるようです。また、圧縮・展開速度は、「リアルタイム」(ディスクアクセスと同じくらい)と称されるほど速いです。
※2：気にいらない点
zstd / unzstd コマンドは、圧縮するのみでアーカイバではないのでディレクトリに非対応！
tar を経由するとディレクトリ扱えるが長いコマンド打つのがめんどくさい！(出力ファイル名を指定するのがめんどくさい！)
1ファイルだけ扱う時とディレクトリを扱う時で使うコマンドが違う！
zip / unzip と同じかそれ以上にサクッと短いコマンドで使いたい！

※ 公開当初からの変更・修正箇所まとめ
・「zst.sh」に1ファイルだけ食わせると拡張子が消えてしまう問題があったので修正
　(tarに1ファイルだけ食わせるようにしていたが、それだと1ファイルであったとしてもtarで固められてしまう。拡張子をいじってもダメだったので、その辺を諦めて、大人しくzstdコマンドを使うことにした。)
・「unzst.sh」も tar されていないファイルにはうまく動かなかったようで、修正しました。
・スクリプトで全体的に挙動よく分かって無かった所等を書き直しました。
・スクリプトを並列処理化させました。
（追記履歴の詳細は更新履歴をご確認ください。）
・半角スペースを含むファイル/ディレクトリに対応しました。
・getoptコマンドを使うことで、後ろ側にオプション書くのも対応しました。(逆にBSDでは動かないスクリプトになったもよう？) 例： zst hoge/ -r

## インストール

```
sudo apt install zstd
```

tar コマンドもこれによりzstdを扱えるようになります。


## 圧縮の基本操作

##### ファイルのみ

```
zstd -f [files]
例：
zstd -f hoge.txt
-> hoge.txt.zst が生成されます。
```

`-f`は上書き警告を表示せず強制上書き。
コマンドを`zstdmt`に変える、または`-T0`オプションを追加するとマルチスレッド化（後述）。
オプション`-v`は付けなくても、tarの`-v`相当の表示が行われます。
また`-vv` `-vvv`と重ねると、表示が細かくなっていくようです。

`--vv` にすると、終了時にかかった時間やCPUにどれだけ負荷をかけられたか表示してくれます。

マルチスレッド化はデフォルトの圧縮レベル3なら、ストレージがSSDでもディスクアクセスの方がボトルネックになり、あまり恩恵無し。さらに`tar -Izstdmt`で使う場合は、`tar`の処理の方がボトルネックになるもよう。展開の場合はより顕著。
`zstdmt -9f [files] ` みたいに圧縮率を上げるほど効いてくる。
コメントアウトしていますが、pzstdは挙動に注意。`pzstd`で実行すると、おそらくマルチプロセスでの圧縮になります。メモリ使用量UP、圧縮率少し低下（おそらく、ファイルをCPUコア数の個数分に刻んで、別プロセスからzstdコマンドをコア数分、複数個実行する、といった事をしてると予想します。） 。さらに、最終更新日時の挙動とかも変わり、現在の日時で出力するようになります。また、Pythonの "zstd" ライブラリで展開しようとするとエラーになるなど、一部問題があるようです(なお、Pythonの "zstandard" ライブラリでは展開できるもよう)。展開時は、`pzstd`を使っても、結果は同じになります。可逆圧縮なので当然といえば当然ですが。
`zstd -T0`または`zstdmt`コマンドであれば`zstd`と同じ挙動で大丈夫そう。 (一般的な圧縮ソフトは、最終日時は上書きする方が標準なようです。zstdに固有の挙動？)
あと、pzstdコマンドには -p オプションがありますが、これは指定しなければコア数、最近のIntelCPUであれば最高効率の出る「Pコアの数+Eコアの半分」で実行されます。具体的には numcpus で、CPUコア数を調べるコマンドで返ってくる数です。 /proc/cpuinfo の中にコア数は記載があるらしい。


##### ディレクトリ

```
tar -Izstd -cvf [directory].tar.zst [directories]
例：
tar -Izstd -cvf moge.tar.zst moge/
```

マルチスレッドでやりたい場合は`-Izstdmt`に変えたら行けます。
※： 1ファイルだけ圧縮する時にtarを使うと、1ファイルだけなのにtarで固めた上での圧縮となってしまいます。つまり、通常は1ファイルだけ圧縮する場合、`zstd`コマンドを使う必要があります。

## 展開 (解凍)の基本操作

##### ファイルのみ

```
unzstd -f [file]
例：
unzstd -f hoge.txt.zst
-> hoge.txt が展開(解凍)されます。
```

オプション内容は圧縮と同様です。
マルチスレッドでやりたい場合は`zstdmt -d`に変えたら行けますが、あんまり効果無いかも。



##### ディレクトリ

```
tar -Izstd -xvf [file]
例：
tar -Izstd -xvf moge.tar.zst
-> moge ディレクトリが展開(解凍)されます。
```

マルチスレッドでやりたい場合は`-Izstdmt`に変えたら行けますが、先述の通り、ほとんど効果無いかも。

## tar の余談

`-cvf`/`-xvf`は`-cf`/`-xf`でもいいです。（`-v`は詳細表示）
さらに言うと、`cf`/`xf`で良いです。
`-Izstd -cf`は tar のバージョンが1.31以降なら`acf`で良いです。1.31 から --zstd オプションに対応しました。それに伴い、出力ファイルの拡張子が .tar.zst であれば -a (--auto-compress) を指定するだけでZStandardを使ってくれます。
（なお -I (アイ)に続けてスペース開けずにzstdです。`-Izstd`を使う場合 `cf`/`xf` のハイフン省略は出来なくなります。）
`f`はファイル入力、省略すると標準入力を見に行ってしまうので省略不可。
`c`は`--create`の略で圧縮形式を指定するということは、tarで固めて圧縮するということ。
`x`は`--extract`の略で展開。
`-I`はどうやら、tarで正式対応していない圧縮ソフトも指定できるようです。なので`-Izstdmt`や`-I"zstd -T0"`にするとマルチスレッド対応になります(`man tar`や`man zstd`を参照)。なお、`-Ipzstd`でも行けますが、先述の通り挙動が異なるので注意。
また、tarは1ファイルだったとしても、tarで固めてから作業を行う、展開時tarで固めらているという前提で処理する。
まとめると以下のような感じ。

```bash.sh
# OKパターン
tar -Izstd -cf abcdir.tar.zst abcdir/
tar -Izstd -xf abcdir.tar.zst 
tar -xf abcdir.tar.zst  # 展開のみ -Izstd 省略可能
tar xf abc.tar.zst  # -Izstd を省略した場合のみ、ハイフンも省略可能
tar -Izstd -cf abc.tar.zst abc.txt  # ただし、tarで固められるので、おすすめ出来ない。
tar -Izstd -xf abc.tar.zst  # tarで固まっていれば1ファイルでも展開できるが、おすすめ出来ない。

# NGパターン
# エラーは出ないが、実際に生成される物はtarで固められて
#   ディレクトリの中に入れられるため、出力ファイル名の辻褄が合わない
tar -Izstd -cf abc.txt.zst abc.txt
tar -Izstd -cf abc.txt.tar.zst abc.txt
# 上記対応。エラーは出ないが、展開された "abc.txt" はまだtarで固められている。
tar -Izstd -xf abc.txt.zst 
# 上記対応。エラーは出ないが、"abc.txt" というディレクトリの中に、さらに "abc.txt" になってしまう。
tar -Izstd -xf abc.txt.tar.zst
# エラーは出ないが、tarコマンド以外で展開しようとするとエラー。
#   （実際には"tar.gz"ファイルが生成されているため。）
tar -cf abcdir.tar.zst abcdir/
tar cf abcdir.tar.zst abcdir/
# -Izstd を付けた場合、ハイフン省略不可
tar -Izstd xf abcdir.tar.zst
# -Izstd は-cf/-xfより前に書く必要がある
tar -cf -Izstd abcdir.tar.zst abcdir/  # '-Izstd'というファイルが生成されてしまう。
tar -xf -Izstd abcdir.tar.zst
# ※ '-Izstd'ファイルは、'rm -Izstd'で削除出来ず、'rm ./-Izstd'なら削除できる。
```

## さらに、ズボラしたい人向け

tarを経由すると毎回長いコマンドがめんどくさい！
1ファイルかそうじゃないかでコマンド使い分けめんどくさい！
zip / unzip と同じようにサクッと使いたい！
という人（私）は・・・
一例として、「ホームディレクトリに隠しファイルのスクリプトを作って、エイリアスで自作コマンドとして使う」手順を以下に記載します。
(標準のコマンドが zstd / unzstd なので被らないように zst / unzst にしています。)

### スクリプトをホームディレクトリに用意

(予めスクリプトファイルを用意しておいて、ホームディレクトリに隠しファイルとしてコピーし、実行権限付与し、.bashrcを編集してエイリアスを設定します。)

#### 圧縮スクリプト

```zst.sh
#!/bin/bash
# supports are only "1 file or 1 directory" and "-r and -v options"

# analyze arguments
FLAG_R=false
FLAG_V=false
args=$(getopt -o rv -- "$@")
eval set -- "$args"
while true; do
	case "$1" in
		-r)
			FLAG_R=true
			shift
			;;
		-v)
			FLAG_V=true
			shift
			;;
		--)
			shift
			break
			;;
	esac
done

if [ $# -le 0 ]; then
	echo "error! too few arguments" >&2
	exit
fi
if [ $# -gt 1 ]; then
	echo "error! too many arguments" >&2
	exit
fi

if "${FLAG_R}"; then
		if [ -d "$1" ]; then
		# set tar options
		if "${FLAG_V}"; then
			TAR_OPT="-Izstdmt -cvf"
			#TAR_OPT="-Izstd -cvf"  # 並列化したくないならこちら
			#TAR_OPT="-Ipzstd -cvf"  # 一部挙動違いを理解して並列化するならこちら
		else
			TAR_OPT="-Izstdmt -cf"
			#TAR_OPT="-Izstd -cf"  # 並列化したくないならこちら
			#TAR_OPT="-Ipzstd -cf"  # 一部挙動違いを理解して並列化するならこちら
		fi
		# run tar
		tar $TAR_OPT "${1%/}.tar.zst" "$1"
	else
		echo "error! it is not directory" >&2
		exit
	fi
else
	if [ -f "$1" ]; then
		# set zstd options
		if "${FLAG_V}"; then
			ZST_OPT="-fT0"
			#ZST_OPT="-f"  # 並列化したくないならこちら
		else
			ZST_OPT="-fqT0"
			#ZST_OPT="-fq"  # 並列化したくないならこちら
		fi
		# run zstd
		zstd $ZST_OPT "$1"
		#pzstd $ZST_OPT "$1"  # 一部挙動違いを理解して並列化するならこちら
  else
		echo "error! it is not file" >&2
		exit
	fi
fi
```

#### 展開スクリプト

```unzst.sh
#!/bin/bash
# supports option is only "-v"
# directory archive second extension from back is need "tar"

# analyze arguments
FLAG_V=false
args=$(getopt -o v -- "$@")
eval set -- "$args"
while true; do
	case "$1" in
		-v)
			FLAG_V=true
			shift
			;;
		--)
			shift
			break
			;;
	esac
done

# check file or tar
# get second extension
SEC_EXT="${1%.*}"
SEC_EXT="${SEC_EXT##*.}"
if [[ "$SEC_EXT" == "tar" ]]; then
	# set tar options
	if "$FLAG_V"; then
		TAR_OPT="-Izstdmt -xvf"
		#TAR_OPT="-Izstd -xvf"  # 並列化したくないならこちら
	else
		TAR_OPT="-Izstdmt -xf"
		#TAR_OPT="-Izstd -xf"  # 並列化したくないならこちら
	fi
	# run tar
	tar $TAR_OPT "$1"
else
	# set unzstd options
	if "$FLAG_V"; then
		UZST_OPT="-dfT0"
		#UZST_OPT="-df"  # 並列化したくないならこちら
	else
		UZST_OPT="-dfqT0"
		#UZST_OPT="-dfq"  # 並列化したくないならこちら
	fi
	# run unzstd
	zstd $UZST_OPT "$1"
	#pzstd $UZST_OPT "$1"  # これでも行ける
fi
```

### 使い方

以下の要領で反映していきます。
zst.sh / unzst.sh はカレントディレクトリにある前提です。
必要に応じてコマンドは調整してください。

```
# ファイルを配置して実行権限を付ける
cp zst.sh ~/.zst.sh
cp unzst.sh ~/.unzst.sh
chmod +x ~/.zst.sh
chmod +x ~/.unzst.sh
vi ~/.bashrc
```

```.bashrc
# 編集
# 記載済みの行は削除せずに、末尾に追記
alias zst='~/.zst.sh $@'
alias unzst='~/.unzst.sh $@'
```

上記 alias を有効にするため、WSLならコマンドプロンプトから一旦シャットダウンしてから再起動。

```コマンドプロンプト.
wsl --shutdown
wsl
```

`source ∼/.bashrc`でもOKです。


#### 圧縮 (スクリプト使用)

```
zst [-r] [-v] [file/directory]
例：
zst hoge.txt
zst -v hoge.txt
zst -rv moge/
```

#### 展開 (スクリプト使用)

```
unzst [-v] [file/directory]
例：
unzst hoge.zst
unzst -v hoge.zst
unzst -v moge.tar.zst
```

##### スクリプトの概要

圧縮は zip コマンドよりもさらにズボラして、
`zst hoge.txt` なら `zst hoge.txt.zst`
`zst -r moge/` なら `moge.tar.zst`
が生成されます。
（出力ファイル名は指定できません。）
後は zip / unzip と似た感じに使えると思います(圧縮は -r/-v 、展開は -v オプションのみ対応)。
無駄に -r とか -v オプションの対応してますが、決め打ちしたらもっと簡単なスクリプトでいいはずです。
ディレクトリ末尾のスラッシュは合っても無くても大丈夫にしてあります。
-r 指定無しの時は1ファイルのみ、-r 指定ありの時は1ディレクトリのみの対応です。

## 参考にした記事
おすすめの圧縮ソフトZstandard(zstd)の使い方
https://qiita.com/oioi_tec/items/e66ec93824f694a473c9

シェルスクリプト オプション解析 徹底解説 (getopt / getopts)
https://qiita.com/ko1nksm/items/cea7e7cfdc9e25432bab

bashで拡張子を除いたファイル名を取得する
https://qiita.com/ksugimori/items/c61843d9353134b92cc7

圧縮アルゴリズムZstandardを導入しバッチ処理時間を短縮　データ鮮度を改善した話 - FORCIA
https://www.forcia.com/blog/001188.html

GNU tar 1.31登場、圧縮アルゴリズム「Zstandard」に対応
https://news.mynavi.jp/techplus/article/20190110-754414/

man tar(1)
https://manpages.debian.org/unstable/manpages-ja/tar.1.ja.html

man pzstd(1)
https://manpages.debian.org/testing/zstd/pzstd.1.en.html

## Windowsの場合

ややクセはありますが PeaZip あたりがおすすめです。
あと 7-Zip からフォークされたらしき 7-Zip-zstd というのもあります（既に本家をインストール済みの人には扱いにくいかも）。