.bashrc内にechoを入れておくとscpに失敗する

# はじめに
ターミナルでbashを実行した際に読み込まれるファイルは.bashrcや.bash_profileなどあるが、ここでは.bashrcを対象にする。これはbashの設定ファイルであり、PATHを通したりaliasをかけたりとできるが、ここである書き方をすると、scpに失敗する。これまで個人で使用する分にはDropboxやファイルサーバを使うなどして取り立てるほどの問題ではなかったが、今回調べて見たのでその結果をまとめる。

なお、要約すると、以下のとおりである。

- 非対話型モードでechoなど実行するとscpのセッションが壊れる
- 非対話型モードにechoなどを回避するように記述する

## .bashrcを編集した理由
当時Unix系システムを勉強し始めたばかりであり、さらに複数の端末を有していた私は、

- ログイン時にどんなファイルを読み込んだか常に分かるようにしていた
- どのマシンにログインしたのか、ターミナル上ですぐわかるようにしておきたい

との理由で、.bashrcに以下のような内容を書き連ねていった。

```bash:.bashrc
function read_usr_bashrc(){
        echo 'read /Users/hikaru/.bashrc...'
}


read_usr_bashrc
```
このマシンでterminalを起動すると、次のように表示される。

```text
Last login: Sun Dec 23 10:31:41 2018
read /Users/hikaru/.bashrc
[hikaru@Judith ~]$ 
```

なお、このとき

```bash:.profile
if [ -r $HOME/.bashrc ]; then
        source $HOME/.bashrc
fi
```

として読み込んでいる。

これでリモートログインしても、どの端末にログインしてどんなファイルを読み込んだのかがすぐに分かる。当初の目的は達成された、はずだった。

## scpの失敗
端末間でファイルをやり取りすることはよくあるが、Unix系のシステムを使っていると、USBメモリなどではなく、network経由で簡単に済ませてしまいたい。しかし、これを実際にやろうとすると、

```text
[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の記事](https://stackoverflow.com/questions/12440287/scp-doesnt-work-when-echo-in-bashrc)を見つけた。
ここを内容をまとめて、以下に述べる。

## Bashの仕様について
まずbash 3.2.57のman pageを確認する。

```text
[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を書き換えて検証した。

```bash:.profile@Judith
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を実行する。

```text
[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の先頭に書き込めば良い。

```bash
if [[ $- != *i* ]]; then return; fi
```

あるいは

```bash
if [ -z "$PS1" ]; then
    return
fi 
```

これで解決できた。