macOS iTerm2使用lrzsz上传文件到服务器或从服务器下载文件

macOS iTerm2使用lrzsz上传文件到服务器或从服务器下载文件

之前我写过两篇文章:

使用scp或rsync的缺点是,每次你都必须手动指定文件路径,而且是两边都得指定,配置了免密登录还好,如果没有配置免密登录,每次传输还要输密码,非常麻烦。

Xmodem/Ymodem和Zmodem协议

  • Xmodem:这种古老的传输协议速度较慢,但由于使用了CRC错误侦测方法,传输的准确率可高达99.6%。 
  • Ymodem:这是Xmodem的改良版,使用了1024位区段传送,速度比Xmodem要快
  • Zmodem:Zmodem采用了串流式(streaming)传输方式,传输速度较快,而且还具有自动改变区段大小和断点续传、快速错误侦测等功能。这是目前最流行的文件传输协议。 

lrzsz就是这三种协议的实现,也就是说lrzsz支持这三种协议,要使用lrzsz,首先需要安装它。

传输原理

利用当前ssh已经建立的连接通过stdin和stdout来传输:

  • 1、向服务器传输就相当于你在终端敲了很多字符,然后服务器那边把你敲的字符全部接收下来保存成一个文件,于是你的文件就“传”到了服务器;
  • 2、你平时操作服务器,服务器返回信息其实也是通过网络“传”到你的终端上,所以服务器端sz命令就是用这个原理,把文件“传”到你的终端上,只不过这个传输的内容全部被rz接收了并保存成一个文件,于是文件就被“下载”到你电脑中了。

由于是通过stdin和stdout来传输,所以只要你能登录上服务器,那就一定能传输,不存在跳板机的问题,但这种方式无法上传/下载大文件。

安装lrzsz

要使用lrzsz传输文件,需要在两台需要传输文件的机器都安装lrzsz,由于我是macOS和服务器相互传文件,所以我要在macOS和服务器上都安装lrzsz

macOS端安装

brew install lrzsz

服务器端安装

# Debian
apt install lrzsz

# redhat/centos7
yum install lrzsz

# centos8
dnf install lrzsz

安装好之后,会有szrz两个命令:

  • sz: Send file(s) with ZMODEM/YMODEM/XMODEM protocol (发送文件)
  • rz: Receive files with ZMODEM/YMODEM/XMODEM protocol (接收文件)

也就是说,sz中的s是send(发送),rz中的r是receive(接收),而sz/rz中的z都是指zmodem。

使用时,需要客户端和服务器端配对运行

  • 服务器端rz(发送),则客户端sz(发送);
  • 服务器端sz(发送),则客户端rz(接收)。

添加脚本

以下两个脚本用于iTerm2自动调用,详细解释可看后面的脚本详解


下载两个脚本到/usr/local/bin/

wget https://raw.githubusercontent.com/xiebruce/iterm2-zmodem/main/iterm2-rz.sh -O /usr/local/bin/iterm2-rz.sh
wget https://raw.githubusercontent.com/xiebruce/iterm2-zmodem/main/iterm2-sz.sh -O /usr/local/bin/iterm2-sz.sh

添加可执行权限

chmod u+x /usr/local/bin/iterm2-*

iTerm2添加触发器

添加触发器(Triggers):iTerm2左上角菜单→Preferences…→Profiles→Default→Advanced→Triggers的“Edit”

点击左下角+号,添加两条规则,最后点击右下角的“Close”关闭

图片不好复制,我做了个表格可以直接复制

Regular Expression Action Parameters Instant Enabled
\\B00 Run Silent Coprocess… /usr/local/bin/iterm2-rz.sh 勾选 勾选
rz waiting to receive.\\B0100 Run Silent Coprocess… /usr/local/bin/iterm2-sz.sh 勾选 勾选

注意:在Parameters中粘贴zmodem.sh路径时,显示如下,就是感觉你复制的根本不是路径,其实是因为路径太长了它显示不下,所以往左滚动到只显示最后一个单词,鼠标点击任意地方它就会显示出来的

启用拖放上传

打开:iTerm2→Preferences→Advanced→搜索 “dropp”
iTerm2-drag-drop-trigger

触发拖放事件后被调用的脚本路径如下

/usr/local/bin/iterm2-sz.sh dragfiles \(filenames)

拖放上传主要是使用了iTerm2的拖放事件,iTerm2支持拖放后,自动调用你指定的脚本,iTerm2版本必须在iTerm2-3_5_20230503-nightly.zip以上。

开始使用

在iTerm2中按Control+T打开一个Default窗口,在该窗口中通过ssh登录到你的服务器

ssh [email protected]

把文件发送到服务器
方法一:在服务器中,cd到你要接收文件的目录,然后输入rz,按回车运行(r表示receive,表示要接收客户端发送过来的文件),稍等一会儿即会弹出窗口让你选择你要上传什么文件,选择后即可上传文件到服务器(即发送到服务器),已上传的文件会被忽略,不会重复上传;
方法二:在服务器中,cd到你要接收文件的目录,然后把要上传的文件/文件夹直接拖进去,注意只能拖一个文件/文件夹,如果文件夹中有子文件夹,则子文件夹中的文件会被上传,但子文件夹本身不会被上传。

从服务器下载文件:在服务器中,cd到你要接收文件的目录,然后输入sz /path/to/file,按回车运行(s表示send,表示要发送文件到客户端),当然也可以发送多个文件sz /path/to/file1 /path/to/file2 /path/to/file3,或某个文件夹下全部文件sz /path/to/folder/*

注意,如果你已经配置了自动登录,那么你需要在对应的Profile条目中重新添加前面的触发器“Triggers”,即每个Profile需要单独配置,因为Default的是不会作用于你自己配置的Profile中的

缺点

  • 1、没有进度条,小文件还好,如果文件比较大,要等多久都不知道;
  • 2、只能用于本地电脑与服务器之间的传输,无法用于服务器到服务器之间的传输。

上传/下载流程分析

上传文件传输流程:服务器执行rz命令→rz命令输出一串码→iTerm2识别到这串码,自动调用预先设置的iterm2-sz.sh脚本→脚本中的applescript会调出系统文件选择窗口→手动选择要上传的文件→点击“choose(即选择)”,文件即开始上传。

下载文件传输流程:服务器执行sz /path/to/file命令→sz命令输出一串码→iTerm2识别到这串码,自动调用预先设置的iterm2-rz.sh脚本→脚本中的applescript会调出系统文件选择窗口→手动选择要把文件下载到哪个目录→点击“choose(即选择)”,文件即开始下载。

所谓“上传文件”:其实是客户端(Mac端)上传,而对服务器来说是“接收”文件,所以需要在服务器端执行rz命令,表示“receive zmodem”。

所谓“下载”文件:其实是客户端下载,而对服务器来说就是“向客户端发送文件”,所以需要在服务器端执行sz命令,表示“send zmodem”

总结:无论上传文件还是下载文件,都是先登录服务器,然后在服务器端执行rzsz命令,客户端虽然也需要执行,但不是手动执行,而是设置为脚本调用,手动执行是不行的。

即当你在服务器运行rz时,iTerm2会自动调用以下脚本,最终在客户端执行sz命令来把文件发送到服务器

eval "/usr/local/bin/sz --escape --binary --bufsize 4096 $FILE"

当你在服务器运行sz /path/to/file时,iTerm2会自动调用脚本,最终在客户端执行rz命令来接收服务器发送的文件

/usr/local/bin/rz -E -e -b --bufsize 4096

Triggers(触发器)分析

为什么在服务器输入rz/sz命令,会触发客户端相应的sz/rz运行呢?这是因为我们在前面iTerm2添加触发器中添加了触发器(这是iterm2的功能,系统自带的“终端”是没这个功能的)。

触发器主要靠正则识别字符串来触发,当在服务器端运行rzsz命令时,会相应的输出一串字符串,iTerm2识别到这个字符串后,发现这个字符串能匹配上触发器中的正则,所以就触发了该触发器,并自动调用运行了对应的脚本。

触发器的正则中,*号要加反斜杠,是为了转义,因为*号本身是有特殊作用的,加了反斜杠才表示*号本身。

脚本详解

前面添加脚本中添加的脚本,现在我们来解释一下。

先说一下,applescript是苹果系统的一种脚本,它可以用于操作macOS,比如打开访达,打开文件选择窗口,自动帮你按快捷键等等。


第一段代码

osascript -e 'tell application "iTerm" to version' >/dev/null 2>&1 && app_name="iTerm" || app_name="iTerm2"
  • osascript是applescript执行器,os就是macOS,a是apple,即:os apple script的意思;
  • -e表示execute,即执行命令;
  • tell application "iTerm" to version就是applescript,tell application "xxx" to version意思是获取“xxx”app的版本,你可以单独运行osascript -e 'tell application "iTerm" to version'试试,它会输出它的版本;
  • >/dev/null 意思是把前面的字符串(在本例是“版本信息”)重定向到空设备(就是不输出);
  • 2>&1表示把“标准错误输出”重定向到“标准输出”,这样无论前面那句命令正常输出,还是报错,它都不会往终端输出,而是全部重定向到空设备中(其实就是丢弃掉),你可能很好奇,为什么获取了版本,却又要丢掉?
  • bash中,任何一个脚本运行结束会返回一个状态码,状态码有可能是0,表示执行成功,此时整句的执行结果为true,有可能是负数,表示执行失败,此时整句的执行结果为false,这个true和false关系到后面的app_name="iTerm"是否会被设置。
  • 因为&&符号有短路机制,所以这整句的意思其实根本就不是用来获取版本,而是随便执行一个关于iTerm2的命令,看它报不报错,报错,说明iTerm不存在(没有安装或程序名称为iTerm2,因为虽然叫iTerm2,但它本身程序名却可能为iTerm或iTerm2,好像与版本有关),由于有2>&1,所以无论否报错,都不会输出到终端,但是否报错却影响了整句命令最终是true还是false,从而决定app_name是iTerm2还是iTerm,它的作用相当于以下命令
    if osascript -e 'tell application "iTerm" to version' >/dev/null 2>&1 == true {
        app_name="iTerm"
    }else{
        app_name="iTerm2"
    }
    

第二段代码(来自iterm2-sz.sh,iterm2-rz.sh的更简单,懂了这段自然就能看懂)

FILE=$(
    osascript <<EOF
tell application "$app_name"
    set selectedFiles to choose file \
        with multiple selections allowed \
        with prompt "Choose some files to send"
    set filePaths to ""
    repeat with theFile in selectedFiles
        set filePaths to filePaths & "\"" & POSIX path of theFile & "\" "
    end repeat
    return filePaths
end tell
EOF
)
  • $()可以把脚本输出结果保存到FILE变量中(否则它会输出到终端);
    FILE=$(
        # 这里是脚本
    )
    
  • 以下结构,用于使用osascript命令去运行两个EOF之间的applescript代码
    osascript <<EOF
    # 这里写applescript
    EOF
    
  • tell application xxx,对“xxx”应用进行操作;
  • set selectedFiles to choose file,定义变量selectedFiles,把它的值设置为“choose file”的值(会弹出让用户选择文件);
  • with multiple selections allowed,与前一句是同一句,只不过用\换行写了,表示允许选择多个文件;
  • with prompt "Choose some files to send",与前一句是同一句,只不过用\换行写了,表示选择文件时提示句为“Choose some files to send”;
  • 这段代码是设置一个变量filePaths,然后循环处理文件路径(因为可以选择多个文件)
    set filePaths to ""
    repeat with theFile in selectedFiles
        set filePaths to filePaths & "\"" & POSIX path of theFile & "\" "
    end repeat
    
  • “choose file”返回的文件路径是这样的
    alias Macintosh HD:Users:bruce:Downloads:测试:
    

    需要把它转换成

    /Users/bruce/Downloads/测试/
    

    POSIX path of theFile就是用于把thefile变量的值转换成斜杠分隔的路径

    # 把thefile转换成斜杠分隔的路径,并在它两边用双引号括起来,“&”是拼接符号
    set filePaths to filePaths & "\"" & POSIX path of theFile & "\" "
    
    # 这句不是真正的代码,而是我自己写的用于解释上面这句applescript的意思
    filePaths=filePaths+"\""+ posix_path(theFile) + "\""
    

所以,这第二段代码,是为了获取你选择的文件或文件夹路径(对于iterm2.rz.sh就是文件夹路径),最终保存到FILE变量中。

获取到文件或文件夹路径后,后面的脚本就是用于处理传输文件了,如果打开选择文件,用户没有选择文件,而是点了取消,那么$FILE就是空,否则就不为空

if [[ $FILE = "" ]]; then
    echo Cancelled.
    # Send ZModem cancel, \\x18 actually is \x18, it is hex for Ctrl-X
    echo -e \\x18\\x18\\x18\\x18\\x18
    sleep 1
    echo
    echo \# Cancelled transfer
else
    # send files to server by using sz
    eval "/usr/local/bin/sz --escape --binary --bufsize 4096 $FILE"
    sleep 1
    echo
    # string that split by space transform to array
    read -ra files <<<"$FILE"
    # print received files one by one
    for file in "${files[@]}"; do
        echo \# Received $file
    done
fi

$FILE为空时,主要是执行了以下这句,-e表示把它们转换成真正的符号,\\x18其实就是\x18,多写一个反斜杠只是为了把后面的斜杠转义成真正的斜杠

echo -e \\x18\\x18\\x18\\x18\\x18

ASCII码表可知,\x18就是“Cancel”的意思,只不过它不是实际的符号,它是控制符^X(control+x),控制符在终端看不见。所以这整句的意思就是输出5个Cancel控制符。

echo \# Cancelled transfer是为了向终端输出一句#号开头的话“# Cancelled transfer”,因为#号开始是灰色的,只是用于提示用,所以用灰色。

sleep 1表示睡眠1秒钟,空echo我也不知道有什么用,不过如果有选择文件进行传输时,不用空echo会造成终端中报错。


如果选择了文件,即$FILE存在,则会执行对应的发送或接收命令,如果是发送,则把$FILE传进去让它发送,如果是接收,则cd $FILE进入该文件夹,表示把文件保存到该文件夹中。

发送,以下两句等效(选项名用长名和短名的区别)

eval "/usr/local/bin/sz -e -b -B 4096 $FILE"
eval "/usr/local/bin/sz --escape --binary --bufsize 4096 $FILE"

接收,以下两句等效(选项名用长名和短名的区别)

eval "/usr/local/bin/sz -e -b -B 4096 $FILE"
eval "/usr/local/bin/sz --escape --binary --bufsize 4096 $FILE"

rz/sz选项解释

-E, --rename                rename any files already existing
-e, --escape                Escape control characters (Z)
-b, --binary                binary transfer
-B, --bufsize N             buffer N bytes (N==auto: buffer whole file)

sz可以发送单个或多个文件(空格隔开),也可以指定发送文件夹内全部sz /path/to/folder/*

接收的时候,最后有2个空echo,我也不知道干嘛用的。

代码解析

# 把$FILE转换为数组并存储到files数组中
# $FILE格式为:"/path/to/file1" "/path/to/file2" "/path/to/file3"
# 即是多个文件路径,它们之间用空格隔开,每个路径都用双引号括住
read -ra files <<<"$FILE"

# 循环打印上传的每个文件路径(包括文件名)
for file in "${files[@]}"; do
    echo \# Received $file
done

对于接收文件(服务器往客户端发送文件),默认是直接保存到“下载”目录中,如果你需要自己选择保存的目录,你可以通过在~/.bashrc中添加export CHOOSE_FOLDER=true环境变量来启用选择接收文件夹,只是这样每次接收都要选择文件夹,我觉得挺麻烦的。

另外你也可以修改默认接收文件夹,把iterm2-rz.sh中的FILE="$HOME/Downloads/"修改一下路径就行,$HOME表示家目录。


参考:
aikuyun/iterm2-zmodem
Linux基础:利用SSH上传、下载(使用sz与rz命令)

打赏
订阅评论
提醒
guest

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x

扫码在手机查看
iPhone请用自带相机扫
安卓用UC/QQ浏览器扫

macOS iTerm2使用lrzsz上传文件到服务器或从服务器下载文件