background,
github address: https://github.com/lulu-up/record-shell
Have you ever forgotten how to spell a shell
command? Or too lazy to type a long list of commands? For example, mine mac
notebook tachbar
occasionally It will be 'stuck', then I will enter killall ControlStrip
command to restart tachbar
, you can see this command and really don't bother to type.
There is also a new react
project I have to enter every time npx create-react-app 项目名 --template typescript
, in the company's daily development, I am used to writing new requirements every time I write a separate clone
project and create it To develop a new branch, you need to go to gitlab
to copy the project address and then locally git clone xxxxxxxxxx 新的项目名
, in theory, these operations are really repeated.
First of all, this time I will take you to use node
to make a record together shell
a small plug-in for the command. Of course, there are similar plug-ins on the Internet, but this time I made the most simple and rude one. The version of , I use the cool version myself, and I also want to take the opportunity to review the command line related knowledge.
1. Demonstration of usage
Let's see if this 'library' is really convenient:
1: Install
npm install record-shell -g
After installation, your global will have more rs
command:
2: add
rs add
The name is arbitrary, and it is even more comfortable to use Chinese. Here is a demonstration of entering simple commands:
3: view + use'
rs ls
Commands are optional, here I will add a few more commands to demonstrate:
You can press the up and down keys to move the selection, and press Enter to execute the command:
Of course, you can also view the command details, just -a
parameters:
rs ls -a
4: remove
rs rm
5: add command with variables
Of course, our commands are not always written in 'dead' mode. For example, the command echo 内容 > a.txt
means that I want to write --- 内容
to 目标文件
:
6: Use variables
When using the command, it will guide us to fill in the variables, so just write Chinese when defining:
2. Initialize your own node project
Next, we will make this library from scratch. Considering that some novice students may not have done this kind of global node
package, I will talk about it in detail here.
There is nothing to say about the initialization project, just name it:
npm init
Retrofit package.json
file:
"bin": {
"rs": "./bin/www"
},
It is specified here in bin
that when running the rs
command, access "./bin/www"
.
#! /usr/bin/env node
require('../src/index.js')
-
#!
This symbol usually appears at the beginning of the first line of basic in Unix systems to indicate the interpreter for this script file. -
/usr/bin/env
Because you may installnode
to a different directory, here directly tell the system to search in the PATH directory, so that it is compatible with differentnode
installation path. -
node
Needless to say, this is to find ournode
command.
3. Initialization command + global installation
Here's how to hang our commands globally, so that you can use the global rs
command anywhere:
// cd 我们的项目
npm install . -g
It is easier to understand here, it is equivalent to directly installing the project in the global, we usually install xxx -g
is to pull the remote, this command is to pull the current directory.
At this point, you write console.log('全局执行')
to the index.js
file, and then execute it globally rs
and see the following effect is successful:
Fourth, commander.js (node command line solution)
Install first and then talk:
npm install commander
commander
can help us process user commands in a very standardized way. For example, when the user enters rs ls -a
on the command line, I can input the original node
first. args
, 拆解出ls
与-a
, 然后再写一堆if
ls
it is followed by -a
how to do it, but obviously this is not standardized and the code is difficult to maintain, commander
is here to help us standardize these writing methods:
Put the following code in the index.js
file:
const fs = require("fs");
const path = require("path");
const program = require('commander');
const packagePath = path.join(__dirname, "../package.json")
const packageData = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
program.version(packageData.version)
program
.command('ls [-type]')
.description('description')
.action((value) => {
console.log('你输入的是:', value)
})
program.parse(process.argv)
Enter at the command line:
rs ls 123456
Explain the code sentence by sentence:
-
const program = require('commander')
This clearly introducescommander
. -
program.version(packageData.version)
Here is the version that defines the current库
, when you enterrs -V
, it will display theprogram.version
method to get the value of fabc1fe875a19 Theversion
field in thepackage.json
is used directly. -
program.command('ls')
defines a parameter namedls
, when we enterrs ls
will trigger our subsequent processing method, the reason why I wrote it as ---5c7b9db406087e1724423a36f796ab8f---, I wroteprogram.command('ls [-type]')
--Because addingprogram.command('ls [-type]')
[-type]
aftercommander
will thinkls
command can be followed by other parameters, of course you can call[xxxxx]
, so that users can understand it. -
.description('description')
As the name suggests, here is the brief description, when we enterrs -h
it will appear: -
.action
method iscommander
The processing function when the current command is detected, the first parameter is the parameter passed in by the user, and the second parameter is theCommand
object , we will pop up the selection list here later. -
process.argv
You must first know hereprocess
is thenode
in the global variables of the command line, of whichargv
are all the bbb05c72e58 command line parameters when starting. -
program.parse(process.argv)
It's easy to understand after reading the above, pass the command line parameters tocommander
to start execution.
sideshow
If you configure program.option('ls', 'ls的介绍')
, it will appear when the user enters rs -h
, but I feel that it is a little messy, our plugin is simple, so we didn't add it.
Five, inquirer.js (node command line interactive plug-in)
npm install inquirer
inquirer
can help us generate various command line question and answer functions, just like vue-cli
the same effect, you can enter the following code to try the 'single selection mode':
program
.command('ls [-type]')
.description('description')
.action(async (value) => {
const answer = await inquirer.prompt([{
name: "key",
type: "rawlist",
message: "message1",
choices: [
{
name: 'name1',
value: 'value1'
},
{
name: 'name2',
value: 'value2'
}
]
}])
console.log(answer)
})
Explain the code sentence by sentence:
- First here is a pattern of
async
andawite
. -
inquirer.prompt
parameter is a数组
, because it can operate continuously, such as performing two single-selection list operations. -
name
就是最终的key
,name
xxxx
1
, 则最终返回结果就是{xxxx:1}
. -
type
Specify the interaction typerawlist
single selection list,input
input,checkbox
multiple selection. -
message
is the prompt, before we let the user choose, we must tell him what we are doing here. -
choices
array of options,name
option name,value
option value.
6. Add command: add
Officially started to do the first command, I created a new folder named env
, and created a record-list.json
file inside the file with the command to store the user:
add
command is nothing more than adding content to the record-list.json
file:
program
.command('add')
.description('添加命令')
.action(async () => {
const answer = await inquirer.prompt([{
name: "name",
type: "input",
message: "命令名称:",
validate: ((name) => {
if (name !== '') return true
})
}, {
name: "command",
type: "input",
message: "命令语句, 可采用[var]的形式传入变量:",
validate: ((command) => {
if (command !== '') return true
})
}])
let shellList = getShellList();
shellList = shellList.filter((item) => item.name !== answer.name);
shellList.push({
"name": answer.name,
"command": answer.command
})
fs.writeFileSync(dataPath, JSON.stringify(shellList));
})
Explain the code sentence by sentence:
- First we use
commander
to define theadd
command; - When the
add
command is triggered, we useinquirer
to define two input boxes, the first input command name, the second input command statement. -
validate
defines the verification of the input parameters, note: the user does not input the value is notundefined
but空字符串
, so use!== ''
, If the verification fails, the operation cannot be continued. - After filling in, the user will add data to
record-list.json
, and replace if it is a command with the same name.
The name may be repeated, but it doesn't matter, because its usage scenario determines that it does not need to be too restrictive.
Seven, remove command: rm
The principle here is to pull record-list.json
data to delete, and then update record-list.json
:
program
.command('rm')
.description('移除命令')
.action(async () => {
let shellList = getShellList();
const choices = shellList.map((item) => ({
key: item.name,
name: item.name,
value: item.name,
}));
const answer = await inquirer.prompt([{
name: "names",
type: "checkbox",
message: `请'选择'要删除的记录`,
choices,
validate: ((_choices) => {
if (_choices.length) return true
})
}])
shellList = shellList.filter((item) => {
return !answer.names.includes(item.name)
})
fs.writeFileSync(dataPath, JSON.stringify(shellList));
})
Explain the code sentence by sentence:
-
choices
is to define a set of optional options. - Use
checkbox
multiple selection mode, allowing users to delete multiple commands at once. -
validate
Check that nothing is deleted, because the user may forget to click the selection (space bar). - Use
filter
to filter out commands with the same name. - Last update
record-list.json
file.
Eight, view + use: ls
There is a little more content here. After all, one command is responsible for two capabilities. The core principle here is to pull record-list.json
the content of the file and display it as 单选列表
, and then execute the command according to the value selected by the user. Execute, and finally return the execution result;
1: View ls, support parameter-a
program
.command('ls')
.alias('l')
.description('命令列表')
.option('-a detailed')
.action(async (_, options) => {
const shellList = getShellList();
const choices = shellList.map(item => ({
key: item.name,
name: `${item.name}${options.detailed ? ': ' + item.command : ''}`,
value: item.command
}));
if (choices.length === 0) {
console.log(`
您当前没有录入命令, 可使用'rs add' 进行添加
`)
return
}
const answer = await inquirer.prompt([{
name: "key",
type: "rawlist",
message: "选择要执行的命令",
choices
}])
})
Explain the code sentence by sentence:
-
option('-a detailed')
定义了可以-a
参数,ls -a
, 并且如果用户传-a
返回值{detailed: true}
. - If there is
-a
then the command itself will be displayed in thename
attribute. -
choices
is convertedrecord-list.json
list of data in the file. - If the
record-list.json
data is empty, the user is prompted to users add
to add it. - Use
inquirer
to generate a radio list.
2: Determine if there is a variable in the command statement
Since the command input by the user is allowed to contain variables, such as the one demonstrated earlier echo [内容] > [文件名]
, then I need to determine whether there is a variable in the command selected by the current user:
const optionsReg = /\[.*?\]/g;
function getShellOptions(command) {
const arr = command.match(optionsReg) || [];
if (arr.length) {
return arr.map((message) => ({
name: message,
type: "input",
message,
}));
} else {
return []
}
}
Explain the code sentence by sentence:
-
optionsReg
matches all variables of '[this way of writing]'. - If a variable is matched, an array is returned, and the length of this array is the number of variables, because each variable must have an opportunity to enter.
- There is no special treatment for the duplicate
name
, andname
will become the return valuekey
, so it cannot be repeated, otherwise it will cause only Process the first variable.
3: no variable -> execute
Here is a new concept:
const child_process = require('child_process');
child_process
node
, child_process.exec
The method is to start a system shell to parse the parameters, so it can be very complex commands, including pipelines and redirection.
child_process.exec(command, function (error, stdout) {
console.log(`${stdout}`)
if (error !== null) {
console.log('error: ' + error);
}
});
Explain the code sentence by sentence:
-
command
is the command to execute. -
stdout
The output of the execution command, such asls
is to output the file information in the current directory. -
error
This is also very important. If an error is reported, the user should know the error message, so it is alsoconsole
.
4: There are variables -> execute
The core principle is to replace the command statement after parsing the 'variable', and then execute it normally:
function answerOptions2Command(command, answerMap) {
for (let key in answerMap) {
command = command.replace(`[${key}]`, answerMap[key])
}
return command;
}
function handleExec(command) {
child_process.exec(command, function (error, stdout) {
console.log(`${stdout}`)
if (error !== null) {
console.log('error: ' + error);
}
});
}
if (shellOptions.length) {
const answerMap = await inquirer.prompt(shellOptions)
const command = answerOptions2Command(answer.key, answerMap)
handleExec(command)
} else {
handleExec(answer.key)
}
Explain the code sentence by sentence:
-
inquirer
execution, it will return a dictionary, such as{[文本]:"xxxxx", [文件名]:"a.txt"}
, because we setname
andmessage
use the same name. -
answerOptions2Command
loop executionreplace
replace variables. -
handleExec
is responsible for executing the statement.
Nine, let the text change color (chalk)
The function is complete, but our prompt text is still 'black and white', of course we want to be more colorful in the command line, use in node
:
var red = "\033[31m red \033[0m";
console.log('你好红色:', red)
\033
is c语言
in 转义字符
will not be expanded here, anyway, it is to operate the screen when we see him, but we can see the above writing Very unfriendly, we must package it, chalk.js
is a good existing wheel, we will install it:
npm install chalk
use:
const chalk = require('chalk')
chalk.red('你好: 红色')
You were happy too early, now there is something wrong!!
Other tutorials don't say how to solve it, in fact, you just need to chalk
the version of ---657514b4d0693be6140348a5911ac23e--- to 4
and it will be ok!
end
That's it this time, hope to progress with you.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。