前置き
想定読者
想定読者:テキスト処理をしようと思って手元の Mac でコマンドを打ちこんでみたけど書籍や Web サイトの見本通りに動作しなくて困っている人
macOS にプリインストールされているコマンド群には BSD(Unix 系 OS のひとつ)由来のものが多く含まれます.
一方,会社や大学などで利用するサーバ(業務用途・学術用途のサーバ)には Linux 系 OS がインストールされていることが多く,また,書籍や Web ページに(注釈なく)書かれているシェルスクリプトやワイライナーも Linux 系環境が想定されていることがしばしばです.
BSD 系コマンドと GNU/Linux 系コマンドの間の互換性は完全ではなく,たとえば同じ ls でも,BSD の ls と GNU coreutils の ls では利用できるオプションは異なります.
この記事では,初心者が,シェルコマンドを用いたテキスト処理を行う際に(ちょっとしたシェル芸で遊んでみたいときに)書籍や Web ページのコマンドを変更せずに手元で再現できるよう,また macOS 上で作成したコードの可搬性を高められるよう,Homebrew を用いて,macOS に,GNU/Linux 系コマンドを中心とした標準的なテキスト処理用のコマンド群をインストールする手順 を示します.
# 「ここの記述が不正確だぞ」「もっと効率の良い・問題の起きづらい導入法あるよ」「これも入れるべき」など突っ込みどころがありましたら是非コメントをお寄せください.
想定環境
※ 使っているシェルが Bash か Zsh か不明な場合は,ターミナルで $ echo $SHELL と打ってみてください.ログインシェルが(たとえば /bin/bash のように)表示されます.
取り扱うコマンド
- (主に行単位の)テキスト処理
-
tr,cut,ed,gnu-sed,gawk -
grep,ag,sift -
sort,uniq,tac,head,tail
-
- 複数ファイルの結合・分割・差分抽出
cat-
paste,join -
diff系コマンド
- 構造化テキストの処理
-
xmlstarlet,jq,pandoc
-
- 並列処理
-
xargs,parallel
-
- ダウンロード
wget
- 圧縮・解凍
-
gnu-tar,gzip,unzip
-
- ページャなど
-
less,lv
-
- エディタ
-
nano(emacsおよびvimは扱いません)
-
- ファイルシステム
-
ls,mv, etc.
-
- ほか
-
echo, etc.
-
取り扱わないコマンド
- バイナリファイル処理・低レベル言語の処理
- ネットワーク・通信
- プロセス・ジョブ管理
- バージョン管理
- シェル(そのもの)
- ターミナルマルチプレクサ
- ほか
主な参考サイト
- Install and Use GNU Command Line Tools on Mac OS X | Hong Xu (xuhdev)
- terminal - How to replace Mac OS X utilities with GNU core utilities? - Ask Different
- The First 10 Things I Do on a New Mac
- One Tip Per Day: Install GNU in Mac OS
導入手順
1. シェルの設定ファイルの調整
-
~/.bash_profileと~/.bashrc(Bash の場合),または~/.zshenvと~/.zshrc(Zsh の場合)に以下を追記します.ファイルが存在しない場合は作成してください.~/.bash_profileexport PATH export MANPATH # (~/.bash_profile 末尾) # for login (and then interactive) shell # ref. https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html if [ -f ~/.bashrc ]; then . ~/.bashrc; fi~/.bashrc# (~/.bashrc 冒頭) # If not running interactively, don't do anything # ref. https://www.gnu.org/software/bash/manual/html_node/Is-this-Shell-Interactive_003f.html if [ -z "$PS1" ]; then return fi~/.zshenvexport PATH export MANPATH # -U: keep only the first occurrence of each duplicated value # ref. http://zsh.sourceforge.net/Doc/Release/Shell-Builtin-Commands.html#index-typeset typeset -U PATH path MANPATH manpath # ignore /etc/zprofile, /etc/zshrc, /etc/zlogin, and /etc/zlogout # ref. http://zsh.sourceforge.net/Doc/Release/Files.html # ref. http://zsh.sourceforge.net/Doc/Release/Options.html#index-GLOBALRCS unsetopt GLOBAL_RCS # copied from /etc/zprofile # system-wide environment settings for zsh(1) if [ -x /usr/libexec/path_helper ]; then eval `/usr/libexec/path_helper -s` fi~/.zshrc# (~/.zshrc 冒頭) # in ~/.zshenv, executed `unsetopt GLOBAL_RCS` and ignored /etc/zshrc [ -r /etc/zshrc ] && . /etc/zshrc -
$ source ~/.bash_profile(Bash の場合)または$ source ~/.zshenv; source ~/.zshrc(Zsh の場合)をおこない設定を反映します.
これらの設定が必要な理由
あらかじめ PATH, MANPATH を環境変数にしておきました.以後 path を通したい場合は PATH=/usr/local/bin:${PATH} 等をおこなうだけで(export を都度書かずとも)サブシェルに引き継がれる path が通ります.また,zsh 向けには path の重複登録を抑止する命令(typeset -U)を追記しています. ※ 「path を通す」の意味が気になるかた,は例えば拙文ですが UNIX/Linux 環境でのコマンドライン操作に慣れる…前の基礎知識 - Qiita などをご確認ください.
さらに,(1) Bash をログインシェルとして起動時する際に ~/.bashrc が読み込まれない問題,および (2) El Capitan 以後の macOS で Zsh を利用する際に path_helper が ~/.zshenv よりも後から実行される問題に対処してあります.特に Zsh を用いている場合は,Homebrew でインストールした GNU/Linux 系コマンド群を(g prefix 抜きで)利用するためにこの設定が必須です. ※ 詳細が気になる方は末尾の補遺をご確認ください.
2. Homebrew の導入
$ which brew として brew コマンドが見つからない場合は, Homebrew のインストールからはじめましょう.
インストール
インストールは,公式ドキュメントにしたがい次のコマンドを叩けばOKです.
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ brew update
path を通す
-
~/.bash_profile(Bash の場合)または~/.zshenv(Zsh の場合)に以下を追記します.~/.bash_profile# homebrew PATH=/usr/local/bin:/usr/local/sbin:${PATH} MANPATH=/usr/local/share/man:${MANPATH}~/.zshenvpath=( /usr/local/bin(N-/) # homebrew /usr/local/sbin(N-/) # homebrew ${path} ) manpath=( /usr/local/share/man(N-/) # homebrew ${manpath} ) -
$ source ~/.bash_profile(Bash の場合)または$ source ~/.zshenv(Zsh の場合)をおこない設定を反映します.
確認
$ which brew として /usr/local/bin/brew が返ってくれば準備完了です.
3. GNU/Linux 版コマンドへの置き換え
ls, grep, find といった基本的なコマンドを GNU/Linux 版に置き換えます.
インストール
$ brew install coreutils
$ brew install diffutils
$ brew install ed
$ brew install findutils
$ brew install gawk
$ brew install gnu-sed
$ brew install gnu-tar
$ brew install grep
$ brew install gzip
-
coreutils
-
ls,mvといった基本的なコマンド(の GNU/Linux 版)が含まれています. - テキスト処理用途には,
cat,paste,join,head,tail,tr,cut,sort,uniq,wc,echoなどを使うことになるでしょう. - 同梱されているコマンドの一覧は,
$ brew ls coreutilsや GNU Core Utilities - Wikipedia を参照してください.
-
-
diffutils
-
diff,cmp,diff3,sdiffが同梱されています. - これらのコマンドに関しては,GNU のコマンドがもともと macOS にインストールされていますが,Homebrew からより新しいバージョンをインストールすることができます.
- diff をした結果に色を付けて表示させたい場合は,別途
colordiffをインストールしても良いでしょう.
-
-
findutils
-
find,locate,updatedb,xargsが同梱されています.
-
-
gzip
-
gunzip,gzexe,gzip,uncompress,zcat,zcmp,zdiff,zegrep,zfgrep,zforce,zgrep,zless,zmore,znewが同梱されています.
-
- grep
-
grep,egrep(=grep -E),fgrep(=grep -F) が同梱されています.
-
-
gawk
- macOS にもともと入っている awk(いわゆる nawk)を, GNU 版の awk(いわゆる gawk)に置き換えます.これで,
$ gawkあるいは単に$ awkで GNU awk が呼び出されるようになります. - 基本的に gawk は nawk に対して大幅に機能が追加された上位互換版ですが,どうしても nawk を使いたいときのために
.bashrcや.zshrcにalias nawk=/usr/bin/awkとしておいても良いでしょう.
- macOS にもともと入っている awk(いわゆる nawk)を, GNU 版の awk(いわゆる gawk)に置き換えます.これで,
path を通す
-
~/.bash_profile(Bash の場合)または~/.zshenv(Zsh の場合)に以下を追記します.~/.bash_profile# coreutils PATH=/usr/local/opt/coreutils/libexec/gnubin:${PATH} MANPATH=/usr/local/opt/coreutils/libexec/gnuman:${MANPATH} # ed PATH=/usr/local/opt/ed/libexec/gnubin:${PATH} MANPATH=/usr/local/opt/ed/libexec/gnuman:${MANPATH} # findutils PATH=/usr/local/opt/findutils/libexec/gnubin:${PATH} MANPATH=/usr/local/opt/findutils/libexec/gnuman:${MANPATH} # sed PATH=/usr/local/opt/gnu-sed/libexec/gnubin:${PATH} MANPATH=/usr/local/opt/gnu-sed/libexec/gnuman:${MANPATH} # tar PATH=/usr/local/opt/gnu-tar/libexec/gnubin:${PATH} MANPATH=/usr/local/opt/gnu-tar/libexec/gnuman:${MANPATH} # grep PATH=/usr/local/opt/grep/libexec/gnubin:${PATH} MANPATH=/usr/local/opt/grep/libexec/gnuman:${MANPATH}~/.zshenvpath=( /usr/local/opt/coreutils/libexec/gnubin(N-/) # coreutils /usr/local/opt/ed/libexec/gnubin(N-/) # ed /usr/local/opt/findutils/libexec/gnubin(N-/) # findutils /usr/local/opt/gnu-sed/libexec/gnubin(N-/) # sed /usr/local/opt/gnu-tar/libexec/gnubin(N-/) # tar /usr/local/opt/grep/libexec/gnubin(N-/) # grep ${path} ) manpath=( /usr/local/opt/coreutils/libexec/gnuman(N-/) # coreutils /usr/local/opt/ed/libexec/gnuman(N-/) # ed /usr/local/opt/findutils/libexec/gnuman(N-/) # findutils /usr/local/opt/gnu-sed/libexec/gnuman(N-/) # sed /usr/local/opt/gnu-tar/libexec/gnuman(N-/) # tar /usr/local/opt/grep/libexec/gnuman(N-/) # grep ${manpath} ) -
$ source ~/.bash_profile(Bash の場合)または$ source ~/.zshenv(Zsh の場合)をおこない設定を反映します.
確認
$ which -a ls として /usr/local/opt/coreutils/libexec/gnubin/ls(coreutils 版の ls)が /bin/ls(システムデフォルトの ls)よりも上に返ってくれば準備完了です.以後 $ ls と打てば coreutils の ls が呼び出されます.たとえば $ ls --color=auto で(coreutils の ls 流に)出力結果に色付けをすることができます.
4. 便利コマンドの導入
もともと macOS にはインストールされていないものの,テキスト処理に便利なコマンド群は,この機会にインストールしておきましょう.これらのコマンドのために新たに path を通す必要はありません.
$ brew install ag
$ brew install jq
$ brew install lv
$ brew install parallel
$ brew install pandoc
$ brew install sift
$ brew install wget
$ brew install wdiff --with-gettext
$ brew install xmlstarlet
5. 古いコマンド群を新しくする
- macOS にプリインストールされているけれど,Homebrew でより新しいバージョンをインストールできるコマンド群も,この機会に新しいものと入れ替えておきましょう.これらのコマンドのために新たに path を通す必要はありません.
$ brew install nano
$ brew install unzip
まとめ
- Enjoy!
補遺
以下,本文中で触れた各種設定についての細かい補足です.
Bash の設定ファイルの調整が必要な理由
- Bash は起動時に次の順番で設定ファイルを読み込みます.
-
/etc/profile(ログインシェルの場合) - (
/etc/bashrc.macOS の場合に/etc/profileから呼び出されます.) -
~/.bash_profile,~/.bash_login,~/.profile(ログインシェルの場合)(最初に見つかったものがひとつだけ実行されます.) -
~/.bashrc(インタラクティブシェルの場合)
-
- path などの設定は
~/.bash_profileで,インタラクティブシェルの場合に限った設定(alias やプロンプトなど)は~/.bashrcでをおこなえば良いように見えますが,いくつかの問題があります. - まず,ログインシェルは通常インタラクティブに利用されるにも関わらず,Bash でログインした際にインタラクティブシェルのための設定ファイル
~/.bashrcは自動では読み込まれません.そこで,Bash の公式リファレンスにもある通り,~/.bash_profileの末尾で~/.bashrcを呼び出すようにします. - また,非インタラクティブシェルの場合に(
~/.bash_profileから呼び出された)~/.bashrcの設定が読み込まれないよう,~/.bashrcの冒頭で分岐を入れます.これは,/etc/bashrcの冒頭に元から書かれている分岐と全く同じ目的・書きかたです.
Zsh の設定ファイルの調整が必要な理由
-
Zsh は起動時に次の順番で設定ファイルを読み込みます.
-
/etc/zshenv※ macOS には存在しません. ~/.zshenv-
/etc/zprofile(ログインシェルの場合) -
~/.zprofile(ログインシェルの場合) -
/etc/zshrc(インタラクティブシェルの場合) -
~/.zshrc(インタラクティブシェルの場合) -
/etc/zlogin(ログインシェルの場合) ※ macOS には存在しません. -
~/.zlogin(ログインシェルの場合)
-
-
path などの設定は
~/.zshenvで,インタラクティブシェルの場合に限った設定(alias やプロンプトなど)は~/.zshrcでをおこなえば良いように見えます. -
しかし,El Capitan 以後の macOS では,
~/.zshenvよりも後から読み込まれる/etc/zprofileでシステムデフォルトの path が設定されます(正確にはpath_helperが実行されます.詳細は/etc/zprofileの中身および$ man path_helperを参照).すなわち,ユーザが~/.zshenvに登録した path よりも,システムデフォルトの path の優先順位が上がってしまいます.たとえば coreutils で導入したlsよりも,元々入っているlsが優先されてしまいます. -
この問題に対処するため,今回は (1)
GLOBAL_RCSを用いて/etc/zprofileを一旦無視し,(2)path_helperは別途~/.zshenvから呼び出しました.# 再掲 # ~/.zshenv # ignore /etc/zprofile, /etc/zshrc, /etc/zlogin, and /etc/zlogout unsetopt GLOBAL_RCS # copied from /etc/zprofile # system-wide environment settings for zsh(1) if [ -x /usr/libexec/path_helper ]; then eval `/usr/libexec/path_helper -s` fi # ~/.zshrc # in ~/.zshenv, executed `unsetopt GLOBAL_RCS` and ignored /etc/zshrc [ -r /etc/zshrc ] && . /etc/zshrc
代案
path_helper の問題に対しては上記の方法(本文中に示した方法)がおすすめですが,ほかにも対策は考えられます.
たとえば /etc/zprofile を無視する方法は unsetopt GLOBAL_RCS の他にもあり得ます.
- (a)
setopt NO_GLOBAL_RCSも同様の効果です.書き方はsetopt no_global_rcs,setopt no_globalrcs,setopt noglobalrcs,setopt NO_GLobalrcs, 等でも構いません. - (b)
/etc/zprofileをコメントアウトしても同様の効果が得られます.ただし,このファイルは全ユーザに共通の設定ファイルであり,編集はおすすめできません.( @GeneralD さんコメントありがとうございます.) - (c)
$ brew install zsh --without-etcdirで「/etc以下のグローバルな設定ファイルを無視する zsh」をインストールすることができました.が,この機能はunsetopt GLOBAL_RCSで代替できるため,現在は当該オプションは削除されています. - (d)
$ brew install zsh --disable-etcdirで「/etc以下のグローバルな設定ファイルを無視する zsh」をインストールすることができました.が,現在は当該オプションは削除されています.
また /etc/zprofile を無視せずに path_helper の問題に対処する方法もあり得ます.
- (e)
~/.zshrcで path を通せば,このファイルは/etc/zprofileより後から読み込まれるため,問題は解決されます.しかし,非インタラクティブシェルの場合に path が通らなくなる問題が生じ得ます. - (f)
~/.zprofileで path を通せば,このファイルは/etc/zprofileより後から読み込まれるため,問題は解決されます.しかし,非ログインシェルの場合に path が通らなくなる問題が生じ得ます.
一部の GNU/Linux コマンド用に特別な path を通す理由
- GNU coreutils など一部の GNU/Linux コマンド用に(Homebrew とは別に)path を通した理由を coreutils を例に挙げて説明します.他のコマンド群も同様です.
-
$ brew install coreutilsでインストールされたコマンドにはgという prefix が付きます.たとえばlsはglsとしてインストールされます. -
/usr/local/opt/coreutils/libexec/gnubinには prefix を省いたコマンド名(たとえばls)から coreutils のコマンド(たとえばgls)にシンボリックリンクが張られています.したがって,ここに path を通したあとは,lsとタイプすれば coreutils のglsが実行されるようになります. - 以前は一部のコマンドに対しては
$ brew install時に--with-default-namesというオプションを設定し同様の効果を得ることができましたが,これは廃止されました.( @taku-ito5555 さんコメントありがとうございます.)
更新履歴
2019-04-01
-
--with-default-namesの削除への対応.( @taku-ito5555 さんコメントありがとうございます.) -
$ brew tap homebrew/dupesの削除への対応.( @taku-ito5555 さんコメントありがとうございます.) - Zsh 周りの設定ファイルの更新.( @GeneralD さんコメントありがとうございます.)
-
path_helperへの対策を/etc/zprofileの手動コメントアウトからsetopt GLOBAL_RCSに変更; - 配列変数
$path,$manpathの利用; -
typeset -Uの利用; -
(N-/)の利用.
-
- Bash 周りの設定ファイルの更新.
- awk 周りのコメントの更新.