You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的输入流:

  1. 修改目录检查部分的SFTP命令:
OUTPUT=$(sftp $user@$SERVER << EOF | grep -v "^sftp>"
cd $DIR
ls -1
bye
EOF
</dev/null)
  1. 修改文件上传部分的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:50:00