コンピュータに指示を出す方法
Telling a Computer to Do Things (will-keleher.com)
要約
この記事は、開発者がコンピュータに指示を出すための基本的なスキルであるシェルスクリプティングの重要性について論じています。著者は、GUIに頼るだけでは限界があり、コマンドラインツールやスクリプトを使いこなすことで、より複雑なタスクを自動化し、生産性を向上させられると主張しています。シェルスクリプトの習得は、単なる構文だけでなく、利用可能なツール(fzf, xargs, sedなど)の知識が鍵となると述べています。
全文翻訳
コンピュータに指示を出す方法
キャリアの最初の数年間、私はコンピュータに指示を出す方法を知りませんでした。ターミナルからいくつかのコマンドを実行することはできましたが、一度に一つの単純なコマンドに限られていました。私のターミナルは世界で最も劣悪なGUIであり、シェルはアプリケーションラッパーを持たないプログラムを開始するための方法だと考えていました。
根本的なレベルでは、以下のようなロジックや複数のプログラムを連携させる必要があることをコンピュータに達成させることはできませんでした。
- あることを行い、次に別のあることを行う
- コマンドが失敗した場合、エラーメッセージをログに出力する
- コンピュータ起動時にプログラムを開始する
- 2つのコマンドを同時に実行する
- ディレクトリ内のすべてのファイルをループ処理し、それぞれにアクションを実行する
- あるコマンドの出力を別のコマンドの入力として使用する
理論的には、NodeJSを使ってそのようなプログラムを書くこともできたでしょう。実際には、一度もそうしませんでした。これは部分的に考え方の問題でした。コマンドラインで使用するプログラムを、自分が制御できるものとして考えることに慣れていなかったのです。残りはスキルの不足でした。私が使用しているプログラムを、自分で書いたスクリプトに統合できるほど十分に理解していませんでした。
それでもシェルの学習を続け、基本的なロジックを持つこのようなスクリプトをなんとかこなせるレベルにゆっくりと到達しました。
set +e
npm install
status_code=$?
if [[ "$status_code" != "0" ]]; then
echo "Something went wrong with your npm install. Check your ~/.npmrc to make sure it's authed to our registry."
exit 1
fi
set -e
シェルスクリプトの経験によって、あなたは今、混乱しているかもしれません。すみません。(もしまだ目が血走っていないなら、なぜそうなるべきかの理由はこちらです:1)
私が書いたコマンドやスクリプトには問題がありましたが、それは私にとって変革的でした。既存のプログラムを連携させて目標を達成するようにコンピュータに指示する能力を得たのです。それは、プログラミングを学ぶこと自体から得られた変化と少し似ていました。
何年もの間、私はゆっくりとより多くのシェルツールを学び、シェル構文を理解していきました。新しいツールやパターンを学ぶたびに、ドリルが必要な問題にハンマーを使ったために、過去の問題がどれほど簡単に解決できたかという痛みを伴う瞬間がありました。
その間、私は(信じられないほど優秀な!)多くのエンジニアと仕事をしてきましたが、彼らはシェルにそれほど時間を費やさず、代わりにGUIを使ってテストの実行、gitの管理、データベースとの対話、日々のタスクなどを行っていました。GUIに頼るのは、GUIが対応できるように設定されていないことを達成したいと思ったときまではうまくいきます。そして、GUIの能力に限定されるという考え方に陥りやすいと思います。私は、コマンドを繰り返し実行するためにwhileを使ったり、ディレクトリ内のすべてのファイルに同じ操作を行うためにforループを書いたりする方法を知らなかったために、多大な労力を費やしている熟練したエンジニアを見てきました。
同じGUI中心のアプローチが、シェルスクリプトの編集や保守においても問題になると思います。ほとんどの企業には、BashやZshのような言語で、ビルド、デプロイ、コードの検証、テストを行うためのかなりの量の必須ロジックがあると推測します。あなたが使用しているツールの言語に慣れていない場合、それを簡単に読んだり改善したりすることはできません。コードが実行されるリモートサーバーにどのように動作するかを指示することはできても、リンティングやテストを並列で実行するようなことを自分のコンピュータに指示することはできないというのは、残念なことです!
ここで少し寄り道しましょう:なぜこれほど多くのスクリプトが、チームが使用する主要な言語ではない言語で書かれているのでしょうか?コマンドを簡単に連携させるために設計された言語で、コマンドを連携させるスクリプトを書く方が、より人間工学的であると私は考えています。非常に簡単な例として、テストが失敗するまで実行するスクリプトを見てみましょう:while pnpm exec mocha ./pathToFile.test.ts; do true; done。この構文には明らかに批判すべき点がありますが、NodeJSでの見た目を見てみましょう。
const { execSync } = require("child_process");
while (true) {
try {
execSync(`pnpm exec mocha ./pathToFile.test.ts`, { stdio: "inherit" });
} catch (err) {
console.error("failed", err);
break;
}
}
ここには多くの粗削りな部分や落とし穴があり、個人的にはシェルの方が簡単だと思います!ファイルを作成したり、child_processを要求したり、出力を表示するために{ stdio: "inherit" }を設定したりする必要はありません。そしてこれは、複数のプログラムをパイプでつなげたり、出力をキャプチャしたり、一時ファイルを使用したりしない、非常に単純な例です!
💡
これは、ビルドスクリプトをBashやZshなどの言語で書くことに甘んじる必要があるという意味ではありません!JavaScriptをよく知っているチームにとって、私が楽しんでいるツールの一つはzxです。これにより、これらのスクリプトを非常に人間工学的に記述および保守できるようになると考えています。RubyとPythonはどちらもNodeJSよりは簡単ですが、この種の簡単なスクリプトを書くにはさらに多くの儀式を必要とする言語がたくさんあると思います。
シェルスクリプトがビルドスクリプトにとって常に簡単であると主張しているわけではありません!問題が単純で、2つのプログラムを連携させるだけであれば、BashやZshのようなツールは非常に人間工学的だと感じられます。より洗練されたロジックやデータ型が必要になったら、より洗練されたロジックやデータ型を表現(そしてテスト!)しやすくする言語を選択したくなるでしょう。
シェル言語でビルドスクリプトが書かれていると不平を言う多くのエンジニアは、実際には別の言語に変換しようとしたことがないのではないかと推測します。構文(潜在的に)がより複雑であるという aside、シェルスクリプトを書くことを学ぶことの大きな部分は、実際には構文ではありません。コマンドを連携させるスクリプトを変換しても、連携させているコマンドの動作を実際には知らない場合、結果のスクリプトも同様に不可解になる可能性が高いです。
シェルを知るということは、コンピュータに指示を出すことができるようになるということです。これは、あなたが達成したいことを行うプログラムの知識に大きく依存します!シェルの知識は、構文が20%、実際に組み合わせたい良いツールボックスを持っていることが80%だと主張します。
fzfを知っていれば、インタラクティブなファジー検索でクイックユーティリティを構築できます。(例:git checkout $(git branch --sort=-committerdate | fzf)でブランチをファジー選択できます。)tldrまたはegを知っていれば、このリストにある他のコマンドの使用例をすぐに参照できます。rsyncを知っていれば、変更されたファイルをより高速なリモートサーバーにコピーして、重くて遅い処理を実行できます。xargsを知っていれば、コマンドを段階的に構築し、作業を並列化できます。sed -iまたはast-grepを知っていれば、多数のファイルにわたる複雑なパターンを迅速に書き換えることができます。direnvを知っていれば、コードベースでコマンドを実行するすべての人に対して正しい環境変数が設定されていることを確認できます。duckdbを知っていれば、ローカルでCSVやJSONファイルをクエリするためにSQLを書き、より大きなスクリプトの一部として使用できます。ghを知っていれば、PRを確認したり、CLIから新しいPRを開いたりするスクリプトを構築できます。ngrokを知っていれば、ローカルポートを迅速に提供し、別のマシンでテストできます。
各追加のプログラムを知ることで、新しいツールはすでに知っている他のすべてのツールと組み合わせて使用できるため、機能はさらに拡大します。
私はシェルスクリプトのエキスパートから最も遠い存在であり、深く知っている人々と比較するとひどいものですが、それでもプログラムを連携させるのに十分なシェル構文を知っていること、そして実際に連携させたいプログラムを十分に知っていることから多くの価値を得ています。
コンピュータに指示を出すことは素晴らしいことです!
-
このスクリプトは善意で書かれましたが、
ifの主なモードがコマンドを取ることを知らなかったため、過度に複雑でした。[[(またはtest)は単なる特別なコマンドです。これは、私たちのifチェックが単に! npm installで済むことを意味します。- このアプローチでは、コマンドの失敗を許可するために
set +eを使用する必要はありません。(set +eとその後set -eで元に戻すのは、コマンドが失敗したためにシェルスクリプト全体が失敗するのを避けるためです - Unofficial Bash Strict Mode) - また、
$?で終了コードをキャプチャする必要もありません。
if ! npm install; then echo "Something went wrong with your npm install. Check your ~/.npmrc to make sure it's authed to our registry." exit 1 fi - このアプローチでは、コマンドの失敗を許可するために
-
以前のブログ投稿へのHNコメントより:「この投稿は…私が遭遇するシェルスクリプトの99%が非効率的に見える理由をもう不思議に思わないようにしてくれます。」 ↩︎