ssh链接远程主机执行脚本的环境变量问题

用ssh命令ssh user@remote "/web/tomcat-7000/bin/startup.sh" 登录到远程机器remote上执行脚本时,遇到一个奇怪的问题:tomcat服务不能启动html

Neither the JAVA_HOME nor the JRE_HOME environment variable is defined
At least one of these environment variable is needed to run this programweb

 

问题分析&处理:shell

      一、将命令单独分开apache

          a、ssh远程登陆服务器   ssh user@remote编程

          b、手动执行脚本  /web/tomcat-7000/bin/startup.sh缓存

          c、执行成功tomcat

      二、为tomcat启动脚本配置环境变量bash

  在 /web/tomcat-7000/bin/startup.sh上配置添加      服务器

 

  添加tomcat环境变量app

  export MAVEN_HOME=/usr/local/apache-maven-3.3.3
  export JAVA_HOME=/usr/local/jdk
  export CROMOLOG_HOME=/usr/local/cronolog-1.6.2/
  export PATH=$CROMOLOG_HOME/sbin:$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH
  export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

     三、从新退出服务器,执行脚本,避免缓存 Ok

 

 

请参考如下ssh的模式

如下转发: http://www.kuqin.com/shuoit/20141113/343188.html

近日在使用ssh命令ssh user@remote ~/myscript.sh登录到远程机器remote上执行脚本时,遇到一个奇怪的问题:

~/myscript.sh: line n: app: command not found

app是一个新安装的程序,安装路径明明已经过/etc/profile配置文件加到环境变量中,但这里为什么会找不到?若是直接登录机器remote并执行~/myscript.sh时,app程序能够找到并顺利执行。但为何使用了ssh远程执行一样的脚本就出错了呢?两种方式执行脚本到底有何不一样?若是你也心存疑问,请跟随我一块儿来展开分析。

目录


说明,本文所使用的机器是:SUSE Linux Enterprise。

问题定位

这看起来像是环境变量引发的问题,为了证明这一猜测,我在这条命令以前加了一句:which app,来查看app的安装路径。在remote本机上执行脚本时,它会打印出app正确的安装路径。但再次用ssh来执行时,却遇到下面的错误:

which: no app in (/usr/bin:/bin:/usr/sbin:/sbin)

这很奇怪,怎么括号中的环境变量没有了app程序的安装路径?不是已经过/etc/profile设置到PATH中了?再次在脚本中加入echo $PATH并以ssh执行,这才发现,环境变量还是系统初始化时的结果:

/usr/bin:/bin:/usr/sbin:/sbin

这证实/etc/profile根本没有被调用。为何?是ssh命令的问题么?

随后我又尝试了将上面的ssh分解成下面两步:

user@local > ssh user@remote    # 先远程登录到remote上 user@remote> ~/myscript.sh      # 而后在返回的shell中执行脚本

结果居然成功了。那么ssh以这两种方式执行的命令有何不一样?带着这个问题去查询了man ssh

If command is specified, it is executed on the remote host instead of a login shell.

这说明在指定命令的状况下,命令会在远程主机上执行,返回结果后退出。而未指定时,ssh会直接返回一个登录的shell。但到这里仍是没法理解,直接在远程主机上执行和在返回的登录shell中执行有什么区别?即便在远程主机上执行不也是经过shell来执行的么?难道是这两种方式使用的shell有什么不一样?

暂时尚未头绪,但隐隐感到应该与shell有关。由于我一般使用的是bash,因此又去查询了man bash,才获得了答案。

bash的四种模式

在man page的INVOCATION一节讲述了bash的四种模式,bash会依据这四种模式而选择加载不一样的配置文件,并且加载的顺序也有所不一样。本文ssh问题的答案就存在于这几种模式当中,因此在咱们揭开谜底以前先来分析这些模式。

interactive + login shell

第一种模式是交互式的登录shell,这里面有两个概念须要解释:interactive和login:

login故名思义,即登录,login shell是指用户以非图形化界面或者以ssh登录到机器上时得到的第一个shell,简单些说就是须要输入用户名和密码的shell。所以一般无论以何种方式登录机器后用户得到的第一个shell就是login shell。

interactive意为交互式,这也很好理解,interactive shell会有一个输入提示符,而且它的标准输入、输出和错误输出都会显示在控制台上。因此通常来讲只要是须要用户交互的,即一个命令一个命令的输入的shell都是interactive shell。而若是无需用户交互,它即是non-interactive shell。一般来讲如bash script.sh此类执行脚本的命令就会启动一个non-interactive shell,它不须要与用户进行交互,执行完后它便会退出建立的shell。

那么此模式最简单的两个例子为:

  • 用户直接登录到机器得到的第一个shell
  • 用户使用ssh user@remote得到的shell

加载配置文件

这种模式下,shell首先加载/etc/profile,而后再尝试依次去加载下列三个配置文件之一,一旦找到其中一个便再也不接着寻找:

  • ~/.bash_profile
  • ~/.bash_login
  • ~/.profile

下面给出这个加载过程的伪代码:

execute /etc/profile IF ~/.bash_profile exists THEN     execute ~/.bash_profile ELSE     IF ~/.bash_login exist THEN         execute ~/.bash_login     ELSE         IF ~/.profile exist THEN             execute ~/.profile         END IF     END IF END IF

为了验证这个过程,咱们来作一些测试。首先设计每一个配置文件的内容以下:

 

1 user@remote > cat /etc/profile 2 echo @ /etc/profile 3 user@remote > cat ~/.bash_profile 4 echo @ ~/.bash_profile 5 user@remote > cat ~/.bash_login 6 echo @ ~/.bash_login 7 user@remote > cat ~/.profile 8 echo @ ~/.profile

 

而后打开一个login shell,注意,为方便起见,这里使用bash -l命令,它会打开一个login shell,在man bash中能够看到此参数的解释:

-l Make bash act as if it had been invoked as a login shell

进入这个新的login shell,便会获得如下输出:

@ /etc/profile @ /home/user/.bash_profile

果真与文档一致,bash首先会加载全局的配置文件/etc/profile,而后去查找~/.bash_profile,由于其已经存在,因此剩下的两个文件再也不会被查找。

接下来移除~/.bash_profile,启动login shell获得结果以下:

@ /etc/profile @ /home/user/.bash_login

由于没有了~/.bash_profile的屏蔽,因此~/.bash_login被加载,但最后一个~/.profile仍被忽略。

再次移除~/.bash_login,启动login shell的输出结果为:

@ /etc/profile @ /home/user/.profile

~/.profile终于熬出头,得见天日。经过以上三个实验,配置文件的加载过程获得了验证,除去/etc/profile首先被加载外,其他三个文件的加载顺序为:~/.bash_profile>~/.bash_login>~/.profile,只要找到一个便终止查找。

前面说过,使用ssh也会获得一个login shell,因此若是在另一台机器上运行ssh user@remote时,也会获得上面同样的结论。

配置文件的意义

那么,为何bash要弄得这么复杂?每一个配置文件存在的意义是什么?

/etc/profile很好理解,它是一个全局的配置文件。后面三个位于用户主目录中的配置文件都针对用户我的,也许你会问为何要有这么多,只用一个~/.profile很差么?究竟每一个文件有什么意义呢?这是个好问题。

Cameron Newham和Bill Rosenblatt在他们的著做《Learning the bash Shell, 2nd Edition》的59页解释了缘由:

bash allows two synonyms for .bash_profile: .bash_login, derived from the C shell’s file named .login, and .profile, derived from the Bourne shell and Korn shell files named .profile. Only one of these three is read when you log in. If .bash_profile doesn’t exist in your home directory, then bash will look for .bash_login. If that doesn’t exist it will look for .profile.

One advantage of bash’s ability to look for either synonym is that you can retain your .profile if you have been using the Bourne shell. If you need to add bash-specific commands, you can put them in .bash_profile followed by the command source .profile. When you log in, all the bash-specific commands will be executed and bash will source .profile, executing the remaining commands. If you decide to switch to using the Bourne shell you don’t have to modify your existing files. A similar approach was intended for .bash_login and the C shell .login, but due to differences in the basic syntax of the shells, this is not a good idea.

原来一切都是为了兼容,这么设计是为了更好的应付在不一样shell之间切换的场景。由于bash彻底兼容Bourne shell,因此.bash_profile.profile能够很好的处理bash和Bourne shell之间的切换。可是因为C shell和bash之间的基本语法存在着差别,做者认为引入.bash_login并非个好主意。因此由此咱们能够得出这样的最佳实践:

  • 应该尽可能杜绝使用.bash_login,若是已经建立,那么须要建立.bash_profile来屏蔽它被调用
  • .bash_profile适合放置bash的专属命令,能够在其最后读取.profile,如此一来,即可以很好的在Bourne shell和bash之间切换了

non-interactive + login shell

第二种模式的shell为non-interactive login shell,即非交互式的登录shell,这种是不太常见的状况。一种建立此shell的方法为:bash -l script.sh,前面提到过-l参数是将shell做为一个login shell启动,而执行脚本又使它为non-interactive shell。

对于这种类型的shell,配置文件的加载与第一种彻底同样,在此再也不赘述。

interactive + non-login shell

第三种模式为交互式的非登录shell,这种模式最多见的状况为在一个已有shell中运行bash,此时会打开一个交互式的shell,而由于再也不须要登录,所以不是login shell。

加载配置文件

对于此种状况,启动shell时会去查找并加载/etc/bash.bashrc~/.bashrc文件。

为了进行验证,与第一种模式同样,设计各配置文件内容以下:

 

1 user@remote > cat /etc/bash.bashrc 2 echo @ /etc/bash.bashrc 3 user@remote > cat ~/.bashrc 4 echo @ ~/.bashrc

 

而后咱们启动一个交互式的非登录shell,直接运行bash便可,能够获得如下结果:

@ /etc/bash.bashrc @ /home/user/.bashrc

由此很是容易的验证告终论。

bashrc VS profile

从刚引入的两个配置文件的存放路径能够很容易的判断,第一个文件是全局性的,第二个文件属于当前用户。在前面的模式当中,已经出现了几种配置文件,多数是以profile命名的,那么为何这里又增长两个文件呢?这样不会增长复杂度么?咱们来看看此处的文件和前面模式中的文件的区别。

首先看第一种模式中的profile类型文件,它是某个用户惟一的用来设置全局环境变量的地方, 由于用户能够有多个shell好比bash, sh, zsh等, 但像环境变量这种其实只须要在统一的一个地方初始化就能够, 而这个地方就是profile,因此启动一个login shell会加载此文件,后面由此shell中启动的新shell进程如bash,sh,zsh等均可以由login shell中继承环境变量等配置。

接下来看bashrc,其后缀rc的意思为Run Commands,由名字能够推断出,此处存放bash须要运行的命令,但注意,这些命令通常只用于交互式的shell,一般在这里会设置交互所须要的全部信息,好比bash的补全、alias、颜色、提示符等等。

因此能够看出,引入多种配置文件彻底是为了更好的管理配置,每一个文件各司其职,只作好本身的事情。

non-interactive + non-login shell

最后一种模式为非交互非登录的shell,建立这种shell典型有两种方式:

  • bash script.sh
  • ssh user@remote command

这两种都是建立一个shell,执行完脚本以后便退出,再也不须要与用户交互。

加载配置文件

对于这种模式而言,它会去寻找环境变量BASH_ENV,将变量的值做为文件名进行查找,若是找到便加载它。

一样,咱们对其进行验证。首先,测试该环境变量未定义时配置文件的加载状况,这里须要一个测试脚本:

 

1 user@remote > cat ~/script.sh 2 echo Hello World

 

而后运行bash script.sh,将获得如下结果:

Hello World

从输出结果能够得知,这个新启动的bash进程并无加载前面提到的任何配置文件。接下来设置环境变量BASH_ENV

 

1 user@remote > export BASH_ENV=~/.bashrc

 

再次执行bash script.sh,结果为:

@ /home/user/.bashrc Hello World

果真,~/.bashrc被加载,而它是由环境变量BASH_ENV设定的。

更为直观的示图

至此,四种模式下配置文件如何加载已经讲完,由于涉及的配置文件有些多,咱们再以两个图来更为直观的进行描述:

第一张图来自这篇文章,bash的每种模式会读取其所在列的内容,首先执行A,而后是B,C。而B1,B2和B3表示只会执行第一个存在的文件:

+----------------+--------+-----------+---------------+ |                | login  |interactive|non-interactive| |                |        |non-login  |non-login      | +----------------+--------+-----------+---------------+ |/etc/profile    |   A    |           |               | +----------------+--------+-----------+---------------+ |/etc/bash.bashrc|        |    A      |               | +----------------+--------+-----------+---------------+ |~/.bashrc       |        |    B      |               | +----------------+--------+-----------+---------------+ |~/.bash_profile |   B1   |           |               | +----------------+--------+-----------+---------------+ |~/.bash_login   |   B2   |           |               | +----------------+--------+-----------+---------------+ |~/.profile      |   B3   |           |               | +----------------+--------+-----------+---------------+ |BASH_ENV        |        |           |       A       | +----------------+--------+-----------+---------------+

上图只给出了三种模式,缘由是第一种login实际上已经包含了两种,由于这两种模式下对配置文件的加载是一致的。

另一篇文章给出了一个更直观的图:

Bash加载文件顺序

上图的状况稍稍复杂一些,由于它使用了几个关于配置文件的参数:--login--rcfile--noprofile--norc,这些参数的引入会使配置文件的加载稍稍发生改变,不过整体来讲,不影响咱们前面的讨论,相信这张图不会给你带来更多的疑惑。

典型模式总结

为了更好的理清这几种模式,下面咱们对一些典型的启动方式各属于什么模式进行一个总结:

  • 登录机器后的第一个shell:login + interactive
  • 新启动一个shell进程,如运行bash:non-login + interactive
  • 执行脚本,如bash script.sh:non-login + non-interactive
  • 运行头部有如#!/usr/bin/env bash的可执行文件,如./executable:non-login + non-interactive
  • 经过ssh登录到远程主机:login + interactive
  • 远程执行脚本,如ssh user@remote script.sh:non-login + non-interactive
  • 远程执行脚本,同时请求控制台,如ssh user@remote -t 'echo $PWD':non-login + interactive
  • 在图形化界面中打开terminal:
  • Linux上: non-login + interactive
  • Mac OS X上: login + interactive

相信你在理解了login和interactive的含义以后,应该会很容易对上面的启动方式进行归类。

再次尝试

在介绍完bash的这些模式以后,咱们再回头来看文章开头的问题。ssh user@remote ~/myscript.sh属于哪种模式?相信此时你能够很是轻松的回答出来:non-login + non-interactive。对于这种模式,bash会选择加载$BASH_ENV的值所对应的文件,因此为了让它加载/etc/profile,能够设定:

 

1 user@local > export BASH_ENV=/etc/profile

 

而后执行上面的命令,可是很遗憾,发现错误依旧存在。这是怎么回事?别着急,这并非咱们前面的介绍出错了。仔细查看以后才发现脚本myscript.sh的第一行为#!/usr/bin/env sh,注意看,它和前面提到的#!/usr/bin/env bash不同,可能就是这里出了问题。咱们先尝试把它改为#!/usr/bin/env bash,再次执行,错误果真消失了,这与咱们前面的分析结果一致。

第一行的这个语句有什么用?设置成sh和bash有什么区别?带着这些疑问,再来查看man bash

If the program is a file beginning with #!, the remainder of the first line specifies an interpreter for the program.

它表示这个文件的解释器,即用什么程序来打开此文件,就比如Windows上双击一个文件时会以什么程序打开同样。由于这里不是bash,而是sh,那么咱们前面讨论的都不复有效了,真糟糕。咱们来看看这个sh的路径:

 

1 user@remote > ll `which sh` 2 lrwxrwxrwx 1 root root 9 Apr 25  2014 /usr/bin/sh -> /bin/bash

 

原来sh只是bash的一个软连接,既然如此,BASH_ENV应该是有效的啊,为什么此处无效?仍是回到man bash,一样在INVOCATION一节的下部看到了这样的说明:

If bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible, while conforming to the POSIX standard as well. When invoked as an interactive login shell, or a non-interactive shell with the –login option, it first attempts to read and execute commands from /etc/profile and ~/.profile, in that order. The –noprofile option may be used to inhibit this behavior. When invoked as an interactive shell with the name sh, bash looks for the variable ENV, expands its value if it is defined, and uses the expanded value as the name of a file to read and execute. Since a shell invoked as sh does not attempt to read and execute commands from any other startup files, the –rcfile option has no effect. A non-interactive shell invoked with the name sh does not attempt to read any other startup files. When invoked as sh, bash enters posix mode after the startup files are read.

简而言之,当bash以是sh命启动时,即咱们此处的状况,bash会尽量的模仿sh,因此配置文件的加载变成了下面这样:

  • interactive + login: 读取/etc/profile~/.profile
  • non-interactive + login: 同上
  • interactive + non-login: 读取ENV环境变量对应的文件
  • non-interactive + non-login: 不读取任何文件

这样即可以解释为何出错了,由于这里属于non-interactive + non-login,因此bash不会读取任何文件,故而即便设置了BASH_ENV也不会起做用。因此为了解决问题,只须要把sh换成bash,再设置环境变量BASH_ENV便可。

另外,其实咱们还能够设置参数到第一行的解释器中,如#!/bin/bash --login,如此一来,bash便会强制为login shell,因此/etc/profile也会被加载。相比上面那种方法,这种更为简单。

配置文件建议

回顾一下前面提到的全部配置文件,总共有如下几种:

  • /etc/profile
  • ~/.bash_profile
  • ~/.bash_login
  • ~/.profile
  • /etc/bash.bashrc
  • ~/.bashrc
  • $BASH_ENV
  • $ENV

不知你是否会有疑问,这么多的配置文件,究竟每一个文件里面应该包含哪些配置,好比PATH应该在哪?提示符应该在哪配置?启动的程序应该在哪?等等。因此在文章的最后,我搜罗了一些最佳实践供各位参考。(这里只讨论属于用户我的的配置文件)

 
  • ~/.bash_profile:应该尽量的简单,一般会在最后加载.profile.bashrc(注意顺序)
  • ~/.bash_login:在前面讨论过,别用它
  • ~/.profile:此文件用于login shell,全部你想在整个用户会话期间都有效的内容都应该放置于此,好比启动进程,环境变量等
  • ~/.bashrc:只放置与bash有关的命令,全部与交互有关的命令都应该出如今此,好比bash的补全、alias、颜色、提示符等等。特别注意:别在这里输出任何内容(咱们前面只是为了演示,别学我哈)

写在结尾

至此,咱们详细的讨论完了bash的几种工做模式,而且给出了配置文件内容的建议。经过这些模式的介绍,本文开始遇到的问题也很容易的获得了解决。之前虽然一直使用bash,但真的不清楚里面包含了如此多的内容。同时感觉到Linux的文档的确作得很是细致,在彻底不须要其它安装包的状况下,你就能够获得一个很是完善的开发环境,这也曾是Eric S. Raymond在其著做《UNIX编程艺术》中提到的:UNIX天生是一个很是完善的开发机器。本文几乎全部的内容你均可以经过阅读man page获得。最后,但愿在这样一个被妖魔化的特殊日子里,这篇文章可以为你带去一丝帮助。

 

转发: http://www.kuqin.com/shuoit/20141113/343188.html

相关文章
相关标签/搜索