第3章.GNUとUnixのコマンド

 

GNUパッケージとしてディストリのLinuxにマージされてるソフトは、

bash、lsとかmvとかcpなどの定番コマンド。

まずbashの複雑な制御フローを理解して、

そのあとで、「"hoge"」とか、「' fuga'」とか、「`puni`」とか、「(heke)」とか、「{puppni}」

のような、各種引用符や各種括弧で、展開される変数やコマンドなどの制御構造を理解できると思うんご。

bashはもはやプログラム開発言語並みの高機能だからおいらは勝手にbashは開発言語って思うことにしてるんご。

bashの本来の用途であるコマンドラインインタプリタに着目すると、コマンドの実行環境を提供するが本来の目的のために、いろんな引数やオプションのパターンをもったコマンドの引数、オプション、パイプ、リダイレクト、bash文法に含まれるifやforなどのループで文を書くと、それぞれの文字列がコマンドの一部なのか、bash文法の一部のなのかまぎらわしく、それを区別するための引用符や各種括弧が非常にわかりづらい。

つまり他のプログラム開発言語とちがってコマンドラインインタプリタ特有のnestがややこしい。

bashの予約語は英数字とアンダースコア以外ほとんどの文字や記号が該当してる。

このようにプログラム開発言語並みに高機能かしたコマンドラインインタプリタであるbashの正しい使い方を覚えるとか慣れることはすでに破たんしてると思う。

おいら個人的な感想では、bashの複雑な制御フローを理解するしかないと思うんご。

そのためには、bashのソースコードを見るのが常套手段だけど、無理ゲーすぎるので、

せめて、manか、bash作者の書いたオライリーのbash本からbashコードの制御フローを理解するのが遠回りなようで近道な気がするんご。

 

C言語とshはUNIXといっしょに生まれたプログラム開発言語で、UNIXからいろんなものを相続してるLinuxとshを改良したbashは他のプログラム開発言語の例外で、

「Linuxの仕様」の一部という認識で取り組んだ方がすっきりするように思えてきたんご。

つまり、Linuxはモノリシックカーネルのほかに、bashとc言語そのものの仕様がモノリシックに結合したソフトウェアと考えて、さらに、x86アーキテクチャとCPU命令と合わせて、

下記の6つ

・Linux kernel

・C言語およびC標準ライブラリ

・bash

・GNUツール

・x86(x86-64)アーキテクチャ

・CPU命令セット(intelとAMD)

のコンポーネントが密結合した一塊の技術 

と捉えるのが一番無難だとおもうんご

 

ところでLinux試験ではGNUツール系コマンド群やbashは、「Linuxの一部」であるかのように編成されてて、おいらの上記で述べた感想と一致するんだけど、それでも、GNUツールとbashとkernelは別のプログラム部品であるという2面性を認識する目線も併せて持ちたいと思うんご。

C言語でコンパイルしたオブジェクトファイルにアセンブリオブジェクトファイルをリンクして作られたプログラムではあるけど、kernelとGNUツールなどのユーザモードプログラムでは、cpuレベルで違うモードで動作するプログラムであるという目線も併せて持ちたいんご


№33,36,38

■cp,mv,rmコマンドの特徴

・centos6ではcoreutilsパッケージ(GNUツールの一つ)に含まれている

・デフォルトは-fオプション。ただし、centos6ではデフォルトで、-iオプションをつけた

「cp -i」,「mv -i」,「rm -i」がそれぞれcp,mv,rmのエイリアスに設定されている。

# alias
alias cp='cp -i'
alias l.='ls -d .* --color=tty'
alias ll='ls -l --color=tty'
alias ls='ls --color=tty'
alias mv='mv -i'
alias rm='rm -i'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

あるいは、

# which mv
alias mv='mv -i'
        /bin/mv

で確認できる。

※例えば、mvコマンドに何のオプションもつけないで実行すると、mv -iのエイリアスがmvに設定されているので、-fオプション以外の他のオプションでも基本的に上書きするかどうか聞かれて「y」を押さないと移動はキャンセルになる。

<20170422追記>

一般ユーザでは、aliasに「cp -i」,「mv -i」,「rm -i」が設定されてなかったんご

$ alias
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias vi='vim'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

rootユーザの/root/.bashrcには、下記のように「cp -i」,「mv -i」,「rm -i」などのalias​​​​​​​が設定されている。

# egrep 'cd|mv|rm|alias' /root/.bashrc
# User specific aliases and functions
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

一方、一般ユーザの/home/hoge/.bashrcには、

$ egrep 'cd|mv|rm|alias' /home/hoge/.bashrc
# User specific aliases and functions

のように「cp -i」,「mv -i」,「rm -i」などのalias​​​​​​​は設定されていないんご

/etc/skel/.bashrcにも当然「cp -i」,「mv -i」,「rm -i」などのalias​​​​​​​は設定されていない。

一方、一般ユーザとrootの「ls」や「which」などのaliasは、下記の通り/etc/profile.d内でaliasが設定されている。

$ grep alias /etc/profile.d/* | grep -v "csh:"
/etc/profile.d/colorls.sh:  alias ll='ls -l' 2>/dev/null
/etc/profile.d/colorls.sh:  alias l.='ls -d .*' 2>/dev/null
/etc/profile.d/colorls.sh:alias ll='ls -l --color=auto' 2>/dev/null
/etc/profile.d/colorls.sh:alias l.='ls -d .* --color=auto' 2>/dev/null
/etc/profile.d/colorls.sh:alias ls='ls --color=auto' 2>/dev/null
/etc/profile.d/vim.sh:  # for bash and zsh, only if no alias is already set
/etc/profile.d/vim.sh:  alias vi >/dev/null 2>&1 || alias vi=vim
/etc/profile.d/which2.sh:alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

つまり、「cp -i」,「mv -i」,「rm -i」などのalias​​​​​​​は設定はrootユーザだけ特別に設定されていた。

<20170422追記/>

 

※sudoでcp,mv,rmを実行する場合は、aliasでなく、元のコマンドが実行されるので、デフォルトとして振る舞い、上書きするか聞かずにいきなり上書きされる。
$ sudo -u suzuki mv hoge fuga/

→上書きするか聞かずにいきなり上書きされる

エイリアスでなくもとのmvコマンドを実行する場合には、

$ \mv hoge fuga/

のようにmvコマンドの前に「\」を付けて実行する

この場合、-iオプションが効かなくなり、-fオプションを付けた場合のように、何も聞かずに上書きされる。

・下記をやらかすと世界が終わる

# rm -rf / hoge/fuga 👈最初の「/」とhogeの間に半角スペースが入っちゃってる

 

■mvコマンドのオプション

※タイムスタンプは移動前後で不変

-b :ファイルの移動によって、移動先のファイルが削除や上書きされる場合にバックアップを作成

例えば、移動先にhogeという名前のファイルがあって、同名のファイルを移動すると、移動先にあったhogeはhoge~というファイル名にリネームされたあと、hogeが移動先にコピーされて、移動元のhogeが削除される

-f :移動先に同名のファイルまたはディレクトリがあっても上書きするかどうか確認せずに上書きする(デフォルト)

-i :移動先に同名のファイルまたはディレクトリがある場合には上書きするかどうか確認

-u :移動先に同名のファイルがある場合、移動元のファイルの最終更新日が新しいときのみ上書き

移動元の最終更新日が新しくない場合は何も表示されず、上書きもされない。

-v :実行結果を表示

※下記のコマンドで、A、Bどっちもディレクトリ名のとき

$ mv A B

もし、BがすでにあったらBの下にAが移動する(B/A)

もし、Bが無かったらAディレクトリがBディレクトリにリネームされる

※symlink と mvコマンド

下記のコマンドで、A、B のいずれかあるいはいずれもがsymlinkだったとき

$ mv A B

・Aが実ファイルでBがファイルへのsymlink

→Aというファイル名が無くなり、Aと同じ内容の実ファイルBが生成した。

Aのsymlinkがリンク切れになった。

BのsymlinkだったB' はAの内容の実ファイルになった。

・Aが実ファイルでBがAへのsymlink

→Aというファイル名が無くなり、Aと同じ内容の実ファイルBが生成した。

BはsymlinkだったAは実ファイルになった。

・AがファイルのsymlinkでBが存在しない

→AがBという名前のsymlinkにリネームされた

・AがファイルのsymlinkでBが実ファイル

→Bという名のsymlinkが無くなり、B(symlink)がAという名前のsymlinkにリネームされた

・AがファイルのsymlinkでBがファイルのsymlink

→Bという名のsymlinkが無くなり、B(symlink)がAという名前のsymlinkにリネームされた

・AがファイルのsymlinkでBが実ディレクトリ

→AがsymlinkとしてB配下に移動

symkinkとして移動されたAが相対参照のリンクならリンク切れになる場合がある

・AがファイルのsymlinkでBがディレクトリのsymlink

→Aがsymlinkとして「Bのリンク元ディレクトリ」の配下に移動

symkinkとして移動されたAが相対参照のリンクならリンク切れになる場合がある

・Aが実ディレクトリでBがディレクトリへのsymlink

→Aが「Bのリンク元ディレクトリ」の配下に移動

・AがディレクトリのsymlinkでBが存在しない

→AがBという名前のsymlinkにリネームされた

・AがディレクトリのsymlinkでBが実ディレクトリ

→AがsymlinkとしてB配下に移動

symkinkとして移動されたAが相対参照のリンクならリンク切れになる場合がある

・AがディレクトリのsymlinkでBがディレクトリのsymlink

→Aがsymlinkとして「Bのリンク元ディレクトリ」の配下に移動

symkinkとして移動されたAが相対参照のリンクならリンク切れになる場合がある

■cpコマンドのオプション

※タイムスタンプはコピーすると変わる(当然)

-i :コピー先に同名のファイルがある場合、上書きするか確認

-p :所有者、グループ、アクセス権、タイムスタンプを保持してコピー

-r(-R) :ファイルだけでなく、ディレクトリも再帰的にコピー

※symlink と cpコマンド

下記のコマンドで、A、B のいずれかあるいはいずれもがsymlinkだったとき

$ cp A B

・Aが実ファイルでBがファイルへのsymlink

→「Bのリンク元の実ファイル」がAで上書き

・AがファイルのsymlinkでBが存在しない

→実ファイルBが生成

・AがファイルのsymlinkでBが実ファイル

→実ファイルBが「Aのリンク元の実ファイル」で上書き

・AがファイルのsymlinkでBがファイルのsymlink

→「Bのリンク元の実ファイル」が「Aのリンク元の実ファイル」で上書き

・AがファイルのsymlinkでBが実ディレクトリ

→B配下に 「Aのリンク元の実ファイル」と同じ内容の実ファイルがファイル名「A」で生成。

つまり、コピー元の「A」はsymlink名でコピー先の「A」は実ファイル名!

・AがファイルのsymlinkでBがディレクトリのsymlink

→「Bのリンク元の実ディレクトリ」配下に 「Aのリンク元の実ファイル」と同じ内容の実ファイルがファイル名「A」で生成。

・Aが実ディレクトリでBがディレクトリへのsymlink  (-rオプション必須)

→「Bのリンク元の実ディレクトリ」配下にAがコピー

・AがディレクトリのsymlinkでBが存在しない (-rオプション必須)

→「Aのリンク元の実ディレクトリ」を指すBという名のsymlinkが生成

つまり、Bはsymlinkとしてコピーされた。

symkinkとしてコピーされたBが相対参照のリンクならリンク切れになる場合がある

・AがディレクトリのsymlinkでBが実ディレクトリ (-rオプション必須) 

→A(symlink)がBの下に生成

symkinkとしてコピーされたAが相対参照のリンクならリンク切れになる場合がある

・AがディレクトリのsymlinkでBがディレクトリのsymlink (-rオプション必須)

→A(symlink)が「Bのリンク元の実ディレクトリ」の下に生成

symkinkとしてコピーされたAが相対参照のリンクならリンク切れになる場合がある

 

■rmコマンド

-f :実行時に削除するかどうか聞かない

-i :実行時に削除するか確認

-r(-R、--recursive) :指定したディレクトリの中をすべて再帰的に削除

※symlink と rmコマンド

$ rm A

・Aがファイルのsymlink

→symlinkとしてのAが削除された。 Aのリンク先はそのまま

・Aがディレクトリのsymlink

→symlinkとしてのAが削除された。 Aのリンク先はそのまま

※rm -rf A も同じ。Aのリンク先はそのまま

 

■隠しファイルの移動(コピー)

・ディレクトリごと移動(コピー)する場合、ディレクトリ内の隠しファイルはどうなるか?

→すべて移動された

・ディレクトリ内のファイルをワイルドカードを指定して移動(コピー)を行う場合、隠しファイルはどうなるか?

→隠しファイルは移動されなかった。

$ mv ./hoge/* ./fuga を実行した場合、hogeディレクトリ内のファイルのうち隠しファイル以外がすべてfugaディレクトリの下に移動する。

もし、hogeディレクトリ内の隠しファイルも移動する場合には、上記のほかに下記も実行する必要がある。

$ mv ./hoge/.* ./fuga

 

■ハードリンクのコピー(移動)

・ハードリンク数が3のファイルをコピー(移動)した場合はハードリンク数が4になるのか、まったく別のinode番号が振り出されて、同名のファイルがハードリンク数1で作成されるのか

 →コピーした場合は別のinode番号が振りだされて同名のファイルがハードリンク数1で作成される。移動した場合は移動先に同じinode番号のファイルがリンク数3のまま移動する。

ただし、違うファイルシステム(「/」→「/boot」)に移動した場合はinode番号が振り出されて同名ののファイルがハードリンク数1で作成され、移動元のハードリンク数が1減る

→考えてみれば当然

 

【疑問】

・cp -pやmvコマンドでは、ファイル属性が保持されるけど、拡張ファイル属性やACLが設定されているファイルシステムどうしでコピー(移動)をする場合、これらの属性も保持されるのか・違うファイルシステム(xfsやBtrFSなど)の場合はどう変わるのか

・ディストリごとに、GNUツールのバージョンの違い、ディストリ固有のカスタマイズ、エイリアスのデフォルト設定はどう異なるか

rm、cp、mvコマンドのほかに、やらかすと後の祭り系コマンドとして下記についても上記のようなテーマでそれぞれ調べたい

tar(とくに展開時)、同じくcpio、rsync、dd(単純コピーで使う場合)、リダイレクト(cat > file名とか)、vimなど

 

 

№37

■lsコマンドの特徴

・centos6ではcoreutilsパッケージ(GNUツールの一つ)に含まれている

・エイリアスは

# which ls
alias ls='ls --color=tty'
        /bin/ls

・ディレクトリとは、直下のファイルとサブディレクトリの名前とinode番号だけを記述したたけのファイル

■lsコマンドのオプション

-a :隠しファイルを含むすべてのファイルとディレクトリを表示(.と..を含む)

-A :隠しファイルを含むすべてのファイルとディレクトリを表示(.と..を含まない) 

-d :ディレクトリ内の情報ではなく、ディレクトリ名を一覧表示

-F :実行ファイルに「*」、ディレクトリに「/」、シンボリックリンクに「@」を付けて一覧表示

-i :ファイルにinode番号を付けて一覧表示

-R :サブディレクトリの内容も再帰的に一覧表示

-1 :ファイルやディレクトリを1列で表示

 

【課題】lsコマンドなどの主なcoreutilesに含まれるコマンドのソースコード、共有ライブラリ、ヘッダファイル、シンボル名(関数名、外部変数名)、システムコール(直接呼んでることはないと思うのでリンクしてる共有ライブラリかスタティックライブラリ内の関数名から判断)を理解することで、サーバアプリ、GNU系以外のコマンド、ミドルウェアなどのユーザモードプログラムがカーネルと連携する部分の基本を理解できそう。

もちろん、ミドルウェアやサーバアプリケーションの中には特殊なカーネルモジュールと連携して、特殊なシステムコールを発行しているものもあるかもしれないが