问题的提出

最近使用 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. asp .net core发布订阅kafka

    Kafka是一种高吞吐量的分布式发布订阅消息系统,有如下特性: 通过O的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能. 高吞吐量:即使是非常普通的硬件Ka ...

  2. Js数据类型、Json格式、Json对象、Json字符串

    数据类型,从结构上看,所有的数据最终都可以分成三种类型: 第一种类型是scalar(标量),也就是一个单独的string(字符串)或数字(numbers),比如“北京”这个单独的词. 第二种类型是se ...

  3. 看完这篇 HashSet,跟面试官扯皮没问题了

    我是风筝,公众号「古时的风筝」,一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农! 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在 ...

  4. Centos7解压Zip文件

    一.安装支持ZIP的工具 yum install -y unzip zip 二.解压zip文件 unzip 文件名.zip 三.压缩一个zip文件 zip 文件名.zip 文件夹名称或文件名称

  5. 小白写了一堆if-else,大神实在看不下去了,竟然用策略模式直接摆平了

    这里涉及到一个关键词:策略模式,那么到底什么是策略模式呢?本文就来好好给大家讲讲策略模式,大家可以带着如下几个问题来阅读本文:   1. 如何通过策略模式优化业务逻辑代码(可以根据自己从事的工作思考) ...

  6. js element类型的属性和方法整理

    Element类型 除了Document类型,我们Web编程中最常用的类型就是Element类型啦.Element 类型用于表现XML或HTML元素,提供了对元素标签名,子节点,特性的访问 特征 no ...

  7. pdf流文件转图片

    需求:将后台返回的pdf流文件转换成图片与页面其他内容一起打印pdf流文件不能直接在前台显示,需要借助pdf.js+viewer.js. 一般情况下,如果要打印pdf流文件,可以直接在新打开的view ...

  8. Report,又是一道思维题

    题目: Each month Blake gets the report containing main economic indicators of the company "Blake ...

  9. POJ1017贪心

    题意:小P开了一家淘宝店铺,店铺里所有的商品高度都为h,但长和宽分别为1*1,2*2,3*3,4*4,5*5,6*6六种规格.这一天来了一个大客户,他订购了很多物品.所以小P需要将东西都邮寄给他,但是 ...

  10. Java面向对象详解-下

    一. static static:静态的,可以用来修饰属性.方法.代码块(或初始化块).内部类 static修饰属性(类变量): 由类创建的所有的对象,都共用这一个属性 当其中一个对象对此属性进行修改 ...