sshexec 蚂蚁任务:环境变量
Posted
技术标签:
【中文标题】sshexec 蚂蚁任务:环境变量【英文标题】:sshexec ant task: environment variables 【发布时间】:2009-07-02 13:37:01 【问题描述】:我正在使用 SSHExec ant 任务连接到远程主机,并且我依赖于在远程主机上设置的环境变量才能成功执行某些命令。
<sshexec host="somehost"
username="$username"
password="$password"
command="set"/>
使用环境中的任务。输出的变量与我使用 SSH 客户端登录时得到的变量不同。
如何制作环境。会话可用的远程主机的变量?
【问题讨论】:
【参考方案1】:实际上,您可以对它不启动 shell 的事实采取一些措施。使用以下内容:
<ssshexec command="/bin/bash -l yourScript.sh" .../>
使用 /bin/bash -l 将启动一个登录 shell,然后在该 shell 中执行您的脚本。就好像您有一个可以正确启动登录 shell 的 sshexec 版本一样。它必须是一个脚本。如果你想运行单个可执行命令,你可以这样做:
<sshexec command="/bin/bash -l -c 'echo $CATALINA_HOME'" .../>
【讨论】:
【参考方案2】:我发现当前的 SSHExeceec 任务实现是使用 JSCh 的 ChannelExec(远程执行命令)而不是 ChannelShell(远程 shell)作为连接通道。
这意味着显然根据 JSCh 的当前实现,ChannelExec 不会加载环境。变量。
我仍然不确定这是对协议还是 API 的限制。
结论是,目前还没有解决问题的办法,除非你实现自己的 Ant 任务。
一份工作草案:
package org.apache.tools.ant.taskdefs.optional.ssh;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.StringReader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.resources.FileResource;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.KeepAliveOutputStream;
import org.apache.tools.ant.util.TeeOutputStream;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
/**
* Executes a command on a remote machine via ssh.
* @since Ant 1.6 (created February 2, 2003)
*/
public class SSHExecShellSupport extends SSHBase
private static final String COMMAND_SEPARATOR = System.getProperty("line.separator");
private static final int BUFFER_SIZE = 8192;
private static final int RETRY_INTERVAL = 500;
/** the command to execute via ssh */
private String command = null;
/** units are milliseconds, default is 0=infinite */
private long maxwait = 0;
/** for waiting for the command to finish */
private Thread thread = null;
private String outputProperty = null; // like <exec>
private File outputFile = null; // like <exec>
private boolean append = false; // like <exec>
private Resource commandResource = null;
private boolean isShellMode;
private long maxTimeWithoutAnyData = 1000*10;
private static final String TIMEOUT_MESSAGE =
"Timeout period exceeded, connection dropped.";
public long getMaxTimeWithoutAnyData()
return maxTimeWithoutAnyData;
public void setMaxTimeWithoutAnyData(long maxTimeWithoutAnyData)
this.maxTimeWithoutAnyData = maxTimeWithoutAnyData;
public boolean isShellMode()
return isShellMode;
public void setShellMode(boolean isShellMode)
this.isShellMode = isShellMode;
/**
* Constructor for SSHExecTask.
*/
public SSHExecShellSupport()
super();
/**
* Sets the command to execute on the remote host.
*
* @param command The new command value
*/
public void setCommand(String command)
this.command = command;
/**
* Sets a commandResource from a file
* @param f the value to use.
* @since Ant 1.7.1
*/
public void setCommandResource(String f)
this.commandResource = new FileResource(new File(f));
/**
* The connection can be dropped after a specified number of
* milliseconds. This is sometimes useful when a connection may be
* flaky. Default is 0, which means "wait forever".
*
* @param timeout The new timeout value in seconds
*/
public void setTimeout(long timeout)
maxwait = timeout;
/**
* If used, stores the output of the command to the given file.
*
* @param output The file to write to.
*/
public void setOutput(File output)
outputFile = output;
/**
* Determines if the output is appended to the file given in
* <code>setOutput</code>. Default is false, that is, overwrite
* the file.
*
* @param append True to append to an existing file, false to overwrite.
*/
public void setAppend(boolean append)
this.append = append;
/**
* If set, the output of the command will be stored in the given property.
*
* @param property The name of the property in which the command output
* will be stored.
*/
public void setOutputproperty(String property)
outputProperty = property;
/**
* Execute the command on the remote host.
*
* @exception BuildException Most likely a network error or bad parameter.
*/
public void execute() throws BuildException
if (getHost() == null)
throw new BuildException("Host is required.");
if (getUserInfo().getName() == null)
throw new BuildException("Username is required.");
if (getUserInfo().getKeyfile() == null
&& getUserInfo().getPassword() == null)
throw new BuildException("Password or Keyfile is required.");
if (command == null && commandResource == null)
throw new BuildException("Command or commandResource is required.");
if(isShellMode)
shellMode();
else
commandMode();
private void shellMode()
final Object lock = new Object();
Session session = null;
try
session = openSession();
final Channel channel=session.openChannel("shell");
final PipedOutputStream pipedOS = new PipedOutputStream();
PipedInputStream pipedIS = new PipedInputStream(pipedOS);
final Thread commandProducerThread = new Thread("CommandsProducerThread")
public void run()
BufferedReader br = null;
try
br = new BufferedReader(new InputStreamReader(commandResource.getInputStream()));
String singleCmd;
synchronized (lock)
lock.wait(); // waits for the reception of the very first data (before commands are issued)
while ((singleCmd = br.readLine()) != null)
singleCmd += COMMAND_SEPARATOR;
log("cmd : " + singleCmd, Project.MSG_INFO);
pipedOS.write(singleCmd.getBytes());
lock.notify();
try
lock.wait();
catch (InterruptedException e)
log(e, Project.MSG_VERBOSE);
break;
log("Finished producing commands", Project.MSG_VERBOSE);
catch (IOException e)
log(e, Project.MSG_VERBOSE);
catch (InterruptedException e)
log(e, Project.MSG_VERBOSE);
finally
FileUtils.close(br);
;
ByteArrayOutputStream out = new ByteArrayOutputStream();
final TeeOutputStream tee = new TeeOutputStream(out, new KeepAliveOutputStream(System.out));
channel.setOutputStream(tee);
channel.setExtOutputStream(tee);
channel.setInputStream(pipedIS);
channel.connect();
// waits for it to finish receiving data response and then ask for another the producer to issue one more command
thread = new Thread("DataReceiverThread")
public void run()
long lastTimeConsumedData = System.currentTimeMillis(); // initializes the watch
try
InputStream in = channel.getInputStream();
byte[] tmp = new byte[1024];
while (true)
if(thread == null) // works with maxTimeout (for the whole task to complete)
break;
while (in.available() > 0)
int i = in.read(tmp, 0, 1024);
lastTimeConsumedData = System.currentTimeMillis();
if (i < 0)
break;
tee.write(tmp, 0, i);
if (channel.isClosed())
log("exit-status: " + channel.getExitStatus(), Project.MSG_INFO);
log("channel.isEOF(): " + channel.isEOF(), Project.MSG_VERBOSE);
log("channel.isConnected(): " + channel.isConnected(), Project.MSG_VERBOSE);
throw new BuildException("Connection lost."); // NOTE: it also can happen that if one of the command are "exit" the channel will be closed!
synchronized(lock)
long elapsedTimeWithoutData = (System.currentTimeMillis() - lastTimeConsumedData);
if (elapsedTimeWithoutData > maxTimeWithoutAnyData)
log(elapsedTimeWithoutData / 1000 + " secs elapsed without any data reception. Notifying command producer.", Project.MSG_VERBOSE);
lock.notify(); // command producer is waiting for this
try
lock.wait(500); // wait til we have new commands.
Thread.yield();
log("Continuing consumer loop. commandProducerThread.isAlive()?" + commandProducerThread.isAlive(), Project.MSG_VERBOSE);
if(!commandProducerThread.isAlive())
log("No more commands to be issued and it's been too long without data reception. Exiting consumer.", Project.MSG_VERBOSE);
break;
catch (InterruptedException e)
log(e, Project.MSG_VERBOSE);
break;
lastTimeConsumedData = System.currentTimeMillis(); // resets watch
catch (IOException e)
throw new BuildException(e);
;
thread.start();
commandProducerThread.start();
thread.join(maxwait);
if (thread.isAlive())
// ran out of time
thread = null;
if (getFailonerror())
throw new BuildException(TIMEOUT_MESSAGE);
else
log(TIMEOUT_MESSAGE, Project.MSG_ERR);
else
//success
if (outputFile != null)
writeToFile(out.toString(), append, outputFile);
// this is the wrong test if the remote OS is OpenVMS,
// but there doesn't seem to be a way to detect it.
log("Exit status (not reliable): " + channel.getExitStatus(), Project.MSG_INFO);
// int ec = channel.getExitStatus(); FIXME
// if (ec != 0)
// String msg = "Remote command failed with exit status " + ec;
// if (getFailonerror())
// throw new BuildException(msg);
// else
// log(msg, Project.MSG_ERR);
//
//
catch (Exception e)
throw new BuildException(e);
finally
if (session != null && session.isConnected())
session.disconnect();
private void commandMode()
Session session = null;
try
session = openSession();
/* called once */
if (command != null)
log("cmd : " + command, Project.MSG_INFO);
ByteArrayOutputStream out = executeCommand(session, command);
if (outputProperty != null)
//#bugzilla 43437
getProject().setNewProperty(outputProperty, command + " : " + out);
else // read command resource and execute for each command
try
BufferedReader br = new BufferedReader(
new InputStreamReader(commandResource.getInputStream()));
String cmd;
String output = "";
while ((cmd = br.readLine()) != null)
log("cmd : " + cmd, Project.MSG_INFO);
ByteArrayOutputStream out = executeCommand(session, cmd);
output += cmd + " : " + out + "\n";
if (outputProperty != null)
//#bugzilla 43437
getProject().setNewProperty(outputProperty, output);
FileUtils.close(br);
catch (IOException e)
throw new BuildException(e);
catch (JSchException e)
throw new BuildException(e);
finally
if (session != null && session.isConnected())
session.disconnect();
private ByteArrayOutputStream executeCommand(Session session, String cmd)
throws BuildException
ByteArrayOutputStream out = new ByteArrayOutputStream();
TeeOutputStream tee = new TeeOutputStream(out, new KeepAliveOutputStream(System.out));
try
final ChannelExec channel;
session.setTimeout((int) maxwait);
/* execute the command */
channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(cmd);
channel.setOutputStream(tee);
channel.setExtOutputStream(tee);
channel.connect();
// wait for it to finish
thread =
new Thread()
public void run()
while (!channel.isClosed())
if (thread == null)
return;
try
sleep(RETRY_INTERVAL);
catch (Exception e)
// ignored
;
thread.start();
thread.join(maxwait);
if (thread.isAlive())
// ran out of time
thread = null;
if (getFailonerror())
throw new BuildException(TIMEOUT_MESSAGE);
else
log(TIMEOUT_MESSAGE, Project.MSG_ERR);
else
//success
if (outputFile != null)
writeToFile(out.toString(), append, outputFile);
// this is the wrong test if the remote OS is OpenVMS,
// but there doesn't seem to be a way to detect it.
int ec = channel.getExitStatus();
if (ec != 0)
String msg = "Remote command failed with exit status " + ec;
if (getFailonerror())
throw new BuildException(msg);
else
log(msg, Project.MSG_ERR);
catch (BuildException e)
throw e;
catch (JSchException e)
if (e.getMessage().indexOf("session is down") >= 0)
if (getFailonerror())
throw new BuildException(TIMEOUT_MESSAGE, e);
else
log(TIMEOUT_MESSAGE, Project.MSG_ERR);
else
if (getFailonerror())
throw new BuildException(e);
else
log("Caught exception: " + e.getMessage(),
Project.MSG_ERR);
catch (Exception e)
if (getFailonerror())
throw new BuildException(e);
else
log("Caught exception: " + e.getMessage(), Project.MSG_ERR);
return out;
/**
* Writes a string to a file. If destination file exists, it may be
* overwritten depending on the "append" value.
*
* @param from string to write
* @param to file to write to
* @param append if true, append to existing file, else overwrite
* @exception Exception most likely an IOException
*/
private void writeToFile(String from, boolean append, File to)
throws IOException
FileWriter out = null;
try
out = new FileWriter(to.getAbsolutePath(), append);
StringReader in = new StringReader(from);
char[] buffer = new char[BUFFER_SIZE];
int bytesRead;
while (true)
bytesRead = in.read(buffer);
if (bytesRead == -1)
break;
out.write(buffer, 0, bytesRead);
out.flush();
finally
if (out != null)
out.close();
【讨论】:
【参考方案3】:另一个简单的解决方法是在运行命令之前获取用户的.bash_profile
:
<sshexec host="somehost"
username="$username"
password="$password"
command="source ~/.bash_profile && set"/>
【讨论】:
当我使用 command="source ~/.bash_profile && set"/> 时,出现类似 [sshexec] Connecting to somehost:22 [sshexec] sh: source [sshexec] 的错误: 未找到 当我运行像 command="/home/aykut/myscript" 这样的 shell 脚本时,ant 冻结了。是什么原因造成的?【参考方案4】:很棒的帖子 chubbsondubs。我需要设置 ORACLE SID,然后执行一个没有正确出口的 PLSQL 脚本。因此,回声出口通过管道传输。
<sshexec host="$db.ipaddr"
verbose="true"
trust="true"
username="$scp.oracle.userid"
password="$scp.oracle.password"
command="echo exit | /bin/bash -l -c 'export ORACLE_SID=$db.name ; sqlplus $db.dbo.userid/$db.dbo.password @./INSTALL_REVPORT/CreateDatabase/gengrant.sql'"
/>
【讨论】:
以上是关于sshexec 蚂蚁任务:环境变量的主要内容,如果未能解决你的问题,请参考以下文章