有一个漂亮的小工具叫做 zssh 这使得使用起来很容易
lszrz
使用传输文件的实用程序
z调制解调器
通过现有的 ssh
联系。 出奇的方便……但看来我应该
能够使用 expect
完成同样的事情。 我已经得到了
到目前为止...
#!/usr/bin/expect -f
spawn ssh $argv
set ssh_spawn_id $spawn_id
send_user "ssh is: $ssh_spawn_id\n"
interact -o "\030B0000" {
send_user "\nStarting zmodem receive.\n"
spawn rz -v
set rz_spawn_id $spawn_id
send_user "rz is: $rz_spawn_id\n"
while {1} {
expect {
eof break
-i $rz_spawn_id -re .+ {
send -raw -i $ssh_spawn_id $expect_out(buffer)
}
-i $ssh_spawn_id -re .+ {
send -raw -i $rz_spawn_id $expect_out(buffer)
}
}
}
send_user "\nFinished zmodem receive.\n"
set spawn_id $ssh_spawn_id
}
看到
rz
框架后启动ZRQINIT
,并且它显然连接
rz
到ssh会话,但是不起作用。 rz
说:
Retry 0: Bad CRCe.**B0100000023be50
Retry 0: Bad CRC**B0600000023d984
Retry 0: Bad CRC**B0600000023d984
...等等。
有办法让这个工作成功吗? 谢谢!
我发现在发送方使用
-e
/ --escape
(转义所有控制字符)选项有助于解决启动 zmodem 连接时出现的一些问题。
例如:
发送文件:
sz -e somefile.ext
接收文件:
rz -e
这对于通过 IPMI sol(局域网串行)链路传输文件非常方便。
OSX 上有 iterm2-zmodem,Linux 上有 Konsole,集成了 Zmodem。
在调试时使用
exp_internal 1
非常有用。您可以查看 Expect 与传入文本的匹配情况。我想知道终端是否妨碍了。在生成
rz
之前,尝试 stty raw
。然后在send_user "Finished..."
之后做stty -raw
。您可以使用
exp_continue
代替 while
循环:
spawn rz -v
set rz_spawn_id $spawn_id
send_user "rz is: $rz_spawn_id\n"
expect {
-i $rz_spawn_id -re .+ {
send -raw -i $ssh_spawn_id $expect_out(buffer)
exp_continue
}
-i $ssh_spawn_id -re .+ {
send -raw -i $rz_spawn_id $expect_out(buffer)
exp_continue
}
eof
}
这与问题无关,只是风格问题。
感谢您提出这个问题,它帮助我开始自己成功地做到这一点。
我想要类似的东西,这样我就可以通过 QEMU 虚拟串行控制台套接字(与 Proxmox
qm term $vmid
命令相同)使用 Proxmox QEMU VM 的终端发送和接收文件(分时,而不是多个流)用途)。
自从我编写下面的脚本以来已经有一段时间了,它对于这个答案来说绝对是必要的,并且仍然存在问题,但是您的代码被破坏的主要原因是因为需要配置 TCL 通道二进制模式与
chan configure $channel_id -translation binary
.
从“本地”机器发送是通过在空终端中键入
szsend
来触发的(其中expect然后将键入接收命令),然后它将调出fzf进行文件选择,并且从远程机器发送是通过以下方式完成的运行 sz,并期望将检测传输标头并在本地启动 rz。
#!/usr/bin/env -S expect --
#exp_internal 1
#strace 1
#TODO disable error correction in lrzsz since we are on a reliable connection
proc spawn_channel {command} {
puts [concat < spawn $command >]
spawn -noecho -open [open $command wb+]
remove_nulls 0
set channel_id $spawn_id
chan configure $channel_id -translation binary
return $channel_id
}
proc local_file_select {} {
set fzf_id [spawn -noecho fzf --print0 --expect=enter]
interact {
"exit" {
send_user "\n"
exit
}
#TODO "unreliable"?
-o -re "enter\0(.*)\0" {
set selected_file "$interact_out(1,string)"
#send_user "You selected: $interact_out(1,string)\n"
}
}
return $selected_file
}
# Can this be simplified to some pipe?
proc handle_zmodem {sz_id rz_id} {
#TODO maxtogggle
log_user 0
expect {
-i $rz_id -re "(\x00|.)+" {
send -raw -i $sz_id -- "$expect_out(buffer)"
exp_continue
}
-i $sz_id -re "(\x00|.)+" {
send -raw -i $rz_id -- "$expect_out(buffer)"
exp_continue
}
# TODO does eof even need to be handled explicitly
eof
}
log_user 1
}
proc filetransfer_interact {spid} {
set spawn_id $spid
# note order on the -o matters
interact {
exit exit
#TODO -re seems to be the way to not immediately forward to the user process
"szsend" {
set rz_id $spid
set sz_id [spawn_channel [concat "|sz -vv -E" [local_file_select] "2>szerr"]]
#TODO only works if terminal prompt
#TODO
#TODO rzcommand / check os; lrz waiting to receive.
send "lrz -vv 2>lrzerr\n"
handle_zmodem $sz_id $spid
}
-o "rz\015**\030B0000" {
# TODO why does this only work if its in here?
set rz_id [spawn_channel "|rz -vv 2>rzerr"]
handle_zmodem $spid $rz_id
#TODO if handle is successful or whatever?
#return
}
}
}
proc proc_args {} {
set ::vmid [lindex $::argv 0]
set ::argv [lrange $::argv 1 end]
}
proc attach_serial {vmid} {
set socket_path [exec sh -c [concat qm showcmd $vmid | grep -oP {'id=serial[0-9]+,path=\K[^,]+'} ]]
set spawn_id [spawn_channel "|socat - UNIX-CONNECT:$socket_path"]
puts "socat is $spawn_id"
return $spawn_id
}
proc main {} {
proc_args
# Note interact implicitly sets raw per the docs though
set stty_init raw
filetransfer_interact [attach_serial $::vmid]
}
main