没有什么比让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 -n1
。 xargs
工具从输入中读取项目并执行找到的所有命令(如果找不到则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存储库的目录的完整路径。
我们必须注意以下两个假设:
- 包含资源库的本地计算机上目录的名称与URL中资源库的名称相同(如果是使用上述一种方法克隆的,则为这种情况);
- 当前,每个存储库都检出到要推送的分支,即。
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一线时,它可能是有趣且方便的快捷方式。 至少,了解xargs
和awk
类的工具可以帮助自动化和减轻工作中的繁琐工作。 但是,还有一些缺点。
就易于理解,易于维护和易于使用的工具而言,Bash一线难熬。 与使用if
或while
循环的Bash脚本相比,编写它们通常更复杂,并且读取起来当然更复杂。 当我们编写它们时,可能会在某处遗漏单引号或右括号。 正如我希望这篇文章所演示的那样,他们也可以做很多解释。 那为什么要使用它们呢?