tkuchikiの日記

新ブログ https://blog.tkuchiki.net

ログインユーザ名を取得する

su コマンドで root になった場合などで、 ログインしたときのユーザ名を取得する方法です。

結論から書くと、logname(1) を使えば良いです。

$ ssh ec2-user@ec2-host
$ whoami
ec2-user
$ sudo su -
$ whoami
root
$ logname
ec2-user

Amazon Linux, CentOS 6, Ubuntu 14.04、Max OSX では期待通りの動作でした。

その他の方法

logname 以外にも幾つか方法があります。

SUDO_USER 環境変数

sudo su [ユーザ名] で別のユーザになった場合は、
SUDO_USER という環境変数にユーザ名が格納されます。

$ sudo su
$ env | grep SUDO
SUDO_USER=ec2-user
SUDO_UID=500
SUDO_COMMAND=/bin/su
SUDO_GID=500

他にも UID や GID も取れるようです。
ただ、これはログインしたユーザ名を取るのではなく、
sudo したユーザ名を取るので、
root になって更に別のユーザになった場合などはログインユーザ名を取れません。

$ whoami
ec2-user
$ sudo su
$ echo $SUDO_USER
ec2-user
$ sudo su ec2-user
$ echo $SUDO_USER
root

また、sudo su - [ユーザ名] とした場合は、
su したあとのシェルがログインシェルになってしまうためか、 SUDO_USER などが設定されていませんでした。

$ sudo su -
$ env | grep SUDO

SUDO_USER は sudo したユーザ名を取る用途以外に使わないほうが良さそうです。

who am i (who -m) を使う

whoami(1) は現在のユーザ名を取得するコマンドですが、
who am i(who -m)

-m only hostname and user associated with stdin

となります(who(1))。
実際に実行してみると、以下のようになります。

$ who am i
ec2-user pts/0        2015-07-28 02:01 (ec2-xxx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com)

man によると、

If ARG1 ARG2 given, -m presumed: 'am i' or 'mom likes' are usual.

ということですので、引数を2つ与えると who -m を実行するようです(who foo bar などでも動作しました)。
こちらは SUDO_USER と違って、cut や awk で切り出せばログインユーザ名を取得できますが、
Mac OSX ではログインユーザ名を取得できませんでした。
man を見てみると、

-m Only print information about the current terminal. This is the POSIX way of saying who am i.

となっています。
実際に実行してみると、

$ whoami
tkuchiki
$ sudo su -
$ who am i
root         ttys001  Jul 28 11:49

のようになります。

FreeBSD Man Pages の who(1) を見ると、

-m Show information about the terminal attached to standard input only.

となっているので、FreeBSD だといけるのかもしれません(未検証)。

おまけ

getlogin works on debian but not ubuntu? を見ていると、
getlogingetpwuid でログインユーザ名が取れるそうです。
同名のライブラリ関数があるので、色々な言語にも同等の機能を持った関数がありそうです。

まとめ

  • SUDO_USER は sudo したユーザ名を取る用途以外に使わないほうが良さそう
  • who am i は環境によっては意図した動作にならない
  • logname を使える環境であれば logname を使うのが楽そう

ec2 instance の name tag と private ip を /etc/hosts に追記する

aws ec2 describe-instances が返す json を parse して、
/etc/hosts に private ip, name tag を追記するスクリプトです(name tag を hostname としてる場合を想定しています)。
aws configure をしている前提で書いていますが、
そうでない場合は、適宜 region, access key, secret key を引数に取れば動くはずです。
fp.write の部分を print に書き換えて表示するだけにしたり、
i.get('InstanceId') を追加して、instance id を表示することもできます。

現在の shell を shellscript の中から確認する(Linux, BSD 両対応)

現在の shell(以下、current shell) を確認する方法として真っ先に思い浮かぶのは以下だと思います。

$ echo $SHELL
/bin/bash

ただ、これは不正解で、$SHELL はあくまでも login shell ですので、
login shell と current shell が違う場合は正しい値を返してくれません。
私の場合ですと、ローカル環境で常時利用しているのは zsh ですが、
login shell は bash です(zsh を login shell にすると重いと教えていただいたことがあるので)。

このようなケースでも正しい値を返す方法が以下です。

$ echo $0
-zsh

shell 上では $0 に current shell が格納されています。
これで解決すれば良いのですが、shellscript 内で確認しようと思うと話しは変わってきます。
shellscript 内で $0 には、script 名が格納されています。
shell 上での利用よりも、shellscript 内での利用頻度の方が高いでしょうからこれでは困ります。

色々調べていたら、現在使っているシェルの名前を知る方法 - 揮発性のメモ を発見しました。
いろいろ参考になりました、ありがとうございます。

readlink /proc/$$/exe

の説明をすると、
Linux の /proc/PID 以下には、プロセスに関する情報が色々入っていて、

  1. $$ で shellscript 自身の PID を取得して、
  2. シンボリックリンクの実体のパスを返す readlink に、
  3. コマンドのパスのシンボリックリンクが含まれている /proc/$$/exe を渡して、
  4. shellscript を実行している shell を取得する

となります。

これで解決かと思いましたが、以下の欠点があります。

  • /proc が Linux にしかない(たぶん)
  • 実行した時の shell になるので、current shell とは違う

2点目について説明すると、
shebang に #!/bin/bash と指定してスクリプトを実行したり、
bash /path/to/script のように実行すると、zsh を使っていても /bin/bash が返ってくる、ということです。

今回は、あくまでも current shell を取得したいのでこれでは要件を満たせません。

解決策

実行した shellscript の親プロセスは current shell になっているのではないか、
と当たりをつけて実験したらできました。

以下がスクリプトです。
2つ目のスクリプトは、活用例です。

1つずつ説明すると、

_PID=$$

で Shellscript 自身の PID を取得して、

_PPID=$(ps -o ppid -p $_PID | tail -n 1)

で、Shellscript 自身の親PID(PPID)を出力しています。
これを更に分解すると、

$ ps -o ppid -p $_PID
PPID
12345

のような出力になるので、

tail -n 1

で 2 行目だけ出力しているようにしています。
--no-headers を使っていない理由は BSD の ps になかったからです。

あとは、親PIDが取れたので、

ps -p $_PPID

で PID 指定で ps しています。
これで 実行した shellscript の親プロセスの情報を取得出来ました。

zsh を使っている状態でこれを実行すると、

$ ./get_current_shell.sh
  PID TTY          TIME CMD
12345 pts/1    00:00:00 zsh

となります。
header を消したければ、同じ要領で tail をかませればよいでしょう。

あとは、grep -qs zsh にマッチすれば zshgrep -qs bash にマッチすれば bash
といった判定が可能になります。

まとめ

login shell と current shell が違う場合や、
実行時の shell と current shell が違う場合でも、current shell を返す方法を示しました。
それほど使い道はないかもしれませんが、
.bashrc や .zshrc を読み直させたり追記したいときに、
login shell と current shell が違う場合でも、適切なファイルに対して処理が行えるでしょう。
Linux だけでなく BSD でも動くようにしましたので、汎用的に使えるのではないかと思います。

追記1

@hnakamur2 さんに、awk で 2行目をだけを出力するよりも、
tail -n 1 のほうがシンプルだとご指摘いただきましたので修正しました。
ご指摘ありがとうございます!
Linux, BSD で検証して動作することを確認済みです。

# 訂正前
_PPID=$(ps -o ppid -p $_PID | awk 'NR==2 { print }')

# 訂正後
_PPID=$(ps -o ppid -p $_PID | tail -n 1)

追記2

@songmu さんからご指摘頂きました。
ありがとうございます!

shellscript(親shellscript) の中で shellscript(子shellscript) を呼び出す場合は、
子shellscript から見た親プロセスは、親shellscript となってしまいます。
したがって、正しく動作しません。

現状では、まだ解決できていませんが、
current shell 判定処理を関数化して別ファイルにし、

. get_current_shell.sh

get_current_shell

のように読み込んで、子shellscript から呼ばないようにするしかありません。
解決できたら追記致します。

find でディレクトリに symlink が含まれるときはパスの末尾に / をつけるか、-L をつける

$ tree -p /tmp/
/tmp/
├── [lrwxrwxrwx]  foobar -> /tmp/hoge
└── [drwxr-xr-x]  hoge
    ├── [-rw-r--r--]  hogege
    └── [-rw-r--r--]  hogehoge

2 directories, 2 files

というディレクトリ構成の時に、

$ find /tmp/hoge
/tmp/hoge
/tmp/hoge/hogege
/tmp/hoge/hogehoge

は、問題ないが、

$ find /tmp/foobar
/tmp/foobar

symlink の先が出力されない。
パスの末尾に / をつけるか、-L をつければ、symlink の先が出力される。
LinuxMac で検証したが、どちらも同じ結果だったので、
この件に関しては、LinuxBSD で find の挙動とオプションの違いは無いようだ。

$ find /tmp/foobar/
/tmp/foobar/
/tmp/foobar/hogege
/tmp/foobar/hogehoge

$ find -L /tmp/foobar
/tmp/foobar
/tmp/foobar/hogege
/tmp/foobar/hogehoge

symlink が含まれる可能性があるのならば、常にどちらかをつけておくと良いと思う。
ちゃんと調べていないが、つけていることによる弊害はないはず(問題があったら申し訳ないです...)。

※追記

@hnakamur2 さんからご指摘頂きましたが、Mac OS X (10.8.5, 10.9.2 で検証)だと、

$ find /tmp/foobar/
/tmp/foobar/
/tmp/foobar//hogege
/tmp/foobar//hogehoge

末尾に / をつけると、指定したディレクトリに / が2つ付いてしまう。
-L だとつかないので、find に渡したパスに / をつける実装になっているのだと思われる。
殆どの場合は問題無いと思われるが、パスを / で分解して処理するときにハマる可能性がある。
現実的ではないが、例を挙げる。
cut でファイルを抜き出そうとした場合、

# Linux
find /tmp/foobar/ | cut -d / -f 4

hogege
hogehoge

Linux では問題ないが、

# BSD
$ find /tmp/foobar/ | cut -d / -f 4


BSD だと抜き出す位置がずれてしまう(// の間になるので "" になる)。

したがって、LinuxBSD 両方で動かしたいスクリプトの場合は、
-L を指定するのが無難

Shellscript で指定回数ループするコードのベンチマーク

Shellscript で 指定回数ループする場合を調べていたら、
while でインクリメントしていくか、for in seq の2 パターンが見つかった。
どちらが高速なのか疑問に思ったのでベンチマークを取った。

while

code

#!/bin/sh
i=0
while [ $i -ne 10000 ]; do
  i=`expr $i + 1`
  echo $i
done

result

$ time ./while.sh
real    0m20.557s
user    0m4.466s
sys     0m10.371s

for

code

#!/bin/sh

for i in `seq 1 10000`; do
  echo $i;
done

result

$ time ./for.sh
real    0m0.188s
user    0m0.132s
sys     0m0.041s

考察

while が圧倒的に遅いことがわかった。
この結果から、while で行っている変数代入、expr、while 自体、のいずれかが遅いと考えられる。
そこで、この3つのどこに原因があるのか調査した。

while 自体が遅い?

これはほとんどないと思われるが一応ベンチをとる。

code

seq の結果をパイプで渡して、read すれば for in seq と大体同等の処理になると思われる(多分)。

seq 1 10000 | while read i; do
  echo $i
done
result
$ time ./seq_while.sh
real    0m0.239s
user    0m0.184s
sys     0m0.061s

read の分で for より少し遅くなっていると思われるが、誤差の範囲内である。

変数代入

code

seq_while.sh に 代入を加えただけ。

seq 1 10000 | while read i; do
  hoge=$i
  echo $i
done
result
$ time ./assign.sh
real    0m0.326s
user    0m0.250s
sys     0m0.092s

seq_while.sh より少し遅くなったくらいで、これも原因ではなさそうだ。

expr

消去法で、これが原因だとわかる。

結論

expr は遅いので、指定回数ループを回すときには使わないほうが良い。
また、expr 自体コストが高いようなので四則演算を頻繁に行う場合は、perl などに行わせたほうが良いのだと思う。

dstat で expr のループを見ていたがそこそこ CPU を喰っていたので、CPU リソースの観点からも使わないほうが良いと思われる。

おまけ

以下のような perl script もベンチをとってみた。

code

for my $i (1..10000) {
  print $i + 1, "\n";
}

result

$ time perl ./loop.pl
real    0m0.044s
user    0m0.017s
sys     0m0.026s

四則演算しても、shellscript より perl のほうが速い。

2014/04/08 追記

id:hirata_yasuyuki さんにコメントいただいた、$(()) (以下、算術式展開)のベンチマークもとってみました。
id:hirata_yasuyuki さん、ありがとうございます。
以前の環境が思い出せないので、expr と for seq を再度実行して、結果の比較を行った。

expr

code

while.sh と同じ

result

real    0m17.375s
user    0m4.588s
sys     0m8.534s

for seq

code

for.sh と同じ

result

real    0m0.207s
user    0m0.154s
sys     0m0.028s

算術式展開

code

#!/bin/sh
i=0
while [ $i -ne 10000 ]; do
  i=$(($i+1))
  echo $i
done

result

real    0m0.291s
user    0m0.254s
sys     0m0.035s

結論

ご指摘いただいた通り、算術式展開を使ったほうが圧倒的に速い。
また、for seq とも大差ない結果だった。
ただ、私の手元にある以下の書籍には、
BSDの一部の sh で一部使用できない演算子があったり、Solaris の sh だとそもそも使えない?らしく、移植性を重視したい場合は、expr を使ったほうが良いと記されていました(共に未検証)。

[改訂新版] シェルスクリプト基本リファレンス  ??#!/bin/shで、ここまでできる (WEB+DB PRESS plus)

[改訂新版] シェルスクリプト基本リファレンス  ??#!/bin/shで、ここまでできる (WEB+DB PRESS plus)

しかし、Linux 上で使うだけであれば、算術式展開で十分だと思われる。

nginx で sslを設定 & オレオレ証明書の作成

ローカル環境で ssl の動作環境を構築する際のメモ。

オレオレ証明書の作成

openssl genrsa -out SERVER_NAME.key 2048
openssl req -new -key SERVER_NAME.key -out SERVER_NAME.csr
openssl rsa -in SERVER_NAME.key -out SERVER_NAME.key
openssl x509 -req -days 3650 -in SERVER_NAME.csr -signkey SERVER_NAME.key -out SERVER_NAME.crt

上から順にコマンドの説明を書くと、

  1. 秘密鍵の作成
  2. 公開鍵の作成
  3. パスフレーズなしにする
  4. 証明書作成(3650 は有効期間(日))

となる。

nginx に ssl 設定

server {
    listen 443 ssl;
    ssl on;
    ssl_certificate /path/to/SERVER_NAME.crt;
    ssl_certificate_key /path/to/SERVER_NAME.key;
}

簡単にディレクティブの説明を書くと、

listen IPアドレスとポートの指定(いずれかを省略可)
ssl ssl の on, off
ssl_certificate PEM証明書ファイルのパス
ssl_certificate_key PEMプライベートキーファイルのパス

※あとで、Apacheの設定方法も書こう。nginx に比べると面倒だし。。。

Mac で cp を使うときの注意

MacでFinderからドラッグ&ドロップしてディレクトリを上書きしようとすると置き換えられてしまう。
そこで、

cp -rf src dest


のように実行したところ、
まったく上書きされていない。。。

Macのマニュアルを見てみると、

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.

     The -v and -n options are non-standard and their use in scripts is not


BSDの日本語マニュアル(On-line Manual of "cp")も載せておくと、

互換性
     従来版の cp には -r オプションがありました。本実装でもこのオプションはサ
     ポートされていますが、特殊ファイル・シンボリックリンク・FIFO などを正しく
     コピーできないため、これを使用することは奨められません。

     -v および -n は標準ではありませんし、スクリプト中での使用はお勧めしません。


とのこと。
BSDの英語マニュアル(Man Page for cp (freebsd Section 1) - The UNIX and Linux Forums)を見てみたら、若干文章が違うけれど、とりあえず気にしないでおく。
正しく使う場合は、"-R" を使えばOKのようだ。

あと、同僚に教えていただいたのだが、
"-a" でも再帰的にコピーできるようだ。
ずっと "-a" はユーザとグループを維持したままコピーだと思っていた。。。

確かにマニュアルと見ると、

GNU

      -a, --archive
              same as -dR --preserve=all

       -d     same as --no-dereference --preserve=links

       -P, --no-dereference
              never follow symbolic links in SOURCE

       -p     same as --preserve=mode,ownership,timestamps

       --preserve[=ATTR_LIST]
              preserve the specified attributes (default:  mode,own-
              ership,timestamps), if possible additional attributes:
              context, links, xattr, all

       --no-preserve=ATTR_LIST
              don’t preserve the specified attributes

       -R, -r, --recursive
              copy directories recursively

BSD

     -a    Same as -pPR options. Preserves structure and attributes of files
           but not directory structure.

     -P    If the -R option is specified, no symbolic links are followed.  This
           is the default.

     -p    Cause cp to preserve the following attributes of each source file in
           the copy: modification time, access time, file flags, file mode,
           user ID, and group ID, as allowed by permissions.  Access Control
           Lists (ACLs) and Extended Attributes (EAs), including resource
           forks, will also be preserved.

           If the user ID and group ID cannot be preserved, no error message is
           displayed and the exit value is not altered.

           If the source file has its set-user-ID bit on and the user ID cannot
           be preserved, the set-user-ID bit is not preserved in the copy's
           permissions.  If the source file has its set-group-ID bit on and the
           group ID cannot be preserved, the set-group-ID bit is not preserved
           in the copy's permissions.  If the source file has both its set-
           user-ID and set-group-ID bits on, and either the user ID or group ID
           cannot be preserved, neither the set-user-ID nor set-group-ID bits
           are preserved in the copy's permissions.

     -R    If source_file designates a directory, cp copies the directory and
           the entire subtree connected at that point.  If the source_file ends
           in a /, the contents of the directory are copied rather than the
           directory itself.  This option also causes symbolic links to be
           copied, rather than indirected through, and for cp to create special
           files rather than copying them as normal files.  Created directories
           have the same mode as the corresponding source directory, unmodified
           by the process' umask.


再帰的にコピーしたい場合、ユーザとグループを維持したままでよければ "-a" 、
維持したくなければ "-R" を使えば概ねOK。

ここまで書いていて思ったが、シンボリックリンクとかで問題が出ることはわかったが、
ファイルが全く上書きされなかった理由にはなっていない気がする。。。
階層が深すぎるとだめなのか?
ただ、今回のように全く上書きされなかったのはかなり特殊なケースだと思われる(これ以外で同様のケースに遭遇したことがない)。

色々探してみたら、こちらの記事の方がよくまとめられている(manに「cp -rは使うな」と書いてあった話 - 西尾泰和のはてなダイアリー)