通过SSH使用tar传输文件时部分场景出现gzip错误的排查
问题原因与解决方法
问题原因
你用result=$(ssh user@server "sshGetFiles")捕获远程输出时,bash会将二进制的tar/gzip数据流当作文本字符串处理:
- 过程中会对二进制数据里的特殊字符(如空字节、非ASCII字符)进行转义或修改,直接破坏了gzip压缩包的结构。
- 同时
$(...)会自动截断输出末尾的换行符,导致二进制数据流不完整。
这两种情况都会让gzip无法正常解析数据流,从而抛出"stdin has flags 0xc9 -- not supported"或"stdin is a multi-part gzip file -- not supported"的错误。
解决方法
方法1:分离错误输出,保留管道直接传输(推荐)
不要用变量捕获输出,而是通过管道直接传输二进制数据流,同时将远程的错误信息单独输出到本地stderr,避免混入数据流:
function sshGetFiles() { if ! [[ -f "file1" && -f "file2" && -f "file3" ]]; then echo "error, some files don't exist" >&2 # 将错误信息定向到stderr exit 1 fi tar --create --gzip --file - "file1" "file2" "file3" } # 传递函数定义到远程主机,同时执行函数 ssh user@server "$(declare -f sshGetFiles); sshGetFiles" 2>&1 > >(tar --extract --gzip --file - --directory .) # 获取ssh命令的退出码,判断执行状态 exit_code=${PIPESTATUS[0]} if [[ $exit_code -ne 0 ]]; then exit $exit_code fi
declare -f sshGetFiles会把本地的函数定义传递给远程主机,确保远程能执行该函数。- 进程替换
>(...)将ssh的stdout(即tar的二进制数据流)直接传给本地tar命令,完全避免字符串处理对二进制数据的破坏。 - 远程的错误信息通过
2>&1转发到本地stderr,不会干扰数据流传输。
方法2:临时文件中转数据流(适合需暂存的场景)
如果需要先验证远程命令是否成功,再处理数据流,可以用临时文件保存二进制输出,避免变量破坏数据:
function sshGetFiles() { if ! [[ -f "file1" && -f "file2" && -f "file3" ]]; then echo "error, some files don't exist" >&2 exit 1 fi tar --create --gzip --file - "file1" "file2" "file3" } # 创建临时文件 temp_file=$(mktemp) # 执行远程命令,将stdout存入临时文件,stderr输出到本地 ssh user@server "$(declare -f sshGetFiles); sshGetFiles" > "$temp_file" 2>&1 exit_code=$? if [[ $exit_code -ne 0 ]]; then cat "$temp_file" # 输出错误信息 rm "$temp_file" exit $exit_code fi # 使用临时文件解压 tar --extract --gzip --file "$temp_file" --directory . rm "$temp_file"
- 临时文件直接保存二进制数据流,不会像变量那样修改或破坏数据。
- 命令失败时,临时文件里会保存远程输出的错误信息,可直接输出查看。
内容的提问来源于stack exchange,提问作者PrOpoLo
相关产品推荐
相关产品推荐

