Tomcat调用Shell脚本时读取文件的While循环异常退出排查
问题描述
在AIX服务器上编写了一个Shell脚本,功能如下:
- 接收
environment_name和action(可选值:shutdown、startup)作为输入参数; - 根据
environment_name读取每行包含environment_name和server_name的文本文件; - 获取对应环境的
server_name,将写入action的文件通过SFTP传到目标服务器,由目标服务器的脚本执行对应操作。
脚本直接在AIX服务器上运行正常,但通过AIX上部署的Tomcat表单调用时,执行到读取文本文件的while循环就退出,循环内的log_message "Just Inside While"未写入日志,循环前的日志正常输出。相关代码如下:
Shell脚本读取文件代码
# Process each line in the mapping file while read -r LINE; do log_message "Just Inside While" # Split the line into environment and server ENV=$(echo "$LINE" | awk '{print $1}') SERVER=$(echo "$LINE" | awk '{print $2}') # Set the sftp user if [ "$SERVER" == "vaathmr600" ]; then user="psdev" else user="psoft" fi log_message "Coming in while" # Proceed only if the environment matches if [[ "$ENV" == "$ENV_NAME" ]]; then # Generate the passcode.trg and action.trg files PASSCODE_FILE="$TRIGGER_FOLDER/passcode_ps_${CURRENT_DATE}.trg" echo "$CLEAN_PASSCODE" > "$PASSCODE_FILE" ACTION_FILE="$TRIGGER_FOLDER/action_ps_${CURRENT_DATE}.trg" echo "$ACTION" > "$ACTION_FILE" log_message "Generated passcode and action files in $TRIGGER_FOLDER." # Generate the trigger file name and content CURRENT_DATE=$(date +%Y%m%d) TRG_FILE="$TRIGGER_FOLDER/manage_ps_${CURRENT_DATE}.trg" echo "${ENV}_${SERVER}_${ACTION}_${CURRENT_DATE}" > "$TRG_FILE" echo "Passcode: $CLEAN_PASSCODE" >> "$TRG_FILE" log_message "Generated trigger file: $TRG_FILE" # Check target server trigger directory to ensure it does not have previous dated files DIRECTORIES=("$TRIGGER_FOLDER/app" "$TRIGGER_FOLDER/prcs" "$TRIGGER_FOLDER/web") # Loop through each directory and check if it's empty for DIR in "${DIRECTORIES[@]}"; do log_message "Checking target server directory: $DIR" OUTPUT=$(sftp $user@$SERVER << EOF | grep -v "^sftp>" cd $DIR ls -1 bye EOF ) # Check if target server trigger directory is empty if [[ -z "$OUTPUT" || "$OUTPUT" == "^sftp>*" ]]; then echo "Target server trigger directory is empty" else log_message "Directory $DIR contains files:" log_message "Files present : -> $OUTPUT" log_message "Target Server trigger directory contains previous dates files. Clear the target server trigger directory" # Clean up the local trigger file rm -f $TRG_FILE $PASSCODE_FILE $ACTION_FILE log_message "Cleaned up local trigger files as the script will need to rerun after clearing target trigger directory" exit 1 fi done # SFTP the file to the target server trigger folder sftp $user@$SERVER <<EOF lcd $TRIGGER_FOLDER cd $TRIGGER_FOLDER/app put *.trg cd $TRIGGER_FOLDER/prcs put *.trg cd $TRIGGER_FOLDER/web put *.trg bye EOF # Check if the SFTP command succeeded if [[ $? -eq 0 ]]; then log_message "File $TRG_FILE, $PASSCODE_FILE, $ACTION_FILE successfully sent to $SERVER:$TARGET_FOLDER." # Clean up the local trigger file rm -f $TRG_FILE $PASSCODE_FILE $ACTION_FILE log_message "Cleaned up local trigger files" else log_message "Failed to SFTP $TRG_FILE, $PASSCODE_FILE, $ACTION_FILE to $SERVER:$TARGET_FOLDER." fi fi done < "$ENV_SERVER_MAP"
$ENV_SERVER_MAP为脚本中定义的文本文件。
Java执行代码(ShellExecutor.java)
package com.manageps; import java.io.BufferedReader; import java.io.InputStreamReader; public class ShellExecutor { public static boolean runScript(String param1, String param2, String param3) { try { String scriptPath = "/home/psoft/scripts/manage_ps/scripts/manage_ps.sh"; // Define Directories String triggerFolder = "/home/psoft/scripts/manage_ps/trigger"; String dir1 = triggerFolder + "/web"; String dir2 = triggerFolder + "/app"; String dir3 = triggerFolder + "/prcs"; ProcessBuilder pb = new ProcessBuilder("sh", scriptPath, param1, param2, param3); // Set Environment variables for Directories pb.environment().put("DIR1",dir1); pb.environment().put("DIR2",dir2); pb.environment().put("DIR3",dir3); Process process = pb.start(); int exitCode = process.waitFor(); // Log output for debugging BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; while ((line = reader.readLine()) != null) { System.out.println(line); } return exitCode == 0; } catch (Exception e) { e.printStackTrace(); return false; } } }
Java Servlet代码(ManagePSServlet.java)
package com.manageps; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/manage") public class ManagePSServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String param1 = request.getParameter("param1"); String param2 = request.getParameter("param2"); String param3 = request.getParameter("param3"); // Execute the shell script boolean success = ShellExecutor.runScript(param1, param2, param3); // Respond to the user response.setContentType("text/html"); response.getWriter().println("<html><body>"); if (success) { response.getWriter().println("<h1>Script executed successfully!</h1>"); } else { response.getWriter().println("<h1>Failed to execute script.</h1>"); } response.getWriter().println("<a href='manage_ps.jsp'>Back</a>"); response.getWriter().println("</body></html>"); } }
问题分析与解决
核心问题是:Shell脚本中的while read循环与SFTP命令共享了标准输入(stdin)。Tomcat调用脚本时,SFTP会读取while read的输入源(即$ENV_SERVER_MAP文件),导致read命令无法获取后续行,循环直接退出。
解决办法
有两种可行的修改方式:
方式一:重定向SFTP的标准输入
将所有SFTP命令的标准输入重定向到/dev/null,避免占用while read的输入流:
- 修改目录检查部分的SFTP命令:
OUTPUT=$(sftp $user@$SERVER << EOF | grep -v "^sftp>" cd $DIR ls -1 bye EOF </dev/null)
- 修改文件上传部分的SFTP命令:
sftp $user@$SERVER <<EOF </dev/null lcd $TRIGGER_FOLDER cd $TRIGGER_FOLDER/app put *.trg cd $TRIGGER_FOLDER/prcs put *.trg cd $TRIGGER_FOLDER/web put *.trg bye EOF
方式二:使用独立文件描述符读取映射文件
将$ENV_SERVER_MAP绑定到非标准文件描述符,避免与SFTP的stdin冲突:
while read -r LINE <&3; do # 循环内代码保持不变 done 3< "$ENV_SERVER_MAP"
额外检查点
- 确认Tomcat运行用户对
$ENV_SERVER_MAP文件有读取权限; - 确认
log_message函数的日志路径对Tomcat用户可写; - 检查脚本中使用的环境变量(如
TRIGGER_FOLDER、CURRENT_DATE等)在Tomcat环境中是否正确设置。
内容的提问来源于stack exchange,提问作者Anicaprio
相关产品推荐
相关产品推荐

