没有什么比让Bash自动化完成数小时繁琐工作的Bash优雅系列更令我满意的了。 作为最近使用Bash脚本自动重新创建笔记本电脑的探索的一部分,我想找到一种方法,可以轻松地将GitHub托管的存储库克隆到新计算机上。 经过一番挖掘之后,我写了一篇这样的单线纸。 然后,本着不把所有鸡蛋都放在同一篮子的精神,我写了另一种单行代码来自动创建并推送到GitLab托管的备份。 他们来了。

一个Bash单一代码来克隆您所有的GitHub存储库

警告:您将需要要克隆的GitHub存储库的列表。 这样做的好处是,它为您提供了全面的代理权,使您可以只选择要在计算机上存储的存储库,而不必花很多精力。

您可以轻松地克隆GitHub存储库,而无需每次都输入HTTPS并使用15分钟的缓存凭据,或者,我首选的方法是使用SSH连接到GitHub,而无需每次输入密码。 为简便起见,我假设我们要使用后者,并且我们已经设置了SSH密钥。

gh-repos.txt文件中给出GitHub URL列表,如下所示:



git@github.com:username/first-repository.git
git@github.com:username/second-repository.git
git@github.com:username/third-repository.git



我们跑:



xargs -n1 git clone < gh-repos.txt



这会将列表上的所有存储库克隆到当前文件夹中。 如果替换适当的URL,则同一行也适用于GitLab存储库。

这里发生了什么?

单行代码有两半:违反直觉的输入在右侧,而使事情发生的部分在左侧。 通过编写如下相同的命令,我们可以使这些部分的顺序更直观(也许?):



<gh-repos.txt xargs -n1 git clone



要对输入的每一行gh-repos.txt运行命令,我们使用xargs -n1xargs工具从输入中读取项目并执行找到的所有命令(如果找不到则echo )。 默认情况下,它假定项目之间用空格隔开; 新行也可以使我们的列表更易于阅读。 标志-n1告诉xargs每个命令使用1参数,在本例中为1行。 我们使用git clone构建命令,然后xargs将针对每一行执行。 -

Bash一线式工具,可在GitLab上创建并推送许多存储库

与GitHub不同,GitLab让我们可以做这件漂亮的事情,而不必先使用网站创建新的存储库。 我们可以从终端创建一个新的GitLab存储库 。 新创建的存储库默认设置为“私有”,因此,如果要在GitLab上将其设置为“公开”,则稍后必须手动进行。

GitLab文档告诉我们使用git push --set-upstream来推动创建一个新项目,但是我觉得使用GitLab作为备份不是很方便。 将来在使用存储库时,我想运行一个命令同时推送到GitHub GitLab,而无需我付出额外的努力。

为了使此Bash单线工作,我们还需要GitLab的存储库URL列表(尚不存在的URL)。 我们可以轻松地做到这一点,方法是复制我们的GitHub存储库列表,使用Vim打开它,然后进行搜索和替换



cp gh-repos.txt gl-repos.txt
vim gl-repos.txt
:%s/\<github\>/gitlab/g
:wq



这将产生gl-repos.txt ,看起来像:



git@gitlab.com:username/first-repository.git
git@gitlab.com:username/second-repository.git
git@gitlab.com:username/third-repository.git



我们可以在GitLab上创建这些存储库,将URL添加为远程存储,并通过运行以下命令将代码推送到新的存储库:



awk -F '\/|(\.git)' '{system("cd ~/FULL/PATH/" $2 " && git remote set-url origin --add " $0 " && git push")}' gl-repos.txt



请稍等,我会解释。 现在,请注意~/FULL/PATH/应该是包含我们的GitHub存储库的目录的完整路径。

我们必须注意以下两个假设:

  1. 包含资源库的本地计算机上目录的名称与URL中资源库的名称相同(如果是使用上述一种方法克隆的,则为这种情况);
  2. 当前,每个存储库都检出到要推送的分支,即。 master

单行代码可以扩展以处理这些假设,但是作者的愚见是,在那时,我们确实应该编写Bash脚本。

这里发生了什么?

我们的Bash单行使用gl-repos.txt文件中的每一行(或URL)作为输入。 使用awk ,它拆分出包含本地计算机上存储库的目录名称,并使用这些信息来构建更大的命令。 如果我们要print的输出awk ,我们会看到:



cd ~/FULL/PATH/first-repository && git remote set -url origin --add git@gitlab.com:username/first-repository.git && git push
cd ~/FULL/PATH/second-repository && git remote set -url origin --add git@gitlab.com:username/second-repository.git && git push
cd ~/FULL/PATH/third-repository && git remote set -url origin --add git@gitlab.com:username/third-repository.git && git push



让我们看看如何构建此命令。

awk分割字符串

工具awk可以基于字段分隔符拆分输入。 默认的分隔符是空格字符,但是我们可以通过传递-F标志来更改它。 除了单个字符,我们还可以使用正则表达式字段分隔符 。 由于我们的存储库URL具有固定的格式,因此我们可以通过请求斜杠/和URL末尾.git之间的子字符串来获取存储库名称。

实现此目的的一种方法是使用我们的正则表达式\/|(\.git)

  • \/是转义的/字符;
  • | 表示“或”,告诉awk匹配任一表达式;
  • (\.git)是URL末尾的捕获组,与“ .git”匹配,带有转义符. 字符。 这有点作弊,因为“ .git”并没有严格分割任何内容(另一面没有任何内容),但这是我们轻松实现这一点的简便方法。

告诉awk在哪里分割后,我们可以使用field运算符来获取正确的子字符串。 我们用$字符引用字段,然后用字段的列号引用。 在我们的示例中,我们需要第二个字段$2 。 这是所有子字符串的样子:



1: git@gitlab.com:username
2: first-repository



要使用整个字符串或本示例中的整个URL,我们使用字段运算符$0 。 要编写该命令,我们只需将字段运算符替换为存储库名称和URL。 在我们构建时使用print运行它可以帮助确保所有空间都正确。



awk -F '\/|(\.git)' '{print "cd ~/FULL/PATH/" $2 " && git remote set-url origin --add " $0 " && git push"}' gl-repos.txt



运行命令

我们在system()括号内构建命令。 通过将其用作awk的输出,每条命令在生成并输出后将立即运行。 system()函数创建一个执行我们命令的子进程 ,然后在命令完成后返回。 用简单的英语来说,这使我们可以在每个存储库上一个接一个地执行Git命令,而不会破坏awk使用输入文件执行操作的主要过程。 这是我们的最终命令,所有命令都放在一起了。



awk -F '\/|(\.git)' '{system("cd ~/FULL/PATH/" $2 " && git remote set-url origin --add " $0 " && git push")}' gl-repos.txt



使用我们的备份

通过将GitLab URL添加为远程站点,我们简化了推送到两个外部托管存储库的过程。 如果在其中一个存储库目录中运行git remote -v ,我们将看到:



origin  git@github.com:username/first-repository.git (fetch)
origin  git@github.com:username/first-repository.git (push)
origin  git@gitlab.com:username/first-repository.git (push)



现在,简单地运行不带参数的git push会将当前分支推送到两个远程存储库。

我们还应该注意, git pull通常只会尝试从最初克隆的远程存储库中(fetch)在上面的示例中标记为(fetch)的URL)。 可以同时从多个Git存储库中拉出,但很复杂,超出了本文的范围。 如果您有好奇心,这是推和拉到多个遥控器以帮助您入门的说明。 遥控器上的Git文档也可能会有所帮助。

详细阐述Bash单行代码的简洁性

当了解Bash一线时,它可能是有趣且方便的快捷方式。 至少,了解xargsawk类的工具可以帮助自动化和减轻工作中的繁琐工作。 但是,还有一些缺点。

就易于理解,易于维护和易于使用的工具而言,Bash一线难熬。 与使用ifwhile循环的Bash脚本相比,编写它们通常更复杂,并且读取起来当然更复杂。 当我们编写它们时,可能会在某处遗漏单引号或右括号。 正如我希望这篇文章所演示的那样,他们也可以做很多解释。 那为什么要使用它们呢?