Node.js で Bash スクリプトを書くための究極のソリューション

Node.js で Bash スクリプトを書くための究極のソリューション

序文

最近、bash スクリプトの構文を学んでいますが、bash 構文に慣れていないと、未定義の変数を表示するなど、間違いを犯しやすくなります。シェルで変数が定義されていない場合でも、その変数は使用できますが、結果は期待どおりにならない可能性があります。例えば:

#!バイナリ

# ここでは、変数 var が文字列 abc と等しいかどうかを判断していますが、変数 var は宣言されていません if [ "$var" = "abc" ] 
それから
   # if 判定が真の場合、コンソールに「not abc」と出力します
   echo "abcではない" 
それ以外
   # if 判定が偽の場合、コンソールに「abc」と出力します
   「abc」をエコーする
フィ

結果として、abc が印刷されますが、問題は、このスクリプトがエラーを報告する必要があることです。変数に値が割り当てられていないことがエラーです。

これらのエラーを修正するには、スクリプトの先頭に次の行を追加することを学びました: set -u

このコマンドは、スクリプトがそれを先頭に追加し、存在しない変数に遭遇した場合はエラーを報告して実行を停止することを意味します。

再度実行すると、次のメッセージが表示されます: test.sh: 3: test.sh: num: parameter not set

もう一度、最初に削除しようとしていたと想像してください: rm -rf $dir/* すると dir が空になります。何が起こるでしょうか? rm -rf は削除コマンドです。$dir が空の場合は、rm -rf /* を実行するのと同じになり、すべてのファイルとフォルダーが削除されます。 。 。すると、システムが消えてしまいます。これは、データベースを削除して逃げるという伝説的な行為なのでしょうか?

ノードやブラウザ環境であれば、var === 'abc' を直接使用すると確実にエラーが発生します。つまり、多くの JavaScript プログラミング経験は bash では再利用できません。再利用できれば最高です。

その後、私は探索を始めました。bash の代わりに node スクリプトを使用できれば素晴らしいだろうと思いました。1 日中試行錯誤した後、私は徐々に魔法のツール、Google の zx ライブラリを発見しました。心配しないでください。このライブラリはまだ紹介しません。まず、主流が node を使用して bash スクリプトを記述する方法を見てみましょう。そうすれば、なぜそれが魔法のツールであるかがわかります。

ノードで bash スクリプトを実行する: 不本意な解決策: child_process API

たとえば、child_process APIのexecコマンド

const { exec } = require("child_process");

exec("ls -la", (エラー, stdout, stderr) => {
    if (エラー) {
        console.log(`エラー: ${error.message}`);
        戻る;
    }
    (標準エラー出力)の場合{
        console.log(`stderr: ${stderr}`);
        戻る;
    }
    console.log(`stdout: ${stdout}`);
});

ここで注目すべき点は、まず、exec は非同期ですが、bash スクリプト コマンドの多くは同期であるということです。

また、注意: エラー オブジェクトは stderr とは異なります。child_process モジュールがコマンドを実行できない場合、エラー オブジェクトは空ではありません。たとえば、ファイルが見つからない場合、エラー オブジェクトは null ではありません。ただし、コマンドが正常に実行され、標準エラー ストリームにメッセージが書き込まれる場合、その stderr オブジェクトは null にはなりません。

もちろん同期execコマンドexecSyncを使用することもできます。

// child_process モジュールから exec コマンドをインポートします。const { execSync } = require("child_process");

// hello という名前のフォルダーを同期的に作成します。execSync("mkdir hello");

bash コマンドを実行できる child_process の他の API を簡単に紹介しましょう。

  • spawn: コマンドを実行するために子プロセスを起動する
  • exec: サブプロセスを開始してコマンドを実行します。spawn とは異なり、サブプロセスの状態を知るためのコールバック関数があります。
  • execFile: 実行可能ファイルを実行するために子プロセスを起動します
  • fork: spawn と似ていますが、子プロセスが実行する必要がある JavaScript ファイルを指定する必要があります。

exec と ececFile の違いは、exec はコマンドの実行に適しているのに対し、eexecFile はファイルの実行に適していることです。

Node が bash スクリプトを実行する: 高度なソリューション shelljs

シェルは 'shelljs' を必要とします。
 
# ファイルを削除するコマンド shell.rm('-rf', 'out/Release');
// ファイルのコピーコマンド shell.cp('-R', 'stuff/', 'out/Release');
 
# lib ディレクトリに切り替え、ディレクトリ内の .js で終わるファイルを一覧表示し、ファイルの内容を置き換えます (sed -i はテキストを置き換えるコマンドです)
shell.cd('lib');
shell.ls('*.js').forEach(関数 (ファイル) {
  shell.sed('-i', 'BUILD_VERSION', 'v0.1.2', ファイル);
  shell.sed('-i', /^.*REMOVE_THIS_LINE.*$/, '', ファイル);
  shell.sed('-i', /.*REPLACE_LINE_WITH_MACRO.*\n/, shell.cat('macro.js'), ファイル);
});
シェル.cd('..');
 
# 特に指定がない限り、指定されたコマンドを同期的に実行します。 同期モードでは、ShellStringを返します。
# (ShellJS v0.6.x と互換性があり、{ code:..., stdout:..., stderr:... } という形式のオブジェクトを返します)。
# それ以外の場合は、サブプロセス オブジェクトが返され、コールバックは引数 (コード、stdout、stderr) を受け取ります。
if (shell.exec('git commit -am "自動コミット"').code !== 0) {
  shell.echo('エラー: Gitコミットに失敗しました');
  シェルを終了します(1);
}

上記のコードから判断すると、shelljs は nodejs で bash スクリプトを書くための非常に優れたソリューションです。node 環境を自由にアップグレードできない場合は、shelljs で十分だと思います。

続いては17.4kからスタートした本日の主人公、zxを見てみましょう。

zxライブラリ

公式サイト: www.npmjs.com/package/zx

まずは使い方を見てみましょう

#!/usr/bin/envzx です

$`cat package.json | grep name` を待ちます

ブランチ = $`git ブランチ --show-current` を待機します
$`dep deploy --branch=${branch}` を待機します

Promise.all([ を待つ
  $`スリープ1; エコー1`、
  $`スリープ2; エコー2`、
  $`スリープ3; エコー3`、
])

名前を 'foo bar' にします
$`mkdir /tmp/${name} を待つ

どう思いますか? Linux コマンドを記述するだけです。多くの bash 構文を無視して、js を直接使用できます。その利点はそれだけではありません。その機能のいくつかは非常に興味深いものです:

1. ts をサポートし、.ts を .mjs ファイルに自動的にコンパイルします。.mjs ファイルは、上位バージョンの Node.js で es6 モジュールをサポートするファイル拡張子です。つまり、このファイルは他のツールでエスケープすることなくモジュールを直接インポートできます。

2. パイプライン操作パイプ方式をサポート

3. ネットワーク リクエスト用の fetch ライブラリ、カラー フォントを印刷するための chalk ライブラリ、エラー処理用の nothrow メソッドが付属しています。bash コマンドが失敗した場合は、このメソッドでラップしてエラーを無視できます。

完全な中国語文書(私の翻訳スキルの低さをお許しください)

#!/usr/bin/envzx です

$`cat package.json | grep name` を待ちます

ブランチ = $`git ブランチ --show-current` を待機します
$`dep deploy --branch=${branch}` を待機します

Promise.all([ を待つ
  $`スリープ1; エコー1`、
  $`スリープ2; エコー2`、
  $`スリープ3; エコー3`、
])

名前を 'foo bar' にします
$`mkdir /tmp/${name} を待つ

Bash は素晴らしいですが、スクリプトを書くときは、通常、より便利なプログラミング言語が選択されます。 JavaScript は最適な選択ですが、標準の Node.js ライブラリを使用するには、いくつかの追加手順が必要です。 zx は child_process に基づいており、引数をエスケープし、適切なデフォルトを提供します。

インストール

npm i -g zx

必要な環境

Node.js >= 14.8.0

最上位レベルで await を使用できるようにするには、.mjs 拡張子を持つファイルにスクリプトを記述します。

zx スクリプトの先頭に次のシェバンを追加します。

#!/usr/bin/envzx です
これで、次のようにスクリプトを実行できるようになります。

chmod +x ./script.mjs
./script.mjs

または、zx 実行ファイル経由で:

zx ./script.mjs

すべての関数 ($、cd、fetch など) は、インポートなしで直接使用できます。

$`コマンド`

child_process パッケージの spawn 関数を使用して、指定された文字列を実行し、ProcessPromise を返します。

count = parseInt($`ls -1 | wc -l` を待機)
console.log(`ファイル数: ${count}`)

たとえば、ファイルを並列にアップロードするには、次のようにします。

実行されたプログラムがゼロ以外の終了コードを返す場合、ProcessOutput がスローされます。

試す {
  $`exit 1` を待つ
} キャッチ (p) {
  console.log(`終了コード: ${p.exitCode}`)
  console.log(`エラー: ${p.stderr}`)
}

ProcessPromise、以下はPromise TypeScriptのインターフェース定義です

クラス ProcessPromise<T> は Promise<T> を拡張します {
  読み取り専用 stdin: 書き込み可能
  読み取り専用標準出力: 読み取り可能
  読み取り専用stderr: 読み取り可能
  読み取り専用終了コード: Promise<数値>
  パイプ(dest): ProcessPromise<T>
}

pipe() メソッドを使用して標準出力をリダイレクトできます。

$`cat file.txt`.pipe(process.stdout) を待機します。

パイプラインの詳細については、github.com/google/zx/b… をご覧ください。

ProcessOutput の Typescript インターフェース定義

クラス ProcessOutput {
  読み取り専用 stdout: 文字列
  読み取り専用 stderr: 文字列
  読み取り専用終了コード: 数値
  toString(): 文字列
}

関数:

CD()

現在の作業ディレクトリを変更する

cd('/tmp')
$`pwd` を待機 // /tmp を出力

フェッチ()

node-fetch パッケージ。

resp = await fetch('http://wttr.in') を実行します。
(応答OK)の場合{
  console.log(応答テキスト()を待つ)
}

質問()

readline パッケージ

let bear = await question('どんなクマが一番いいですか? ')
let token = await question('環境変数を選択してください: ', {
  選択肢: Object.keys(process.env)
})

2番目のパラメータでは、タブの自動補完のオプションの配列を指定できます。

以下はインターフェース定義です

関数 question(query?: string, options?: QuestionOptions): Promise<string>
QuestionOptions をタイプします。選択肢: 文字列[] }

寝る()

setTimeout関数に基づく

スリープを待つ(1000)

スローしない()

終了コードが 0 でない場合に例外をスローしないように $ の動作を変更します。

ts インターフェース定義

関数 nothrow<P>(p: P): P

await nothrow($`grep something from-file`)
// パイプライン内部:

$`find ./examples -type f -print0` を待ちます
  .pipe(nothrow($`xargs -0 grep something`))
  .pipe($`wc -l`)

以下のパッケージはインポートする必要がなく、直接使用できます。

チョーク

console.log(chalk.blue('Hello world!'))

フェス

次のような使い方も可能

'fs' から { promises as fs } をインポートします
コンテンツ = fs.readFile('./package.json') を待機します。

オス

$`cd ${os.homedir()} && mkdir example` を待機します

構成:

$.シェル

使用する bash を指定します。

$.shell = '/usr/bin/bash'

$.引用

コマンド置換時に特殊文字をエスケープするために使用される関数を指定します

使用されるデフォルトのパッケージは shq です。

知らせ:

2 つの変数 __filename と __dirname は commonjs にあります。 .mjs で終わる es6 モジュールを使用します。

ESM モジュールでは、Node.js は __filename および __dirname グローバル変数を提供しません。 このようなグローバル変数はスクリプトでは非常に便利なので、zx は .mjs ファイルで使用するためにこれらを提供します (zx 実行ファイルを使用する場合)

Require は commonjs のモジュールインポートメソッドでもあります。

ESM モジュールには require() 関数が定義されていません。 zx は require() 関数を提供しているので、.mjs ファイルでのインポートで使用できます (zx 実行ファイルを使用する場合)

環境変数を渡す

process.env.FOO = 'バー'
$`echo $FOO` を待つ

配列を渡す

値の配列が引数として $ に渡された場合、配列の項目は個別にエスケープされ、スペースで連結されます。

例:

ファイルを[1,2,3]とします
$`tar cz ${files}` を待機します

$やその他の関数は明示的にインポートすることで使用できます

#!/usr/bin/env ノード
'zx' から {$} をインポートします
$`date` を待つ

zxは.tsスクリプトを.mjsにコンパイルして実行することができます。

タイプスクリプトの例

要約する

これで、Node.js で bash スクリプトを作成する方法についての記事は終了です。Node.js で bash スクリプトを作成する方法についての詳細は、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • 堅牢な Bash Shell スクリプトを書くためのヒントのまとめ
  • 堅牢な Bash スクリプトの書き方 (経験の共有)
  • 安全で完全に機能する Bash スクリプトの書き方

<<:  Windows SSHサーバーを簡単に構築するためのいくつかの手順

>>:  MySQL トリガー: 複数のトリガー操作の作成例の分析

推薦する

Vue が DingTalk の出勤カレンダーを実装

この記事では、DingTalkの勤怠カレンダーを実装するためのVueの具体的なコードを参考までに共有...

vue構成ファイルはルーティングとメニューインスタンスコードを自動的に生成します

目次前面に書かれたルータ.jsonルート生成メニュー生成効果要約する前面に書かれたルートを繰り返し記...

デザイン: 意志の強いデザイナー

<br />長年の専門的なアートデザイン教育を通じて「美とは何か」を学びましたが、「美を...

MySQLの認可コマンド grant の使い方のまとめ

MySQL 認証コマンド grant の使用方法:この記事の例は MySQL 5.0 以降で実行され...

互換性を維持しながら他のウェブページのデータを適用する iframe の使い方

以下は、Shiji Tiancheng が Tencent KartRider ページを呼び出すため...

JavaScript ステートメントの一般的な for ループの詳細な説明

JavaScript には、for、for in、for of、forEach ループなど、多くのル...

フレックスレイアウトではサブアイテムの高さを維持できる

Flex レイアウトを使用すると、水平に配置すると、すべての子項目の高さが同じになることがわかります...

nginx のスムーズな再起動を実装する方法

1. 背景サーバーの開発プロセスでは、新しいコードや構成をロードするためにサービスを再起動することが...

HTML チュートリアル: よく使われる HTML タグのコレクション (6)

導入された HTML タグは、必ずしも XHTML 仕様に完全に準拠しているわけではありません。実際...

ダッシュボードを実装するためのjQueryプラグイン

jQueryプラグインは、参考のためにダッシュボードを実装します。具体的な内容は次のとおりです。一般...

VUEプロジェクトでXSS攻撃に遭遇した実体験

目次序文原因を発見するカスタムフィルタリングルール要約する序文インターネットの急速な発展に伴い、情報...

VMware マルチノード環境を構成する方法

このチュートリアルでは CentOS 7 64 ビットを使用します。各仮想マシンに 2GB のメモリ...

Vue で Graphql インターフェースを実装する例

注意:この記事は現在取り組んでいる nestjs+graphql+serverless 合宿における...

mysqlは、現在の時刻が開始時刻と終了時刻の間にあるかどうかを判断し、開始時刻と終了時刻が空であることが許可されます。

目次要件: 進行中のアクティビティ データを照会する次のSQLクエリは、上記の4つの要件を満たし、タ...

Navicatは機能ソリューション共有を作成できません

初めて MySQL FUNCTION を書いたとき、エラーが何度も発生しました。 Err] 1064...