Bashプログラミング
 Computer >> コンピューター >  >> プログラミング >> Bashプログラミング

GitHubとGitLabのリポジトリを一括クローン・バックアップするBashワンライナーの作り方

何時間もかかる退屈な作業を、たった1行のエレガントなBashコマンドで自動化できることほど爽快なものはありません。

最近、BashスクリプトでノートPCの環境を自動再構築する試みの一環として(その記事は後日公開予定)、新しいマシンに自分のGitHubリポジトリを手軽にクローンする方法を探していました。調査の末、それを実現するワンライナーを書くことができました。

さらに「卵を同じカゴに入れるな」の精神に従い、GitLab側にバックアップを自動作成・プッシュする別のワンライナーも作りました。以下がその内容です。

すべてのGitHubリポジトリをクローンするBashワンライナー

まず注意点として、クローンしたいGitHubリポジトリのリストが必要です。ただしこれには利点もあります。全リポジトリをまとめて持ってくるのではなく、自分のマシンに必要なものだけを選べるということです。

パスワードを毎回入力せずにGitHubリポジトリをクローンするには、15分間キャッシュされるHTTPS認証情報を使う方法と、筆者のおすすめであるSSH接続を使う方法があります。ここでは簡潔さのため後者を採用し、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でも使えます。

仕組みの解説

このワンライナーは、直感に反するかもしれませんが、右側に入力、左側に処理部分という2つの要素で構成されています。順序をより直感的にしたい場合は、同じコマンドを次のように書くこともできます。

<gh-repos.txt xargs -n1 git clone 

入力ファイルgh-repos.txtの各行に対してコマンドを実行するために、xargs -n1を使用します。xargsは入力から項目を読み込み、コマンドを構築して実行するツールです(コマンドが指定されていない場合はechoを実行します)。デフォルトでは項目はスペース区切りとみなされますが、改行区切りでも動作し、リストが読みやすくなります。-n1フラグは、1回のコマンド実行につき引数を1つ(今回の場合は1行)だけ渡すよう指示します。あとはgit cloneでコマンドを組み立てれば、xargsが各行ごとに実行してくれます。以上です。

GitLab上に多数のリポジトリを作成・プッシュするBashワンライナー

GitLabはGitHubと異なり、先にWebサイトでリポジトリを作成しておかなくてもよい便利な仕様があります。なんとターミナルから直接、新しいGitLabリポジトリを作れるのです。新規作成されたリポジトリはデフォルトでプライベートに設定されるため、公開したい場合は後から手動で変更する必要があります。

GitLabの公式ドキュメントではgit push --set-upstreamによるプッシュ時のプロジェクト作成が案内されていますが、GitLabをバックアップ用途で使うにはあまり便利ではありません。今後リポジトリで作業する際には、追加の手間なしにGitHubとGitLabの両方へプッシュできる1つのコマンドを実行したいところです。

このワンライナーを機能させるには、GitLab用のリポジトリ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スクリプトを書くべきだ、というのが筆者の控えめな意見です。

仕組みの解説

このワンライナーは、gl-repos.txtファイルの各行(各URL)を入力として受け取ります。awkでローカルマシン上のリポジトリディレクトリ名を切り出し、その情報をもとに大きなコマンドを組み立てます。もしprintawkの出力を確認すると、次のように表示されます。

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)です。

  • \/はエスケープされた/文字
  • |は「または」を意味し、どちらかの式に一致させます
  • (\.git)はURL末尾の「.git」に一致するキャプチャグループ(.はエスケープ済み)。これは少し裏技的で、「.git」自体は厳密には何も分割していません(反対側に何もないため)が、この部分を取り除く簡単な方法として機能します

分割位置をawkに伝えたら、フィールド演算子で目的の部分文字列を取り出せます。フィールドは$に続けて列番号で参照します。この例では2番目のフィールド$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()関数は子プロセスを生成してコマンドを実行し、完了後に制御を返します。平たく言えば、awkが入力ファイルを処理しているメインプロセスを中断することなく、各リポジトリに対してGitコマンドを1つずつ順番に実行できるのです。最終的なコマンドをもう一度掲載します。

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)と表示されているURL)からのみ取得しようとすることに注意してください。複数のGitリポジトリから同時にプルすることも不可能ではありませんが、かなり複雑で、本記事の範囲外です。興味があれば、複数リモートへのプッシュ/プルに関する解説記事や、Git公式ドキュメントのリモートに関するページも参考になるでしょう。

Bashワンライナーの簡潔さについて考える

Bashワンライナーは、きちんと理解したうえで使えば、楽しくて便利なショートカットになり得ます。少なくとも、xargsawkといったツールの存在を知っているだけでも、日々の作業における多くの退屈な部分を自動化・軽減できます。しかし、デメリットもあることは事実です。

わかりやすく、保守しやすく、他の人にとっても取り組みやすいツールという観点では、Bashワンライナーは決して優秀とは言えません。ifwhileループを使ったBashスクリプトよりも書くのが難しく、読むのは言うまでもなく大変です。書いている途中でシングルクォートや閉じ括弧をどこかで忘れてしまうこともあるでしょうし、この記事が示すように、かなりの説明量が必要にもなります。それでもなぜ使うのでしょうか?

ケーキを焼くレシピを思い浮かべてください。手順と材料を理解し、道具を揃えます。ところが考えているうちに、材料をオーブンに正確な順番で投げ込めば、ケーキが瞬間的に完成することに気づきます。実際に試してみたら、本当にうまくいきました!

それは、かなり満足感がありますよね?

  1. LinuxターミナルのBashプロンプトの色と表示内容をカスタマイズする方法

    現在、Bashは多くの(ほぼすべての)モダンなLinuxディストリビューションでデフォルトのシェルとして採用されています。しかし、ディストリビューションによって、ターミナルの文字色やプロンプトの表示内容が異なることに気づいたことはありませんか?アクセシビリティの向上のため、あるいは単なる好みでこれをカスタマイズしたいと思ったことがあるなら、ぜひ読み進めてください。本記事では、その具体的な方法を分かりやすく解説します。PS1 Bash環境変数とはコマンドプロンプトやターミナルの外観は、PS1という環境変数によって制御されています。Bashのmanページによると、PS1は「プライマリプロンプト文字

  2. Linuxでカスタムシェル関数とライブラリを作成・活用する方法

    Linuxにおいて、シェルスクリプトはシステム管理タスクの実行や自動化、簡単なコマンドラインツールの作成など、さまざまな場面で役立ちます。本記事では、Linux初心者の方に向けて、独自のシェルスクリプトを保存すべき信頼性の高い場所、カスタムシェル関数やライブラリの書き方、そして他のスクリプトからライブラリ内の関数を呼び出す方法について解説します。 シェルスクリプトの保存場所 スクリプトを実行するときに毎回フルパス(絶対パス)を入力せずに済むようにするには、そのスクリプトを $PATH 環境変数に含まれるいずれかのディレクトリに保存しておく必要があります。 現在の $PATH の内容は、以下のコ