现在基本可以锁定问题了。
你这个 run.sh 有两个高危点:
sh
#! /bin/sh
以及:
sh
nohup java ... > "$DAEMON_OUT" 2>&1 < /dev/null &
在:
text
mac IDEA -> SSH Exec -> /bin/sh -> nohup
链路下,Ubuntu/Debian 的 /bin/sh 实际是:
text
dash
而不是 bash。
dash + nohup + 非交互 SSH channel 对 UTF-8/stdout 处理兼容性很差。
这也是为什么:
- 手工 SSH 登录执行正常
- IDEA 远程执行乱码
因为:
text
手工登录 = interactive bash
IDEA执行 = non-interactive dash
完全不是一个环境。
你现在应该这样改(核心修复)
第一处:改 shebang
把:
sh
#! /bin/sh
改成:
bash
#!/bin/bash -l
这里:
-
bash避免 dash 的 locale/stdout 问题
-
-l强制 login shell
这是关键。
第二处:显式指定 UTF-8
在变量区增加:
bash
export LANG=C.UTF-8
export LC_ALL=C.UTF-8
第三处:JVM 增加编码参数
你现在:
sh
JVM_PARAM="-Xms${JVM_MIN} ..."
改成:
bash
JVM_PARAM="\
-Xms${JVM_MIN} \
-Xmx${JVM_MAX} \
-XX:+ExitOnOutOfMemoryError \
-Dfile.encoding=UTF-8 \
-Dsun.jnu.encoding=UTF-8 \
-Dspring.cloud.config.uri=${CONFIG_URI} \
-Dspring.profiles.active=${RUN_TYPE} \
-DNODEID=${NODE_ID}"
第四处:nohup 前增加 env(非常关键)
现在:
sh
nohup ${JAVA_HOME}/bin/java ...
改成:
bash
nohup env LANG=C.UTF-8 LC_ALL=C.UTF-8 \
${JAVA_HOME}/bin/java ${JVM_PARAM} \
-jar ${EXEC_JAR} ${@} \
>> "$DAEMON_OUT" 2>&1 < /dev/null &
注意:
这里我顺便把:
sh
>
改成:
sh
>>
避免覆盖日志。
为什么你"服务器直接执行正常"
因为:
bash
ssh host
./run.sh start
实际上:
text
bash login shell
会自动:
bash
source /etc/profile
source ~/.bashrc
并继承:
bash
TERM=xterm-256color
LANG=C.UTF-8
而 IDEA 的 SSH Exec:
通常是:
bash
/bin/sh -c ./run.sh start
甚至:
bash
sh run.sh start
所以:
text
shebang可能都失效
这是很多人没意识到的点。
再补一个隐藏问题(很重要)
你这里:
sh
if [ "$USER" != "$RUN_USER" ];
在 IDEA SSH Exec 下:
text
$USER
可能为空。
更安全写法:
bash
if [ "$(whoami)" != "$RUN_USER" ]; then
我建议你最终改成这样
bash
#!/bin/bash -l
export LANG=C.UTF-8
export LC_ALL=C.UTF-8
NODE_ID="34225"
JAVA_HOME="/home/distcom/software/jdk1.8.0_491"
JVM_MIN="128m"
JVM_MAX="512m"
RUN_USER="distcom"
RUN_TYPE="peer1"
EXEC_JAR="openservices-spring-boot-starter.jar"
LOG_FILE="server.log"
CONFIG_URI="http://192.168.1.192:8040/"
if [ "$(whoami)" != "$RUN_USER" ]; then
echo "failed, It can only be executed under $RUN_USER"
exit 0
fi
JVM_PARAM="\
-Xms${JVM_MIN} \
-Xmx${JVM_MAX} \
-XX:+ExitOnOutOfMemoryError \
-Dfile.encoding=UTF-8 \
-Dsun.jnu.encoding=UTF-8 \
-Dspring.cloud.config.uri=${CONFIG_URI} \
-Dspring.profiles.active=${RUN_TYPE} \
-DNODEID=${NODE_ID}"
NODE_PATH=$(cd `dirname $0`; pwd)
LOG_PATH="${NODE_PATH}/log"
mkdir -p "$LOG_PATH"
LOG_FILE="${LOG_PATH}/${LOG_FILE}"
DAEMON_OUT="${LOG_PATH}/stdout.out"
DATA_PATH="${NODE_PATH}/data"
mkdir -p "$DATA_PATH"
PIDFILE="${DATA_PATH}/server.pid"
case $1 in
start)
echo -n "Starting ... "
if [ -f "$PIDFILE" ]; then
if kill -0 `cat "$PIDFILE"` > /dev/null 2>&1; then
echo already running as process `cat "$PIDFILE"`
exit 0
fi
fi
if [ -e "$LOG_FILE" ]; then
mv "$LOG_FILE" "${LOG_FILE}.`date +%Y%m%d%H%M`"
fi
if [ -e "$DAEMON_OUT" ]; then
mv "$DAEMON_OUT" "${DAEMON_OUT}.`date +%Y%m%d%H%M`"
fi
shift
nohup env LANG=C.UTF-8 LC_ALL=C.UTF-8 \
${JAVA_HOME}/bin/java \
${JVM_PARAM} \
-jar ${EXEC_JAR} ${@} \
>> "$DAEMON_OUT" 2>&1 < /dev/null &
echo $! > "$PIDFILE"
;;
esac
最后一个关键建议
如果你用 IDEA:
Remote SSH / Deployment
尽量:
不要用:
text
sh run.sh start
而是:
text
bash -l run.sh start
或者:
text
/bin/bash -lc "./run.sh start"
这里:
-
-llogin shell
-
-ccommand mode
这是 Linux 运维里标准远程执行姿势。
你的问题本质上不是 Java 编码问题
而是:
text
IDEA SSH 非交互 shell
+
dash
+
nohup
+
stdout
组合导致的字符流能力下降。
简单处理直接在run.sh 加上export LANG=zh_CN.UTF-8 export LC_ALL=zh_CN.UTF-8 即可 其他不需修改。
对,从你现在反馈的现象来看:
text
手工执行正常
IDEA SSH 远程执行乱码
说明本质上不是:
- JVM 编码问题
- 日志框架问题
- 文件编码问题
而是:
text
IDEA 远程执行时缺少 locale 环境继承
所以你现在这个最小修复:
bash
export LANG=zh_CN.UTF-8
export LC_ALL=zh_CN.UTF-8
直接放到 run.sh 顶部,确实就足够了。
例如:
bash
#! /bin/sh
export LANG=zh_CN.UTF-8
export LC_ALL=zh_CN.UTF-8
这会让:
text
nohup -> java -> stdout
整条链路强制使用 UTF-8 locale。
对于你当前环境:
- Ubuntu
- 已安装 zh_CN.UTF-8 locale
- java file.encoding=UTF-8
- 手工启动正常
这已经能解决问题。
你这个案例也说明:
text
问题并不在 JVM
而在 SSH 非交互执行环境
因为:
bash
locale
是用户登录后看到的。
但:
text
IDEA SSH Exec
未必继承这些变量。
所以:
bash
export LANG=...
放在脚本内部最稳。
不过还有一个小建议:
虽然你现在验证 zh_CN.UTF-8 可以解决,但最好还是顺手加一个:
bash
-Dfile.encoding=UTF-8
因为:
- JDK8 某些环境仍会参考 locale 推断编码
- nohup/daemon 场景有时会丢 locale
- 后续迁移 Docker/systemd 时更稳定
即:
bash
JVM_PARAM="${JVM_PARAM} -Dfile.encoding=UTF-8"
这是长期运维里比较保险的做法。