はじめに
ターミナルでbashを実行した際に読み込まれるファイルは.bashrcや.bash_profileなどあるが、ここでは.bashrcを対象にする。これはbashの設定ファイルであり、PATHを通したりaliasをかけたりとできるが、ここである書き方をすると、scpに失敗する。これまで個人で使用する分にはDropboxやファイルサーバを使うなどして取り立てるほどの問題ではなかったが、今回調べて見たのでその結果をまとめる。
なお、要約すると、以下のとおりである。
- 非対話型モードでechoなど実行するとscpのセッションが壊れる
- 非対話型モードにechoなどを回避するように記述する
.bashrcを編集した理由
当時Unix系システムを勉強し始めたばかりであり、さらに複数の端末を有していた私は、
- ログイン時にどんなファイルを読み込んだか常に分かるようにしていた
- どのマシンにログインしたのか、ターミナル上ですぐわかるようにしておきたい
との理由で、.bashrcに以下のような内容を書き連ねていった。
function read_usr_bashrc(){
echo 'read /Users/hikaru/.bashrc...'
}
read_usr_bashrc
このマシンでterminalを起動すると、次のように表示される。
Last login: Sun Dec 23 10:31:41 2018
read /Users/hikaru/.bashrc
[hikaru@Judith ~]$
なお、このとき
if [ -r $HOME/.bashrc ]; then
source $HOME/.bashrc
fi
として読み込んでいる。
これでリモートログインしても、どの端末にログインしてどんなファイルを読み込んだのかがすぐに分かる。当初の目的は達成された、はずだった。
scpの失敗
端末間でファイルをやり取りすることはよくあるが、Unix系のシステムを使っていると、USBメモリなどではなく、network経由で簡単に済ませてしまいたい。しかし、これを実際にやろうとすると、
[hikaru@Ava ~]$ scp hikaru@Judith:~/hoge.txt .
Password:
read /Users/hikaru/.bashrc...
[hikaru@Ava ~]$ ls -la hoge.txt
ls: hoge.txt: No such file or directory
[hikaru@Ava ~]$
となってファイルを転送できない。これでは困る。
scpについて
真面目に調べて見るとstackoverflowの記事を見つけた。
ここを内容をまとめて、以下に述べる。
Bashの仕様について
まずbash 3.2.57のman pageを確認する。
[hikaru@Judith ~]$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
Copyright (C) 2007 Free Software Foundation, Inc.
[hikaru@Judith ~]$ man bash
(中略)
When bash is invoked as an interactive login shell, or as a non-inter-
active shell with the --login option, it first reads and executes com-
mands from the file /etc/profile, if that file exists. After reading
that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile,
in that order, and reads and executes commands from the first one that
exists and is readable. The --noprofile option may be used when the
shell is started to inhibit this behavior.
##要約##
# bashは対話型or非対話型にかかわらず、/etc/profileを読み込む
# その後 ~/.bash_profile, ~/.bash_login, ~/.profileの順に探索し、最初に見つけたものを読み込む。
######
(中略)
When an interactive shell that is not a login shell is started, bash
reads and executes commands from ~/.bashrc, if that file exists. This
may be inhibited by using the --norc option. The --rcfile file option
will force bash to read and execute commands from file instead of
~/.bashrc.
##要約##
# bashがログインシェルではない場合、bashを実行すると~/.bashrcを読み込む。
######
(中略)
Bash attempts to determine when it is being run by the remote shell
daemon, usually rshd. If bash determines it is being run by rshd, it
reads and executes commands from ~/.bashrc, if that file exists and is
readable. It will not do this if invoked as sh. The --norc option may
be used to inhibit this behavior, and the --rcfile option may be used
to force another file to be read, but rshd does not generally invoke
the shell with those options or allow them to be specified.
##要約##
# リモートシェルデーモンが走った場合、~/.bashrcのみを読み込む。
######
(以下略)
Login shell がbashとすると、
- 自端末でログイン: Login shellがbashなので、/etc/profile -> ~/.profile (さらに読み出している場合 -> ~/.bashrc)
- sshでログイン: Login shellがbashなので、/etc/profile -> ~/.profile (さらに読み出している場合-> ~/.bashrc)
- scpでファイル転送: remote shell daemonが実行され、~/.bashrcのみが実行
sshとscpの比較検証
下記のように.profileを書き換えて検証した。
if [ -r $HOME/.bashrc ]; then
source $HOME/.bashrc
fi
function read_usr_profile(){
echo 'read '$HOME'/.profile'
}
function echo_machine_name(){
echo '-----------------------------------------------'
echo ' ██╗██╗ ██╗██████╗ ██╗████████╗██╗ ██╗ '
echo ' ██║██║ ██║██╔══██╗██║╚══██╔══╝██║ ██║ '
echo ' ██║██║ ██║██║ ██║██║ ██║ ███████║ '
echo ' ██ ██║██║ ██║██║ ██║██║ ██║ ██╔══██║ '
echo ' ╚█████╔╝╚██████╔╝██████╔╝██║ ██║ ██║ ██║ '
echo ' ╚════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ '
echo '-----------------------------------------------'
}
read_usr_profile
echo_machine_name
この条件下で、sshとscpを実行する。
[hikaru@Ava ~]$ ssh hikaru@192.168.179.112
Password:
Last login: Mon Dec 24 18:23:47 2018 from 192.168.179.127
read /Users/hikaru/.bashrc...
read /Users/hikaru/.profile
-----------------------------------------------
██╗██╗ ██╗██████╗ ██╗████████╗██╗ ██╗
██║██║ ██║██╔══██╗██║╚══██╔══╝██║ ██║
██║██║ ██║██║ ██║██║ ██║ ███████║
██ ██║██║ ██║██║ ██║██║ ██║ ██╔══██║
╚█████╔╝╚██████╔╝██████╔╝██║ ██║ ██║ ██║
╚════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
-----------------------------------------------
[hikaru@Judith ~]$ exit
logout
Connection to 192.168.179.112 closed.
[hikaru@Ava ~]$ scp hikaru@192.168.179.112:~/hoge.txt .
Password:
read /Users/hikaru/.bashrc...
[hikaru@Ava ~]$
確かに、sshで接続した際は.profileから読み込んでいるが、scpで接続したときは.bashrcのみ読み込まれた。
scpの仕様について
scpのman pageにある通り、scpはrcpをもとに開発されたプログラムである。
rcpは.profileなどの中で何らかの出力があるとうまく動作しないことがバグとして報告されており、scpにおいても、出力が実行されるうまくいかない。
scpを利用する際の回避策
scp実行時に呼び出されるのは.bashrcのみであるから、このファイルの書き方を気にすれば良い。
.bashrcの中でechoなど出力を伴う処理を除去
最も簡単な手法で、出力さえ伴わなければ上記のバグを回避できる。
対話型モードか否かの判定文を使用
これは、bashのman pageにあるPS1 is set and $- includes i if bash is interactive, allowing ashell script or a startup file to test this state.
を元に、以下のいずれかを.bashrcの先頭に書き込めば良い。
if [[ $- != *i* ]]; then return; fi
あるいは
if [ -z "$PS1" ]; then
return
fi
これで解決できた。