MacはBSD UNIXベースなのでGNU Linuxのコマンドと比べて 同じコマンドでも少し振舞いが違う所があります。
これまでGNU Linuxだけ使ってきてFree BSD等使ったことが無いので、 基本的にGNU系のコマンドに慣れていて、 たまにMacでコマンドを打つと予想外の事が起きて戸惑っています。
今回、特に気になっていたcp
についてwrapper関数(bash用)を作って
Mac上でもGNU的な振る舞いにするようにしてみました。
cp Wrapper関数
次の様な関数を定義して.bashrc
等に書いておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
cpがGNU
のものでない場合これらのwrapper関数を適用します。
特に気になったのが、コピーする元のディレクトリの指定が
最後に/
を付けた状態だとディレクトリではなく、中身だけ
持ってきてしまうことです。
(GNUの場合は/
あるなしに関わらずディレクトリそのものがコピーされる。)
Tabで補完してディレクトリ名を入力すると/
まで補完されるので、
ディレクトリをコピーしたい場合はいちいち消さないといけないし、
忘れると中身をコピーしてきてしまって、
中身が大量のディレクトリの場合には酷いことになります。
元からあったファイルと区別して移動し直すのも大変ですが、 同じファイル名の物があった日には上書きされてしまってどうしようもないことに…
GNU Linuxと両方使ってる場合はとっても危険なのでとても気をつけないといけません。
通常、ディレクトリ内の物を移動したい時はdir/*
使う様にします。
ただこれだとdir
内の.
で始まる隠しファイルは移動出来ません。
BSDコマンドの場合だと、
$ cp -R ../dir/ ./
とすれば/dir
の中身が.
で始まるファイルも含めてコピー出来るのですが、
GNUと合わせて使いたいのでこの機能は封印することになります。
(意図してない限り知らないうちに.git/
等も含めて複製したりすることになるので、
この機能を普段から意図的に使ってない場合はむしろ混乱の元になるかと。)
もう1つの変更が-r
オプションで、GNUの場合は-r
は-R
と同様、単なる
recursive
としてのオプションですが、
BSD
の場合はmanしてみると
COMPATIBILITY
Historic versions of the cp utility had a -r option. This implementation
supports that option; however, its use is strongly discouraged, as it
does not correctly copy special files, symbolic links, or fifo's.
の様に、使わないように、となっています。
これまで普通にcp -r
してきて上の/
問題以外には余り気付かなかったのですが、
特にディレクトリ内にシンボリックリンクがある場合に微妙な振る舞いをします。
GNU cpだとcp -r
で他のオプション無しだと中にあるシンボリックリンクを
シンボリックリンクとしてコピーしますが、BSD cpだとシンボリックの
元の実態をコピーしようとします。
一方でcp -R
とすればGNUのcp -r
の様な振る舞いになります。
基本的に-r
としている場合でもシンボリックリンクはシンボリックリンクとして
コピーされるものだと身に付いているので、これも-R
に変換しておきます。
シンボリックリンクを追うかどうかは-L/P
でオン/オフ出来ますが、
BSDでは-r
はこれらのオプションとは同時に使えないため、
やはり-r
は使うべきでは無いようです。
ちょっと使ってみた感じ、問題なく動いているのでしばらく使ってみようと思っていますが、
cp
という頻繁に使うコアなコマンドなので、もし使う際には自己責任で注意して使って下さい。
おまけ
cp以外にも色々と細かい所で違いがあってたまにイラッとしますが (まだ単に気づいてないだけの所も多々あると思いますが…)、 気になった所でGNU/BSDの互換性をもたせようと思って使っている物をついでに紹介しておきます。
sed
sed
もGNUとBSDでちょっと違う所があります。
ファイルを直接書き換えるコマンドで
$ sed -i 's/foo/bar/g' test.txt
とGNU sedですると、test.txt
の中のfoo
がbar
に変換されます。
-i
に続いて.bak
等とサフィックスを与えれば元のファイルがtest.txt.bak
と言う風にバックアップされます。
これがBSD sedだと
$ sed -i 's/foo/bar/g' test.txt
sed: 1: "test.txt": command a expects \ followed by text
等と怒られます。BSDの場合では-i
に続いて必ずサフィックスを指定しなくてはならず、
バックアップが要らない場合でも空の""
を与える必要があります。
それぞれman
してみるとGNUの場合:
-i[SUFFIX], --in-place[=SUFFIX]
edit files in place (makes backup if extension supplied)
BSDの場合:
-i extension
Edit files in-place, saving backups with the specified extension.
If a zero-length extension is given, no backup will be saved. It
is not recommended to give a zero-length extension when in-place
editing files, as you risk corruption or partial content in situ-
ations where disk space is exhausted, etc.
となっています。BSDの方では親切にno backupは危ないよ、と言う思想で、
どうしてもと言うなら意図的に""
を与えなさい、と言う感じ。
前置きが長くなりましたが、これの対処としてはsed
にWrapper関数かけるのは
色々複雑すぎて面倒なので、 aliasで別コマンドにして処置します。
ただ、この""
を与える際、GNU sedの場合、-i
と離すと(sed -i ""
とすると)次の引数として
取られるのでエラーが出ます。
一方、BSD sedの場合、-i
とくっつけて渡すと(sed -i""
とすると)エラーになります。
これらも考慮して、
if sed --version 2>/dev/null |grep -q GNU;then
alias sedi='sed -i"" '
else
alias sedi='sed -i "" '
fi
といったものを.bashrc
に書いておいてファイル内のfoo
をすべてbar
にしたい場合、
$ sedi 's/foo/bar/g' test.txt
としています。(これで余計なファイルを作らずに変換出来ます。)
その他のsed
での違い:
tail: ファイルの中身を逆さにする
BSDのtail
コマンドには-r
オプションがあり、
$ cat << EOF > test.txt
> abc
> def
> 123
> EOF
$ cat test.txt
abc
def
123
$ tail -r test.txt
123
def
abc
$
こんな感じでファイルの中身を逆にします。 引数を与えない場合は標準入力を変換します。
$ cat test.txt|tail -r
123
def
abc
一方、GNU tailには-r
がありません。
ただし、大概のLinuxにはGNUのtac
と言うコマンドが入っていてこれがtail -r
と同じ
働きをします(BSDにはtail -r
があるからtac
は無い?)。
これら気にせず使うために、これもaliasで
# Revert lines in the file/std input
if ! type tac >& /dev/null && \
! tail --version 2>/dev/null|grep -q GNU;then
alias tac='tail -r'
fi
こんな感じでBSDの場合でもtail -r
をtac
で呼べる様にしてあります。
余談ですが、これを最初考えてる時、
if type tac >/dev/null 2>&1;then
alias rev="tac"
elif ! tail --version 2>/dev/null |grep -q GNU;then
alias rev="tail -r"
fi
こんな感じでrev
(reverse)と言う名前で統一しようかと思ったのですが、
実はrev
と言うコマンドは存在していることにこの時気づきました。
各行の文字列をそれぞれ反転される、というコマンドです。
(LinuxにもMacにもありました。)
実行すると
$ rev test.txt
cba
fed
321
の様に、各行が逆から書かれています。 正直、このコマンドを使う場面が余り思いつかないし、 使っているところを見たことありませんが、 一応潰さないように。
まとめ
(おまけの方が長くなった…)
MacにもGNUコマンド入れれば良いじゃん、と言ってしまえばそれまでですが、
設定ファイル(.bashrc
)1つで手軽に変えられる方法、と言うことで。
cp
に関してはcp
なんて使わずrsync
でしょ、とか、find
+cpio
だろ、とか、
さらにはtar
使うでしょ、という話もある様ですが
1、
通常のファイルコピー同様cp
で慣れてるのでまだcp
使いたいな、と。
そのうち他の物も気になったら加えて行こうとおもっていますが、 もし他にもクリティカルな違いがあれば教えて頂けるとうれしいです。