ラベル bash の投稿を表示しています。 すべての投稿を表示
ラベル bash の投稿を表示しています。 すべての投稿を表示

2013年7月21日日曜日

fileコマンドがテキストファイルでエラーになる問題の回避

昨日、WindowsやLinuxで作成したテキストファイルの文字コーディングを調べようとしたら、fileコマンド( /usr/bin/file )でエラーがでました。
fileコマンドは、ファイルの音声や画像などの種類や、テキストファイルの場合文字エンコーディングなどを表示してくれるコマンドです。
やりたかったのは、UTF-8以外のファイルを探して、必要ならばUTF-8に変換しようと思ったのですが。

例えば以下の内容のファイル(UTF-8)をfileコマンドで調べると、ERRORとなります。(OS X 10.8.4)


$ cat test.txt
- 外部キー
  他のテーブルの主キーとなるキー
  外部キーは表の結合に用いる
od -c -t x1 test.txt
0000000    -      外  **  **  部  **  **  キ  **  **  ー  **  **  \n    
           2d  20  e5  a4  96  e9  83  a8  e3  82  ad  e3  83  bc  0a  20
0000020       他  **  **  の  **  **  テ  **  **  ー  **  **  ブ  **  **
           20  e4  bb  96  e3  81  ae  e3  83  86  e3  83  bc  e3  83  96
0000040   ル  **  **  の  **  **  主  **  **  キ  **  **  ー  **  **  と
           e3  83  ab  e3  81  ae  e4  b8  bb  e3  82  ad  e3  83  bc  e3
0000060   **  **  な  **  **  る  **  **  キ  **  **  ー  **  **  \n    
           81  a8  e3  81  aa  e3  82  8b  e3  82  ad  e3  83  bc  0a  20
0000100       外  **  **  部  **  **  キ  **  **  ー  **  **  は  **  **
           20  e5  a4  96  e9  83  a8  e3  82  ad  e3  83  bc  e3  81  af
0000120   表  **  **  の  **  **  結  **  **  合  **  **  に  **  **  用
           e8  a1  a8  e3  81  ae  e7  b5  90  e5  90  88  e3  81  ab  e7
0000140   **  **  い  **  **  る  **  **  \n                            
           94  a8  e3  81  84  e3  82  8b  0a                            
0000151
$ file test.txt
test.txt: ERROR: line 22: regexec error 17, (illegal byte sequence)

fileコマンドで、regexec関数を呼び出していて、その関数が不正なバイトの並びを見つけたというエラーのようです。test.txtは3行しかありませんので、行番号の22は、コマンドかライブラリのソースコードの行番号でしょう。
以前は、このようなエラーを見たことがなかったのですが、いつから出るようになったのでしょうか。

いろいろ試行錯誤したところ、localeの環境変数(LANGなど)を消すかCにすると正常に表示されるようになりました。

echo $LANG
ja_JP.UTF-8
$ LANG=C file test.txt
test.txt: UTF-8 Unicode text
$ unset LANG
$ file test.txt
test.txt: UTF-8 Unicode text

あまり、めでたしめでたしではないですが、暫定的には解決できました。

2013年6月9日日曜日

bashのsequence expressionをfor文で使おうとしたら

bashにsequence expressionというものがあります。
例えば、{1..5}と書くと1 2 3 4 5に展開されます。
$ echo {1..5}
1 2 3 4 5
ファイル名の一部に連番がある場合にファイル名の生成に使ったり、for文の範囲指定に使えます。
例えば、
for i in {1..5}; do
  echo $i
done
と書くと、実行結果は
1
2
3
4
5
となります。ところが、範囲指定のどちらかに変数になっていると期待通りにはなりません。
N=5
for i in {1..$N}; do
  echo $i
done
では、結果は
{1..5}
となって、sequence expressionが表示されます。ブレース展開({}を用いた式の展開)は、bashの実行の最初期に行われ、その後変数展開が行われるためとのことです。
どうしても使いたい場合は、以下のように{1..5}となる部分をもう一度評価する必要があります。

N=5
for i in `eval echo {1..$N}`; do
  echo $i
done
の実行結果は
1
2
3
4
5
となります。最初に考えた{1..$N}より随分長くなってしまいます。これなら、以前のseqコマンドを使った書き方の

N=5
for i in `seq 1 $N`; do
  echo $i
done

が短いですね。
seqは外部コマンドで、eval, echoはbashの内部コマンドなので多少evalの方が軽いかもしれませんが、使う状況を考えるとseqのままで良さそうです。

2013年2月14日木曜日

bashのキーバインドで、control + pキーと上矢印キーの機能を変えると便利

bashの設定で、意外と便利に使えるキーバインドを紹介します。

control + p, n キーや上下矢印で、シェル上で入力したコマンド履歴を遡ったり戻ってきたりして使うことも多いと思います。
私は、control + p, nキーはそのままコマンド履歴をひとつひとつ上下するようにしていますが、上下矢印キーは以下の設定に変更しています。
~/.bashrcファイルなどbash起動時に読み込まれるファイルに以下を記述します。

# 上矢印キー
bind '"\e[A": history-search-backward'
# 下矢印キー
bind '"\e[B": history-search-forward'

このコマンドは、コマンド履歴を検索するようにキーバインドの変更をしています。
例えば、コマンドを途中まで入力して上矢印キーを押すと、その入力した文字列で始まるコマンド履歴を辿ってくれます。
具体的な例を示します。
コマンド履歴が以下のようになっているとします。

100  man info
101  port info
102  port outdated
103  sudo port selfupdate
104  port outdated
105  port -R -y upgrade ruby19
106  port dependents ruby19
107  sudo port -v upgrade ruby19 2>&1 | tee ruby19.txt
108  ls
109  less ruby19.txt
110  cp ruby19.txt trash
111  cd trash
112  ls
113  rm trash
114  cd -


ここで、コマンド行に"c"と入力し、上矢印キーを押していくと順に
cd -
cd trash
cp ruby19.txt trash

と表示されます。
また、はじめに"cp"と入力し、上矢印キーを押すといきなり

cp ruby19.txt trash
の行が表示されます。
同様に、"s"キーを押してから上矢印キーを押すと
sudo port -v upgrade ruby19 2>&1 | tee ruby19.txt
の行が表示されるので、左右キーで移動して修正して別のMacPortsのportをアップグレードしたり等の繰り返しが簡単になります。基本的には、一連のコマンドシーケンスを繰り返す時に、スクリプトを書くほどのものではないという程度の時に重宝しています。
慣れると手放せなくなりますよ。

2013年2月12日火曜日

bashのcompletionでtabキー1回で候補が出るようにする

tcshからbashにデフォルトのシェルを変えて、不満だった点のひとつに、補完(completion)時に、tabキー一発で候補が出なかったことが挙げられます。

completion機能とは、ターミナルでコマンドやファイル名を打ち込んでいる途中でtabキーやescキーを押すことにより、残りの部分や、候補を表示してくれる機能です。

例えば、compと打ってtabキーを2回押すと
$ comp<tab><tab>
compare      compileHelp  complete     composite    
compgen      compile_et   compopt      compress     
$ comp
と表示されます。<tab>はtabキーを押すことに対応したつもりです。

ここで、lと打ち込み、tabキーを押すと以下のようになります。
$ compl<tab>
$ complete

上では、2行に分けて書いていますが、実際はtabキーを押した瞬間に、残りの"ete"が自動的に追加され、キー入力が軽減されます。

ところが、以前 tcsh を使っていた時は、最初のcomp<tab><tab>の部分が、comp<tab>とtabキー1回の押打で済んでいたので、結構ストレスになっていました。
当初、bashのキーバインド機能やcompletion機能をmanページ等で調べていましたが、なかなか解決にたどりつけず、諦めかけていたのですが、やっと解決方法がわかりました。(既に数年前の話ですが)

結論ですが、~/.inputrcに以下の記述を追加することで解決しました。

set show-all-if-ambiguous on

これで、tabキー一回でcompletionが効くようになりました。

2013年1月31日木曜日

PATH環境変数の設定はどうなっている

PATH変数に定義するディレクトリの並びについて書きましたので、ターミナルにログインした時に、PATH変数がどのように設定されるのかについて書きたいと思います。一応、bashを使っている場合です。

大体、以下の流れになります。

  1. ターミナル.appが立ち上がる際にされる設定
  2. /etc/profileで行われる設定
  3. /etc/bashrcで行われる設定
  4. ユーザの~/.bash_profileで行われる設定
  5. ユーザの~/.bashrcで行われる設定

ターミナル.appが立ち上がる際にされる設定

ターミナル.appをDockなどから立ち上げる際に、launchd関係でPATH設定をしているとPATH環境変数が設定されます。他にもあるかもしれません。個人的には、bash本来の/etc/profile以降だけで設定される方が好きです。

/etc/launchd.conf - launchd経由で立ち上がるプロセスに適用されます。setenv PATH path_name1:path_name2:... のようにcsh系と似た記述で指定します。
launchd.plist - 使っていないのでわかりませんが、manをみるとEnvironmentVariables <dictionary of strings> で、XMLフォーマットでPATHを指定できそうです。対象は、launchd本体、Terminal.appなどでしょうか。

また、ターミナル.app自体の環境設定で特別なコマンドを指定しているとそこでPATHが指定される可能性もありますね。この場合は、あえて指定しているので問題ないと思いますが。

その後、ターミナルが立ち上がると、ウインドウが開きシェルが起動されます。デフォルトでは/usr/bin/login経由で、/bin/bashが立ち上がります(変更等していない場合)。以下にbashの場合を説明します。

/etc/profileで行われる設定

bashは、起動すると/etc/profileを実行します。デフォルトでは、その中でpath_helperが実行され、/etc/pathsに記述してあるディレクトリをPATHに設定します。その後、/etc/paths.dに記述してあるファイルをアルファベット順に読んで、中に記述してあるディレクトリをPATHに追加していきます。PATHの前の部分に挿入することはできないようです。paths.d内のファイル名が"-" (マイナス)等で始まる場合は、前の部分に付加できるなどの仕様になっているといいと思ったのですが。

/etc/profileの中から/etc/bashrcが実行されます。

/etc/bashrcで行われる設定

デフォルトでは/etc/bashrcでは、特にPATHの設定は行われていないようです。
次から、ユーザの~/.bash_profileが実行されます。

~/.bash_profileで行われる設定

.bash_profile以降は、ユーザがどのように書いたかに大きく依存しますので、あまり解説しても意味が無いかもしれません。ここでは私の場合を説明します。
私の.bash_profileでは、それ自身では細かい設定は行わず、.bashrc, .profileをそれぞれ呼び出しているだけです。

~/.bashrcで行われる設定

私は、ここでPATH変数の頭に~/bin, ~/local/bin、/usr/local系、/opt/local系を付加しています。また、PATHの最後にclamXavを追加しています。

~/.profileで行われる設定

私は、.profileは使っていませんでした。

この記事を書いていて、.bashrcにあまりよくない記述も見つかったので直したくなってきました。よさそうな修正ができたらこのブログにも書きたいと思います。

2013年1月29日火曜日

PATH環境変数内の、~/bin, /bin, /usr/bin, /usr/local/binなどの順番

ターミナルでシェルを使う場合の小ネタです。初心者には重要かもしれません。知っている人は今さらと思うかもしれませんが。
私は、以前はtcshを使っていました。Mac OS X 10.6.2までターミナルのデフォルトのシェルだったからです。Tiger (10.6.4)の時からbashにしました。乗り換えが1世代遅れたのは、使い方に若干慣れないところがあったのと、.tcshrcから.bashrc, .bash_profileへの変更が面倒だったためです。シェルスクリプトは/bin/shで書くことにしています。

今回の本題は、tcsh, bashのどちらにも関係するのですが、PATH環境変数内に設定するディレクトリの順番です。別の考え方もあると思いますので、参考ということで。

PATH環境変数は、コマンドを探すときのディレクトリの順番を指定するものです。PATH変数に含まれていないディレクトリにあるコマンドを指定しても、"コマンドが見つかりません"といったエラーがでます。

$ chdir
-bash: chdir コマンドが見つかりません

ディレクトリにコマンドが存在するのに上のようなエラーが出る時は、PATH環境変数にそのディレクトリが含まれていない(PATHが通っていないと言いますね)か、そのコマンドに実行権がないか、csh系だとrehashしていないという場合が多いです。(自分の経験では)

現在の私のPATH環境変数は、以下のようになっています。

$ echo $PATH
/Users/karuku/bin:/Users/karuku/local/bin:/usr/local/bin:/opt/local/bin:/usr/local/sbin:/opt/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/usr/local/clamXav/bin

基本的な考え方は、特殊なもの、局所的なものを先頭に持ってくる、標準的なものは後ろにするというふうにしています。一部、その考え方に沿っていないところもありますが。

定義順にそれぞれの使用目的を書いてみます。

/Users/karuku/bin - ホームディレクトリ下のbinです。自分で作成したスクリプト類を置いています
/Users/karuku/local/bin - テスト目的でインストールしたいものを置いています
/usr/local/bin - MacPortsでサポートされていないコマンドを置いています
/opt/local/bin - MacPortsでサポートされているコマンドを置いています
/usr/local/sbin - MacPortsでサポートされていないシステムコマンドを置いています
/opt/local/sbin - MacPortsでサポートされているシステムコマンドを置いています
/usr/bin - 一般的なユーティリティ、プログラミングツール、アプリケーション
/bin - ユーザ用ユーティリティの基本的なもの。シングルユーザ環境、マルチユーザ環境どちらでも使用する
/usr/sbin - (ユーザによって実行される) システムデーモン、およびシステムユーティリティ
/sbin - (ユーザによって実行される) システムデーモン、およびシステムユーティリティ
/opt/X11/bin - XquartzでインストールされるX Window System
/usr/local/clamXav/bin - ウィルスチェッカー clamav / clamXavをインストールしています。

( /binや/usr/binなどのシステムディレクトリ説明にはjman hierの記述を使いました。)

最後のふたつの /opt/X11/binと/usr/local/clamXav/binは、最初の考え方の原則に反していますね。

/opt/X11/bin (X11)は、Xquartsをインストールした際に、/etc/paths.dに登録されました。ここに登録されると、ログイン時に/etc/profile内で、その時のPATH変数にアペンドされる形になるようです。
X11は、以前Mac OS Xに使いたいGUIアプリケーションが少なかったころや、会社でLinuxマシンを使う際に使っていましたが、最近は全く使っていません。LinuxかFreeBSDを使うこともなさそうなので、当面このままにするつもりです。

clamXavは、ターミナルからではなく、Finderから使う場合がほとんどなので、PATHから外してもいいかもしれないですね。

コマンド名がぶつかる場合

たまに、同名のコマンドがそれぞれのディレクトリにある場合があります。上の考え方の場合、通常は使いたいコマンドが先に来ているので問題ないのですが、たまに後のディレクトリのコマンドをデフォルトとして使いたい場合があります。
例えば、rsyncはAppleの提供しているバージョンとMacPortsでインストールされるバージョンでは、HFS+関係の属性等を扱うオプションが違います。MacPortsのrsyncを使おうと思った訳ではなく、依存関係でインストールされたのですが。

こういう場合は以下のようにしています。

シェルスクリプトの場合は、以下のようにフルパスで記述します。

RSYNC=/usr/bin/rsync
RSYNC -hfsv ...

コマンドラインから使う場合は、~/bin から使いたいバージョンのコマンドにシンボリックリンクを張ります。~/binは先頭にあるので、意識せずに使いたいバージョンのコマンドが使えます。

$ ln -s /usr/bin/rsync ~/bin

別の解決方法として、alias が考えられますがあまり使っていません。特に意識していませんでしたが、bashの場合、whichコマンドでaliasで定義したコマンドが表示されないから(方法を知らないだけかもしれませんが)というような理由です。
また、bashに移行した初めの頃は、tcshも併用していたので、~/binの一箇所に設定しておけばいいからという理由もあったかもしれません。(ちょっとうろ覚えです)