技術ブログ用の参考URLリンクを作成してクリップボードにコピーしてくれるコマンドを作ってみた

## はじめに

技術ブログを書くとき、参考URLのリンクを載せることは頻繁にあると思います。僕は今までURLとタイトルをそれぞれコピーアンドペーストして作っていたのですが、案外面倒だなーと思っていました。

特に別タブで開く用の```target="_blank" rel="nofollow noopener"```などをつけたaタグのリンクは作るのが面倒で、コマンド一発でリンク作成からクリップボードにコピーまでしてくれたらだいぶ楽なのになーと思っていました。

そこでこの記事では、**参考URLからリンクを作成し、かつクリップボードにコピーしてくれるコマンド**を前々から試したかったシェル芸っぽく作ってみたので、そちらについて書きたいと思います。

## 環境

* Mac OS X 10.13.3
* zsh 5.5.1

今回はzshを使ってますが、おそらくbashなどでも動くと思いますー。

## tl;dr

下記の関数を```.zshrc```にペーストして保存し、```source /path/to/.zshrc```します。
(bashを利用されている方は```.zshrc```を```.bashrc```とか```.bash_profile```に読み替えてください)

```zsh:.zshrc
# プレーンなaタグを作成する場合
function al() {
  curl $1 -sL | xmllint --html --xpath 2>/dev/null "//head/title" - | sed -e "s;<title>;<a href=\"${1//&/\&}\" target=\"_blank\" rel=\"nofollow noopener\">;g" -e "s;</title>;</a>;g" | pbcopy ; pbpaste
}

# Markdownのリンクを作成する場合
function ml() {
  curl $1 -sL | xmllint --html --xpath 2>/dev/null "//head/title" - | sed -e "s;<title>;\[;g" -e "s;</title>;\]\(${1//&/\&}\);g" | pbcopy ; pbpaste
}
```
その後、こんな感じで```al URL```または```ml URL```とコマンドを打つと、

1. htmlのページを取得して
2. titleタグを抽出して
3. リンクに置換して
4. 結果をクリップボードにコピーしつつ標準出力

してくれます。後はそのまま執筆中のブログ記事などにペーストしちゃってください。
aタグの方は別タブで開くリンクの設定```target="_blank" rel="nofollow noopener"```を入れていますが、不要な場合は削っちゃってください。

### 実行例

```zsh
$ al https://railsguides.jp/getting_started.html
<a href="https://railsguides.jp/getting_started.html" target="_blank" rel="nofollow noopener">Rails をはじめよう | Rails ガイド</a>

$ ml https://railsguides.jp/getting_started.html
[Rails をはじめよう | Rails ガイド](https://railsguides.jp/getting_started.html)
```

## コマンド解説

それぞれのコマンドでどんなことをやっているかを簡単に解説しますー

### パイプごとの処理概要おさらい

パイプの処理概要を再掲します。パイプ1,2,4は```al```と```ml```どちらも共通で、パイプ3だけ中身が異なります。

* パイプ1: htmlのページを取得
* パイプ2: titleタグを抽出
* パイプ3: リンクに置換
* パイプ4: クリップボードにコピーしつつ標準出力

### パイプ1: htmlのページを取得

```
curl $1 -sL 
```

項目|詳細
:--|:--
curl|指定したURLにリクエストを送信し、HTMLレスポンスを受け取る。通常はGETリクエストを送信し、標準出力にレスポンスを出力
$1|コマンドライン引数。指定したURLが入る
-s|--silentの省略形。データ取得のメーターやエラーメッセージを非表示にする
-L|--locationの省略形。指定したURLでリダイレクトされたらリダイレクト先に再リクエストする

まずはおなじみ```curl```でhtmlページを取ってきています。必要なのはhtmlページだけなので、それ以外の情報は表示させないように絞りつつ、リダイレクトされてもページ取得できるようにしています。

ちなみに```curl```のオプションに```-o -```をつけても標準出力に結果を出力してくれますが、何もつけなくてもデフォルトで標準出力してくれるので省略しています。

* 参考：<a href="https://curl.haxx.se/docs/manpage.html" target="_blank" rel="nofollow noopener">curl - How To Use</a>

### パイプ2: titleタグを抽出

```
xmllint --html --xpath 2>/dev/null "//head/title" -
```

項目|詳細
:--|:--
```xmllint```| xml形式のファイルをパースするコマンド
```--html```| html形式ファイルをパースする
```--xpath```| xpath形式でタグを検索する
```2>/dev/null```| 標準エラー出力を/dev/nullに出力して非表示にする
```"//head/title"```| xpath形式で```<head>```タグ内の```<title>```タグを取得する
```-```| 検索結果を標準出力に出力する

Webスクレイピングでよく見るxpathでhtmlをパースしたかったので、```xmllint```を使っています。LinuxやMacには標準で入っているコマンドだと今回初めて知りました…！

curlで取得したhtml形式のデータから、xpathで```head```以下の```title```タグを抽出しています。またパース時の余計な標準エラー出力は不要なので、```2>/dev/null```を使って省くようにしています。

* 参考：<a href="https://linux.die.net/man/1/xmllint" target="_blank" rel="nofollow noopener">xmllint(1): XML tool - Linux man page</a>
* 参考：<a href="https://qiita.com/greymd/items/368faf898c161165fee7" target="_blank">プリキュアで学ぶワンライナーWebスクレイピング - Qiita</a>

ちなみにgrepだと```grep -o "<title>.*</title>"```みたいな感じで抽出できるのですが、```<body>```タグ内にiframeなどを使って別の```<title>```が入り込んでることもあると思います。その為、xpathで```<head>```内の```<title>```タグに限定したいと思い、```xmllint```を使っています。


* 参考：<a href="http://vim.wikia.com/wiki/Format_your_xml_document_using_xmllint" target="_blank" rel="nofollow noopener">Format your xml document using xmllint | Vim Tips Wiki | FANDOM powered by Wikia</a>

### パイプ3: リンクに置換

```
# alの場合
sed -e "s;<title>;<a href=\"${1//&/\&}\" target=\"_blank\" rel=\"nofollow noopener\">;g" -e "s;</title>;</a>;g"

# mlの場合
sed -e "s;<title>;\[;g" -e "s;</title>;\]\(${1//&/\&}\);g"
```

項目|詳細
:--|:--
```sed```| stream editorの略。ファイルまたはパイプから入力されたデータを編集できる
```-e``` | 編集処理用コマンドを指定。```-e```を複数指定すれば複数のコマンドをまとめて実行できる
```"s;<title>;<a href=\"${1//&/\&}\" target=\"_blank\" rel=\"nofollow noopener\">;g"```|※```al```の場合<br />文字列```<title>```を```<a href="URL" target="_blank" rel="nofollow noopener">```に変換
```"s;</title>;</a>;g"```|※```al```の場合<br />文字列```</title>```を```</a>```に置換
```"s;<title>;\[;g"```| ※```ml```の場合<br /> 文字列```<title>```を```[```に置換
```"s;</title>;\]\(${1//&/\&}\);g"```|※```ml```の場合<br /> 文字列```</title>```を```](URL)```に置換

ここでは```<title>```タグをリンクの形に愚直に置換する処理をしています。

多分一番わかりづらいのが```${1//&/\&}\```の部分だと思います。ここでは```${変数//検索文字列/置換文字列}```の形式の変数内文字列置換（変数内の検索合致した全ての文字列置換）を使っています。

コマンドラインから渡した引数```$1```内のすべての文字列アンパサンド```&```を、エスケープした```\&```に置換しています。

* 参考：<a href="https://qiita.com/aosho235/items/c36568830a8d47288284" target="_blank" rel="nofollow noopener">bashの変数内文字列置換まとめ - Qiita</a>


```sed``` では検索でマッチした文字列をアンパサンド```&```で参照するようになっているため、```&```が入っているURLだとその部分が置換されてしまう挙動になってしまうため、こちらの処理を入れています。(```&```はYouTubeのURLとかに入ってますよね)

* 参考：<a href="https://linuxjm.osdn.jp/html/GNU_sed/man1/sed.1.html" target="_blank" rel="nofollow noopener">Man page of SED</a>

### パイプ4: クリップボードにコピーしつつ標準出力

```
pbcopy ; pbpaste
```

項目|詳細
:--|:--
```pbcopy```|パイプから入力されたデータをクリップボードにコピーする
```;```| ```command1 ; command2```において、```command1```が実行完了したら```command2```も実行する
```pbpaste```| クリップボードにあるデータを標準出力に出力する

```pbcopy```、```pbpaste``` はどちらもMacに搭載されているコマンドです。Ubuntuだと```xsel```で同じような事ができるようです。

* 参考：<a href="https://qiita.com/koara-local/items/e9ba9a15406aa5fe8299" target="_blank" rel="nofollow noopener">[Linux] コマンドラインでの標準出力をクリップボードにコピーする - Qiita</a>

ここでは```pbcopy```でクリップボードにパイプで渡されてきた内容をコピーし、```pbpaste```でクリップボードにコピーした内容をペーストすることで標準出力に表示しています。

```pbcopy``` だけでも用途として足りてはいるのですが、どんなリンクが作成されたか標準出力でも確認できたほうが良いなと思い、表示させるようにしています。

出力結果をクリップボードにコピーできるのは一手間省けた感があり、かなり使い勝手が良いなと感じています。

* 参考：<a href="https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/pbcopy.1.html" target="_blank" rel="nofollow noopener">pbcopy(1) Mac OS X Manual Page</a>

## 想定Q&A

### これであらゆる参考URLのリンクを作ってくれるの？

UTF-8のページであればほとんど問題なくリンクを作れると思うのですが、EUC-JPなどの文字コードを使っているサイトだとまれに文字化けすることがあります。

今回文字コードの変換対応までは入れていないので、もし文字化けしたらお手数ですが手動でリンクを作成するか、```iconv```とか```nkf```とかでうまく文字コード変換してもらえたらと思います。

### 何でワンライナーをfunctionに登録しているの？

シェル芸というくらいなので、ワンライナーで完結するんじゃないの？という声が聞こえてきそうな気がしています。

今回はリンク作成を簡単にしたかったということもあり、ワンライナーを毎回コピー&ペーストするのは用途に合わないなと思っていました。

なのでワンライナーを```alias```に登録すればいいかなーと思ってましたが、```alias```だとコマンドラインの引数をパイプ間でうまく引き渡してくれないとのことで。。

```function``` を使えばできるとのことだったので、こちらを```.zshrc```に登録して使う形にしました。

* 参考：<a href="https://www.linuxquestions.org/questions/programming-9/bash-command-works-at-command-line-but-not-via-an-alias-934969/" target="_blank" rel="nofollow noopener"> bash: command works at command line, but not via an alias</a>

### 何でプレーンなaタグ用とMarkdown用の2つ作ったの？

当初はaタグを作成する用の```al```しか作ってなかったのですが、記事を書いている最中に、Qiitaでは```[TEXT](URL)```の形でよしなに```<a href="" target="_blank" rel="nofollow noopener">```のリンクに変換してくれるのを知ったからですw
ちなみに```ml``` はフォーマットをちょっと変えるだけだったのですぐできました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/3b679cc0-2bd7-39b2-afb1-d4e83622d85a.png)


こうなると、もはや```al```はいらないかなとも思ったのですが、せっかく作ったのでどちらも載せることにしました。

## 終わりに

僕はこれでリンク作成が楽になりましたー。ちなみにこの記事のリンクは全て```al``` で作ってみました…！ 
もしもっとこうした方がいいよなどあれば教えていただけるとありがたいです！