问题的提出

最近使用 github 上传、下载项目代码时,经常会卡很久,有时候在命令行打了 git push 然后就去上厕所了,结果等我回来的时候,发现 push 早已经失败了,还得重新提交一下。如果有一个工具,可以不停的重启失败的 git push 直到它成功才退出,那就好了。

什么是 expect

在介绍使用 expect 重启 git 操作之前,先简单说明一下这个命令。其实它并不是一个新潮的东西,在很早以前就存在了,以至于现在一些系统默认都不带这个命令了,需要自己手工安装下:

$ sudo yum install expect
$ expect -v
expect version 5.44.1.15

简单的说,expect 就是完成一些需要与用户交互的任务,例如 telnet、ftp、ssh 远程登录机器的时候,这些命令会要求用户输入用户名、密码等相关信息,而这些,是无法通过 shell 脚本来完成的。这是因为这些命令是从控制终端而不是标准输入上读取的,所以无法事先将信息重定向到标准输入从而实现自动化运行。而 expect 就是用来解决这类问题的,下面是一个使用 expect 进行 ssh 登录的例子:

 #!/usr/bin/expect -f
set ipaddr "localhost"
set passwd "iforgot" spawn ssh root@$ipaddr
expect {
"yes/no" { send "yes\r"; exp_continue}
"password:" { send "$passwd\r" }
} expect "]# "
send "touch a.txt\r"
send "exit\r"
expect eof
exit

expect 脚本里有这么几个关键动作:

  • spawn :启动需要执行的命令;
  • expect :解析命令输出,并根据下面的匹配语句进入子控制块;
  • send :向命令发送信息,这些信息相当于是命令从控制终端读取的;
  • interact :继续命令与控制终端的交互,此时用户可以正常向命令输入信息(本例未展示)。
  • ……

好了,熟悉了 expect 的用法后,有人可能有疑问了,这个 git pull/push 操作也不涉及密码,用它做什么呢?这就是因人而异了,有些人是因为密码的关系用它,而我只看中了它的 expect 动作。

失败日志与正常日志

以 git pull 为例,失败时,它的输出如下:

$ git pull
ssh: connect to host github.com port 22: Connection refused
fatal: The remote end hung up unexpectedly

成功时,它的输出是这样的:

$ git pull
remote: Enumerating objects: 38, done.
remote: Counting objects: 100% (38/38), done.
remote: Compressing objects: 100% (24/24), done.
remote: Total 36 (delta 24), reused 24 (delta 12), pack-reused 0
Unpacking objects: 100% (36/36), done.
From github.com:goodpaperman/apue
86b80d3..e0cc835 master -> origin/master
Updating 386fd43..e0cc835
Fast-forward
apue.c | 10 ++++++++++
1 files changed, 10 insertions(+), 0 deletions(-)

如果已经没有更新的内容可以拉取,它的输出是这样的:

$ git pull
Already up-to-date.

对于 git push 而言也是大同小异,失败时:

$ git push
Connection reset by 13.229.188.59 port 22
fatal: Could not read from remote repository. Please make sure you have the correct access rights
and the repository exists.

成功时:

$ git push
Counting objects: 16, done.
Compressing objects: 100% (10/10), done.
Writing objects: 100% (10/10), 1.05 KiB, done.
Total 10 (delta 7), reused 0 (delta 0)
remote: Resolving deltas: 100% (7/7), completed with 6 local objects.
To git@github.com:goodpaperman/apue.git
87748c7..08e3a1e master -> master

已经是最新时:

$ git push
Everything up-to-date

于是很自然的想到了一个解决方案:一直 spawn git pull / push 直到 expect 到我们想要的输出 "xxx up-to-date."

重启失败的操作

利用上面的思路,写出了下面的 expect 脚本

pull.exp

 #! /usr/bin/expect -f
set timeout 30
for {set i 0} {$i<=10} {incr i} {
puts "start pulling git $i"
spawn git pull
expect "Already up-to-date." { puts "pulling ok"; exit }
}

这段脚本使用了 expect 循环,最多尝试 10 次,如果仍然拉取不成功,则可能是其它原因导致的,此时退出循环。

push.exp

 #! /usr/bin/expect -f
set timeout 30
for {set i 0} {$i<=10} {incr i} {
puts "start pushing git $i"
spawn git push
expect "Everything up-to-date" { puts "pushing ok"; exit }
}

与 pull 类似,只是 expect 的特征串不同,这里使用 “Everything up-to-date” 代替 “Already up-to-date.”

但是这样写有个缺点,就是如果这个做成脚本放在某个目录下的话,我需要明确指定对应的路径才可以调用它。有没有什么办法可以像命令一样随时随地的调用这个脚本呢?

使用 alias

在你的系统上输入 alias 可以查看当前开启的命令别名。

$ alias
alias cd='cdls'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias vi='vim'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

可以看到我的机器上 cd 命令被重定义为 cdls,这又是个什么神奇的东东呢,打开 ~/.bashrc,可以看到它的定义:

 $ cat ~/.bashrc
# .bashrc # Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi # User specific aliases and functions
cdls() {
cd "${1}";
ls;
} alias cd='cdls'

其实就是一个 shell function,里面组合调用了 cd 与 ls 命令,达到切换到新目录同时列出目录内容的功能。看到这里,类比着去实现一个 gpull / gpush 应该不难了吧:

 git_pull() {
expect -c 'set timeout 30; for {set i 0} {$i<=10} {incr i} { puts "start pulling git $i"; spawn git pull; expect "Already up-to-date." { puts "pulling ok"; exit } }'
} git_push() {
expect -c 'set timeout 30; for {set i 0} {$i<=10} {incr i} { puts "start pushing git $i"; spawn git push; expect "Everything up-to-date" { puts "pushing ok"; exit } }'
} alias gpull='git_pull'
alias gpush='git_push'

这里使用 expect 的 -c 选项来在一行内输入所有脚本语句,各个语句之间使用分号分隔。在 ~/.bashrc 中加入上面的内容,然后执行以下命令重载 bashrc 文件

$ . ~/.bashrc

就可以使刚加入的 gpull 与 gpush 别名生效啦!当然,这样做了以后,只对当前用户生效,其它用户登录后是无法使用的。可以将这个别名定义在 /etc/bashrc 中,这样所有用户就都可以使用啦~ 下面是执行的效果:

$gpull
start pulling git 0
spawn git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 2), reused 3 (delta 2), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:goodpaperman/apue
   65d83a6..8560ad0  master     -> origin/master
Updating 65d83a6..8560ad0
Fast-forward
 apue.c |   11 +++++------
 1 files changed, 5 insertions(+), 6 deletions(-)
start pulling git 1
spawn git pull
Already up-to-date.
pulling ok
$gpush
start pushing git 0
spawn git push
Counting objects: 5, done.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 316 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To git@github.com:goodpaperman/apue.git
   8560ad0..0d3c3c7  master -> master
start pushing git 1
spawn git push
Everything up-to-date
pushing ok

从上面的输出可以看到一个问题,就是第一次实际上已经 pull / push 成功了,但是由于没有得到我们想要的输出,操作又被重启了一次,直到它输出 xxxx up-to-date 为止。可见我们的 expect 也不是非常智能啊,有关于这个的改进就留给各位看官了……

结语

其实我们这里只是用了 expect 的脚本语法,并没有用到它更高深的部分:终端控制,其实与 expect 类似的还有 script 和 tee 等命令,它们都是在内部开一个伪终端对,来实现对终端输入/输出的重定向能力的。与终端相关的内容,可以参考我之前写的这篇文章:[apue] 书中关于伪终端的一个纰漏

参考

[1]. Linux-expect命令详解

[2]. expect用法

[3]. expect语法基础: while、for 循环、if 语句的用法示例

[4]. expect(spawn) 自动化git提交和scp拷贝---centos(linux)

使用 expect 重启失败的 git pull/push 操作的更多相关文章

  1. 解决git pull/push每次都需要输入密码问题 和 HttpRequestException encountered

    如果我们git clone的下载代码的时候是连接的https://而不是git@git (ssh)的形式,当我们操作git pull/push到远程的时候,总是提示我们输入账号和密码才能操作成功,频繁 ...

  2. git pull push 所有分支

    因为远端 git 服务器上有很多分支,一个个分支pull太麻烦,所以找了 pull 所有分支的方法,如下: git branch -r | grep -v '\->' | while read ...

  3. Git复习(五)之多人协作、git push失败、git pull失败

    多人协作 多人协作时,大家都会往master和dev分支上推送各自的修改. 现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把SSH Key添加到GitHub)或者同一台电脑的另一个目录下克隆: $ ...

  4. linux服务器git pull/push时提示输入账号密码之免除设置

    1.先cd到根目录,执行git config --global credential.helper store命令 [root@iZ25mi9h7ayZ ~]# git config --global ...

  5. linux git pull/push时提示输入账号密码之免除设置

    1.先cd到根目录,执行git config --global credential.helper store命令 [root@iZ25mi9h7ayZ ~]# git config --global ...

  6. idea中git pull push需要反复输入密码

    在使用idea开发的过程中,在终端terminal中git pull和git push时遇到一个问题,一个是 每次提交都需要输入用户名和密码,,从网上找了下解决方案,记录一下. 解决: 打开git终端 ...

  7. idea中git git pull push需要反复输入密码

    在使用idea开发的过程中,在终端terminal中git pull和git push时遇到一个问题,一个是 每次提交都需要输入用户名和密码,,从网上找了下解决方案,记录一下. 解决: 打开git终端 ...

  8. linux服务器git pull/push时避免频繁输入账号密码

    1.先cd到根目录,执行git config --global credential.helper store命令 [root@iZ25mi9h7ayZ ~]# git config --global ...

  9. git pull push 不用输入用户名和密码的方法

    1.在计算机的安装盘下找到 '用户' 这个文件夹打开. 2.找到'用户' 文件夹下面你当前的用户. 3.新建'.gitconfig' 文件 4. [user] email =  name = [pus ...

随机推荐

  1. Java基础之Synchronized原理

    思维导图svg: https://note.youdao.com/ynoteshare1/index.html?id=eb05fdceddd07759b8b82c5b9094021a&type ...

  2. Nginx 从入门到放弃(一)

    Nginx nginx的使用场景 静态资源服务 通过本地文件系统提供服务 反向代理服务 nginx的强大性能 缓存 负载均衡 API服务 OpenResty nginx优点 高并发.高性能 可扩展性好 ...

  3. Python数据结构(二)

    array固定类型的数据序列,与list类似,只不过成员必须是相同的基本类型 array.typecodes #包含所有可用类型代码的字符串bBuhHiIlLqQfd 输入代码 C型 Python类型 ...

  4. 如何修复 WordPress 中的 HTTP 错误

    如何修复我们会向你介绍,如何在 Linux VPS 上修复 WordPress 中的 HTTP 错误. 下面列出了 WordPress 用户遇到的最常见的 HTTP 错误,我们的建议侧重于如何发现错误 ...

  5. Hadoop2.7.7 centos7 完全分布式 配置与问题随记

    Hadoop2.7.7 centos7 完全分布式 配置与问题随记 这里是当初在三个ECS节点上搭建hadoop+zookeeper+hbase+solr的主要步骤,文章内容未经过润色,请参考的同学搭 ...

  6. Jenkins - 解决集成 jmeter+ant 发送邮件时报错:java.lang.ClassNotFoundException: javax.mail.internet.MimeMessage

    jenkins + jmeter +ant 发送邮件失败 问题原因 其实就是缺失 jar 包,导致某些类找不到了 解决方案 点击该网站,下载commons-email.jar包 点击该网站,下载act ...

  7. day12 作业

    1.通用文件copy工具实现 with open("a.txt","r",encoding="utf-8") as f ,open(&quo ...

  8. easy tornado

    easy tornado 题目分析 这是一道2018年护网杯的题目 /flag.txt /welcome.txt /hints.txt 一共有3个文件. /flag.txt flag in /flll ...

  9. scala 数据结构(十一):流 Stream、视图 View、线程安全的集合、并行集合

    1 流 Stream stream是一个集合.这个集合,可以用于存放无穷多个元素,但是这无穷个元素并不会一次性生产出来,而是需要用到多大的区间,就会动态的生产,末尾元素遵循lazy规则(即:要使用结果 ...

  10. java 面向对象(三十二):泛型一 泛型的理解

    1.泛型的概念所谓泛型,就是允许在定义类.接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型.这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量.创建对象时确定 ...