Macのコマンドの挙動がなんかおかしい時はcoreutilsを使おう

Macユーザーだったらこんな経験があると思う。ネットからコマンドを丸々コピってきてターミナルに貼っつけたけどなんか上手くいかない。command not foundじゃない、コマンドはちゃんとある、コピペもミスってない。なんなんだ?
特に、シェル芸ワンライナー160本ノックをやってるときによくこの現象に遭遇した。例えば、以下のようなコマンドがある。

$ echo 2023/01/02 | date -f - '+%Y%m%d'
20230102

このコマンドをMacのターミナルにコピペして実行しても動かないが、Ubuntu環境とかで実行すると問題なく動く。この原因はコマンドの違いにある。MacはUnixベースのBSD系、UbuntuはLinuxベースのGNU系OSであり、コマンドの動きは大体に似通っているのだが、完全な互換性はない。ネット上にある記事なんかは大体Linux環境を念頭に置かれて書かれているので、同じコマンドが入っていたとしてもMacでは動かないことが稀によくある。

解決策

coreutils を入れると良い。coreutilsはGNU Core Utilities、要はGNUにおいてコアとなるコマンドの詰め合わせみたいなもの。これを入れておくとよく使う有名なコマンドのGNU版が手に入る。インストールは簡単。

brew install coreutils

これで手に入ったコマンドの一覧を見てみる。

$ ls /usr/local/Cellar/coreutils/9.1/bin/
b2sum
base32
basenc
chcon
factor
g[
gb2sum
gbase32
gbase64
gbasename
gbasenc
gcat
gchcon
gchgrp
gchmod
gchown
gchroot
gcksum
gcomm
gcp
gcsplit
gcut
gdate
gdd
gdf
gdir
gdircolors
gdirname
gdu
gecho
genv
gexpand
gexpr
gfactor
gfalse
gfmt
gfold
ggroups
ghead
ghostid
gid
ginstall
gjoin
gkill
glink
gln
glogname
gls
gmd5sum
gmkdir
gmkfifo
gmknod
gmktemp
gmv
gnice
gnl
gnohup
gnproc
gnumfmt
god
gpaste
gpathchk
gpinky
gpr
gprintenv
gprintf
gptx
gpwd
greadlink
grealpath
grm
grmdir
gruncon
gseq
gsha1sum
gsha224sum
gsha256sum
gsha384sum
gsha512sum
gshred
gshuf
gsleep
gsort
gsplit
gstat
gstdbuf
gstty
gsum
gsync
gtac
gtail
gtee
gtest
gtimeout
gtouch
gtr
gtrue
gtruncate
gtsort
gtty
guname
gunexpand
guniq
gunlink
guptime
gusers
gvdir
gwc
gwho
gwhoami
gyes
hostid
md5sum
nproc
numfmt
pinky
ptx
realpath
runcon
sha1sum
sha224sum
sha256sum
sha384sum
sha512sum
shred
shuf
stdbuf
tac
timeout
truncate

色々入ってる。特徴的なのは先頭がgのコマンドが多いこと。ネイティブで入っているコマンドと名前が被らないように、GNUコマンドだと分かりやすいように付けているのだろう。

$ ls /usr/local/Cellar/coreutils/9.1/bin/ | grep date
gdate

dateコマンドを探すとgdateが出てきた。これがGNU版のdateコマンドなんだろう。ということで、さっきのコマンドを今度はgdateに変えてやってみる。

$ echo 2023/01/02 | gdate -f - '+%Y%m%d'
20230102

うん、これでMacでも問題なく動いた。ちなみに、なぜこのコードの挙動が異なるかというと、 -f - の部分が原因なのではないかと思われる。Unixシェルでは - (ハイフン)は標準入力を表すものとされる習慣があるのだが、その習慣がMac標準のdateでは採用されていなかったのが原因なんじゃないかと。ただ、これはMac標準のコマンド全てに当てはまるわけではない。例えば、catに対してハイフンを渡すと、「標準入力から受けたものを標準出力から返す」という挙動になるのだが、これはcatだろうがcoreutilsで入れたgcatであろうが同様に動作する。難しいね。

まあ、こんな七面倒なコマンドの挙動の違いをいちいち覚えているわけにはいかないので、MacとLinuxではコマンドの挙動が微妙に違うことを覚えておくと良いだろう。なんか動かなかったらcoreutilsに同じコマンドがあるかどうか見てみる、という選択肢があるのは心強い。