热搜:前端 nest neovim nvim

简单认识 Shell | 青训营笔记

lxf2023-05-13 01:04:43

Shell 简介

Shell 基本概念

  • 终端: 获取用户输入、展示运算结果的硬件设备。
  • tty: teletypeWriter, 早期指的是电传打印机, 如今的 tty 变成一个虚拟的概念, 在 Linux 中是一个程序, 表示 输入/输出 环境。
  • 终端模拟器: 每一个终端模拟器会关联一个虚拟 tty, 通过 tty 和系统内核交互。Mac Terminal、iTerm2(也是 mac 上的终端)、GNOME Terminal(Linux 上的) 和 Window Terminal (可能需要下载) 是常见的终端模拟器。
  • Shell: command interpreter, 命令解释程序, 用来处理来自终端模拟器的输入、解释执行之后输出结果给终端。
  • Bash: Shell 的一种具体实现。此外还有 Zsh、Fish 和 Powershell 等。

Shell 发展

  1. 1971 年, Ken Thompson 在贝尔实验室为 UNIX 开发了第一个 shell, 称为 V6 shell。
  2. 1979 年, Stephen Bourne 在贝尔实验室在 V7 UNIX 发布了 Bourne shell , 即 sh。
  3. 1989 年, 开源组织 GNU 为了取代 Bourne shell 发布了第一版 Bourne-Again shell,即 Bash

bash 是 sh 的超集, 可执行大部分 sh 脚本, 并集成了 Korn shell 和 C shell 的功能。

Shell 构成

shell 提供了与内核和设备交互的方法(接口), 还集成软件开发中通用的设计模式(如管道和过滤器), 具备控制流程、循环、变量和命令查找的机制。

shell 是一门命令解释器, 同时也是一门编程语言。

简单认识 Shell | 青训营笔记

Shell 基础语法

变量

类型作用域声明方式
自定义变量当前 shell=
环境变量当前 shell 以及子 shellexportdeclare -x
系统环境变量所有 shell启动自动加载

declare 声明变量类型

格式: declare 选项 变量

选项含义
-给变量设定类型属性
+取消变量的类型属性
-a将变量声明为数组类型
-i将变量声明为整数型
-x将变量声明为环境变量
-r将变量声明为只读变量
-p输出变量及其被声明的类型

示例代码

a.sh 内容:

#!/bin/bash
my_var=123
declare -p my_var

在 bash 终端下运行

$ bash a.sh
declare -x my_var="123

父子 shell

一个 shell 中可以调用另外一个 shell 脚本, 比如:

a.sh 文件内容:

#!/bin/bash
export my_var=123
bash b.sh

b.sh 文件内容:

echo ${my_var}

在 bash 终端下运行

$ bash a.sh
123

简单认识 Shell | 青训营笔记

自定义变量

#!/bin/bash

# 变量名=变量值 等号左右不能有空格!
page_size=1
page_num=2
# 上面的变量值默认是字符串, 不能进行加减操作
# total=page_size*page_num ❌
# 声明变量为整型
let total=page_size*page_num # 方法1, 使用 let
declare -i total=page_size*page_num # 方法二, 使用 -i 参数

# 变量值是命令
_ls=ls

# 变量值是命令执行结果
file_list=${ls -a}

环境变量

page_size=1
page_num=2
let total=page_size*page_num

# 使用 export 将变量变成环境变量
export total

# 也使用 -x 参数声明为环境变量
declare -x total

系统环境变量

变量名含义常见操作
$0当前 shell 名称/脚本名声$1, $2 等可以获取到传入参数
$#传入脚本的参数数量if[ $# -gt 1 ]
$*传入脚本的所有参数
$?上条命令执行的状态码if[ $? -eq 0 ]
$PS1命令提示符export PS1="\u@\h \w> "
$USER用户名
$HOME用户主文件夹cd ~
$PATH全局命令的搜索路径PATH=$PATH:[新增路径]

PS1="\u@\h \w> " 含义: \u 是用户名; \h 是主机名; \w 是当前目录。

linux 中 $PATH 是使用 分号 : 作为分隔符。

配置文件加载顺序和优先级

系统环境变量是定义在配置文件中的, 配置文件有多个, 不同情况加载的配置文件是不一样的。

shell 可以分为 login shell 和 non-login shell。

login shell 是指用户登录系统时开启的 shell。在 login shell 中, 系统会执行 /etc/profile 文件来初始化环境变量和路径等系统范围的设置, 并且还会读取当前用户的 ~/.bash_profile~/.profile 文件来设置个人化的环境变量和命令别名等。

non-login shell 是指在登录后通过运行一个新的 shell 脚本而打开的 shell。这些 shell 不会执行系统范围的设置, 而是直接读取当前用户的 ~/.bashrc 文件来加载个人化的环境变量、命令别名和函数等。

如果修改了配置文件, 不会立即生效, 需要我们重启终端或者执行 source 配置文件 命令

简单认识 Shell | 青训营笔记

bashrc 只作用在 bash 中。 ~/.bashrc 是用户级别的, /etc/bashrc 是系统级别的。默认情况下用户级的配置文件优先级是高于系统级别的。为什么叫做默认情况下呢? 因为你可以通过 source 配置文件 命令修改这个优先级。具体看示例:

简单认识 Shell | 青训营笔记

linhieng@LINHIENG:~$ vim ~/.bashrc # 在内容末尾添加 export PS1="\u \w > " 环境变量
linhieng@LINHIENG:~$ source ~/.bashrc # 执行配置文件, 让其生效
# 此时可以看到效果:
linhieng ~ > sudo vim /etc/profile # 同样, 在内容末尾添加 export PS1="> " 系统环境变量
[sudo] password for linhieng:
linhieng ~ > source /etc/profile # 让其生效
# 此时可以看到效果, 这说明优先级最高的是最近执行的 source 配置文件, 其次才是默认情况下的优先级
> bash # 在当前 shell 下新建一个子 bash, 它是属于 non-login shell, 所以它会读取 ~/.bashrc, 可以理解为它执行了 source ~/.bashrc
# 故此时优先级又回到了 "默认情况", 即用户级别优先级高于系统级别优先级
linhieng ~ > exit
exit
# 退出时, 并不会再次执行用户级配置
> source ~/.bashrc
linhieng ~ > source /etc/profile # 可以通过 source 随意切换优先级
>

运算符和引用

类型符号
算术运算符+ - * / % ` & `
逻辑运算符` && !`
比较运算符== != < >
引号"" '' ``
圆括号(()) ()
命令连接` && ;`
后台运行&

引号和圆括号使用示例

#!/bin/bash

a=0

# 双引号, 部分引用
foo="${a}123"
echo $foo # 0123

# 单引号, 完全引用, 原样输出
foo='${a}123'
echo $foo # ${a}123

# 反引号, 内容会被认为是可执行的命令
foo=`echo 'hello, world!'`
echo $foo # hello, world!

# 双括号, 算术运算
foo=$((1+2))
echo $foo # 3

# 单括号, 效果同反引号
foo=$(echo 'hi~')
echo $foo # hi~

执行效果如下:

$ bash a.sh
0123
${a}123
hello, world!
3
hi~

对于命令连接, 特别注意一下, 返回码为 0 代表没有错误(和 C 语言一样)。

  • cmd1 || cmd2: 若 cmd1 执行完并且返回码非 0, 则继续执行 cmd2
  • cmd1 && cmd2: 若 cmd1 执行完并且返回码 0, 则继续执行 cmd2
  • cmd1 ; cmd2: 先执行 cmd1 再执行 cmd2。 两者不会相互影响

后台运行的 & 一般搭配 nohup 命令一起使用。只使用 &, 则当当前 shell 关闭后, 程序依旧会关闭。但若搭配 nohup 则关闭当前 shell 后, 程序也可继续执行。比如 nohup node index.js &

小 Tip: 所谓关闭 shell, 指的是正常关闭, 而不是异常关闭。比如通过 ssh 连接到云服务器后, 若直接关闭当前 shell 窗口, 这种情况属于异常关闭, nohup 是会失效的。正确关闭的方法是执行 exit 命令

管道

管道与管道符 |, 作用是将前一个命令的结果传递给后面的命令。

语法: cmd1 | cmd2

要求: 管道右侧的命令必须能接受标准输入才行, 比如 grep, wc 等命令可以直接使用管道符, 而 ls 等则不能直接使用, 需要借助 xargs 进行预处理

注意: 管道命令仅仅处理 stdout, 对于 stderr 会予以忽略。不过, 可以使用 set -o pipefail 设置 shell 遇到管道错误退出

下面给出 xargs 的简单示例:

$ cd /usr
$ find . -maxdepth 2 -name "*.sh" # 文件数量太多, 使用 maxdepth 来限制搜索目录深度
./bin/gettext.sh
./bin/rescan-scsi-bus.sh
$ find . -maxdepth 2 -name "*.sh" | ls -l # ls 无法接受默认输入, 所以下面输出的结果和 find 的结果无关
total 0
drwxr-xr-x 1 root root 4096 Sep 12  2022 bin
drwxr-xr-x 1 root root 4096 Apr 18  2022 games
drwxr-xr-x 1 root root 4096 Aug  8  2022 include
drwxr-xr-x 1 root root 4096 Sep 12  2022 lib
drwxr-xr-x 1 root root 4096 Aug  8  2022 lib32
drwxr-xr-x 1 root root 4096 Aug  8  2022 lib64
drwxr-xr-x 1 root root 4096 Aug  8  2022 libexec
drwxr-xr-x 1 root root 4096 Aug  8  2022 libx32
drwxr-xr-x 1 root root 4096 Aug  8  2022 local
drwxr-xr-x 1 root root 4096 Sep 12  2022 sbin
drwxr-xr-x 1 root root 4096 Aug  8  2022 share
drwxr-xr-x 1 root root 4096 Aug  8  2022 src
$ find . -maxdepth 2 -name "*.sh" | xargs ls -l # 使用 xargs 对内容进行预处理, 此时 ls 的输入参数就是 find 的输出了
-rwxr-xr-x 1 root root  5188 Mar 25  2022 ./bin/gettext.sh
-rwxr-xr-x 1 root root 38762 Mar 25  2022 ./bin/rescan-scsi-bus.sh

简单认识 Shell | 青训营笔记

重定向

简单认识 Shell | 青训营笔记

重定向输入符号:

  • >: 覆盖写入文件
  • >>: 追加写入文件
  • 2>: 错误输出写入文件, 2 表示标准错误, > 相当于省略了前面的 1(标准输出)
  • &>: 正确和错误输出统一写入到文件中

示例:

# 测试 >
$ echo hello > a.txt
$ cat a.txt
hello
$ echo hi > a.txt
$ cat a.txt
hi
# 测试 >>
$ echo hello >> a.txt
$ cat a.txt
hi
hello
# 测试 2>>
linhieng@LINHIENG:~$ abcde >> a.txt
Command 'abcde' not found, but can be installed with:
sudo apt install abcde
linhieng@LINHIENG:~$ abcde 2>> a.txt
linhieng@LINHIENG:~$ cat a.txt
hi
hello
Command 'abcde' not found, but can be installed with:
sudo apt install abcde
# 测试 &>
$ abcde &> a.txt
$ cat a.txt
Command 'abcde' not found, but can be installed with:
sudo apt install abcde

输入重定向符号:

  • <: 重定向输入, 后面可以接一个文件
  • <<: 用于在命令行界面中直接输入多行文本作为命令的输入, << 后面需要接一个终止字符串

示例:

# 测试 <
# wc 输出结果是 " 行数 单词数 字节数 "
$ wc < a.txt
 2 13 77
# 测试 <<
$ wc << end
> hello
> how are you
> end.
> end
      3       5      23

判断命令

shell 中提供了 test, [[[ 作为判断符号, 他们均可用于判断整数, 字符串和文件。test[ 功能单一, 只能使用自己支持的标志位, <, >= 只能用来比较字符串, 不能比较整数。而 [[ 功能更丰富, 整型可以使用 <, >= 来比较, 并且字符串中支持 =~ 正则比较。具体语法如下:

  • test condition
  • [ condition ] 注意中括号内的空格是有意义的, 不能省略
  • [[ condition ]] 同上, 注意空格;

test 示例:

a.sh 文件内容:

#!/bin/bash

n1=1
n2=1
n3=3

# 整数测试
test $n1 -eq $n2 && echo '-eq 表示是否相等(equal)'
test $n1 -lt $n3 && echo '-lt 表示小于(less than)'
test $n3 -gt $n2 && echo '-gt 表示大于(greater than)'

# 字符串测试, 为避免字符串有空格其他分隔符, 应使用双引号包裹字符串
test -z "$str_a" && echo 'zero length. 若字符串为空则放回真(0), 否则返回假(非0)'
str_b=' '
test -n "$str_b" && echo 'non-zero length. 和 -z 相反'
str_a=' '
test "$str_a" = "$str_b" && echo '注意, 单独一个 = 是正确的'

# 文件测试
test -e a.sh && echo 'exits. 判断文件/目录是否存在'
test -f a.sh && echo 'file. 判断是否是普通文件'

在 bash 终端下运行

$ bash a.sh
-eq 表示是否相等(equal)
-lt 表示小于(less than)
-gt 表示大于(greater than)
zero length. 若字符串为空则放回真(0), 否则返回假(非0)
non-zero length. 和 -z 相反
注意, 单独一个 = 是正确的
exits. 判断文件/目录是否存在
file. 判断是否是普通文件

[test 差不多, 就不给出示例了。

[[ 示例:

#!/bin/bash

n1=1
n2=2

# 整数测试
[[ $n1 = $n2 ]] ; echo $? # = 或者 == 都是可以的
[[ $n1 < $n2 ]] ; echo $?
[[ $n1 > $n2 ]] ; echo $?

str1="hello world 123"
str2='hello world'
pattern="[0-9]+"
[[ "$str1" =~ $pattern ]] ; echo $? # 正则是字符串, 则需要使用变量形式
[[ "$str2" =~ [0-9]+ ]] ; echo $? # 不使用变量, 可直接写正则

分支语句

if else 语法1

if [ condition1 ] ; then
    # execute commands if condition1 is true
elif [ condition2 ] ; then
    # execute commands if condition2 is true
else
    # execute commands if all above conditions are false
fi

案例:

#!/bin/bash

a=$1
b=$2
if [[ $a > $b ]]
then
    echo "$a > $b"
elif [[ $a < $b ]]
then
    echo "$a < $b"
else
    echo "$a == $b"
fi

运行:

$ bash a.sh 1 2
1 < 2
$ bash a.sh 1 1
1 == 1
$ bash a.sh 2 1
2 > 1

case 语法:

case $variable in:
  "pattern1")
    # execute commands for pattern1
  ;;
  "pattern2" | "pattern3")
    # execute commands for pattern2 or pattern3
  ;;
  *)
    # execute commands if no pattern matches
  ;;
esac

案例:

#!/bin/bash

read -p "Input a number between 1 or 2: " num

case $num in
    1)
        echo "You entered one"
    ;;
    2)
        echo "You entered two"
    ;;
    *)
        echo "Invalid input"
    ;;
esac

运行:

$ bash a.sh
Input a number between 1 or 2: 1
You entered one
$ bash a.sh
Input a number between 1 or 2: 2
You entered two
$ bash a.sh
Input a number between 1 or 2: a
Invalid input

循环语句

while 循环

语法:

while [ condition ]
do
  #  command
done

示例:

#!/bin/bash

count=1

while [[ $count < 5 ]]
do
    echo "Count is $count"
    count=$((count + 1))
done

运行:

$ bash a.sh
Count is 1
Count is 2
Count is 3
Count is 4

until 循环

语法:

until [ condition ]
do
  # command
done

示例:

#!/bin/bash

count=1

until [[ $count > 5 ]]
do
    echo "Count is $count"
    count=$((count + 1))
done

运行:

$ bash a.sh
Count is 1
Count is 2
Count is 3
Count is 4
Count is 5

for 循环

语法:

for variable in list
do
   # command
done

示例:

#!/bin/bash

for i in 1 2 3 4 5
do
    echo "Number: $i"
done

运行:

$ bash a.sh
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5

函数

语法1:

function function_name() {
  parameter1=$1
  parameter2=$2
  # ...
  # commands
}
# 调用
function_name parameter1 parameter2 ...

语法2:

function_name() {
  parameter1=$1
  parameter2=$2
  # ...
  # commands
}
# 调用
function_name parameter1 parameter2 ...

示例1:

#!/bin/bash

printName() {
    if [[ $# < 2 ]] ; then
        echo 'illegal parameter.'
        exit 1 # 这不是返回值, 而是直接结束脚本的运行
    fi

    echo "firstName: $1"
    echo "lastName: $2"


}

printName jacky chen
printName $1 $2

运行:

$ bash a.sh JK lin
firstName: jacky
lastName: chen
firstName: JK
lastName: lin
$ bash a.sh JK
firstName: jacky
lastName: chen
illegal parameter.

示例2:

#!/bin/bash

f() {
  echo 666
  return $1
}

f 255
echo "执行状态: $?" # 255
f 256
echo "执行状态: $?" # 256 溢出, 结果为 0
r=$(f 256)
echo "执行结果: $r"
f
echo "执行状态: $?"

运行:

$ bash a.sh
666
执行状态: 255
666
执行状态: 0
执行结果: 666

示例3:

#!/bin/bash

f() {
  test -n "$1"
}

f $1
echo "执行状态: $?"

运行:

$ bash a.sh
执行状态: 1
$ bash a.sh  1
执行状态: 0

示例4:

#!/bin/bash

f1() {
  a=66
}
f1
echo "a: $a" # 66, 被函数内的变量污染了

f2() {
    local b=77
}
f2
echo "b: $b" # 无输出, 没有被污染

f3() {
    c=77
    unset c
}
f3
echo "c: $c" # 无输出, 没有被污染

运行:

$ bash a.sh
a: 66
b:
c:

注意:

  • shell 自上而下执行, 函数必须在使用前定义
  • 函数获取形参是使用 $1 $2 ... 这种方法, 并且不需要再括号内声明
  • 函数内 return 仅仅表示函数执行状态, 不代表函数执行结果。返回值是一个 8 位的状态码, 它的最大值是 255。
  • 想要获取函数返回的结果, 可通过 echo, printf, 不要通过 return 的方式
  • 如果没有 return, 函数返回的状态函数内最后一条语句的执行状态
  • 为了函数内定义的变量不污染全局, 最好在函数内使用 local 去定义, 或者在函数退出之前使用 unset 去处理一下

模块化

模块化的原理是在当前 shell 内执行函数文件, 具体是使用 source 函数库路径 来实现模块化。

案例: math.sh文件:

#!/bin/bash

# add function
# @return platForm
add() {
  declare -i res=$1+$2

  echo $res
}

a.sh文件:

#!/bin/bash
source './math.sh'

total=`add $1 $2`
echo $total

运行:

$ bash a.sh 66 666
732

shell 常用命令

更详细的使用, 可执行 命令 --help 进行查看。

命令说明
grepglobal regular expression print, 文本搜索工具
sort文本排序。 指定分隔符后以第三列进行排序: sort -t ' ' -k 3
wc统计出现的行数、单词数、字符数
head查看开头若干行, 默认 10 行
tail查看末尾若干行。在实时跟踪日志时特别有用: tail -f -n 10 xx.log
cut对数据行的内容进行处理。以空格为分隔查看第三个 `cat acut -d ' ' -f 3`
find文件和目录查找
xargs参数处理
which查找命令路径

Shell 执行过程和原理

执行

shell 脚本一般以 .sh 结尾, 也可以没有, 这是一个约定; 第一行需要指定用什么命令解释器来执行。

脚本以 #! 开头时, 操作系统(内核)会自动查找该行后面指定的解释器, 并使用它来运行该脚本。

#! /bin/bash
#! /usr/bin/env bash

脚本可以有三种启动方式:

# 文件名运行, 注意前缀 ./ 不能省略, 不然会识别为命令, 而不是文件
./xx.sh

# 解释器运行, 这里可以省略 ./
bash xx.sh

# source 运行
source xx.sh

执行过程

  1. 字符解析
  • 识别换行符、 分号(;) 做行的分割
  • 识别命令连接符( || && |) 做命令的分割
  • 识别空格、tab符, 做命令和参数的分割
  1. shell 展开, 例如 {1..3} 解析为 1 2 3

  2. 重定向, 将 stdin, stdout, stderr 的文件描述符进行指向变更

  3. 执行命令

  • builtin 直接执行
  • builtin 使用 $PATH 查找, 然后启动子进程执行

builtin 指的是内置命令

  1. 收集状态并返回

shell 的架构图类似于一个流水线, 在里面进行输入分析和解析。bash 会以一特殊字符作为分隔符, 将文本进行分段解折。在 bash 脚本中是以回车或者分号作为一行命今结束的标志, 这就是第一层级的解折, 将大段的命今行进行分段符号拓展(使用各种方法, 比如大括号 {}、波浪符 ~、变量和参数的展开/替换、文件名展开), 并最终执行命今(通过 shell 内置命今或外部命今)。

简单认识 Shell | 青训营笔记

Shell 展开

大括号展开 Brace Expansion

一般由三部分构成: 前缀, 一对大括号和后缀。大括号内可以是逗号分割的字符串序列, 也可以是序列表达式 {x..y[..increase]}

# 字符串序列
a{b,c,d}e => abe ace ade

# 表达式序列, 数字可以使用 increase 调整增量, 但字母不行
{1..5} => 1 2 3 4 5
{1..5..2} => 1 3 5
{a..e} => a b c d e

波浪号展开 Tilde Expansion

# 当前用户主目录
~ => $HOME

# 指定用户主目录
~lin/foo => 用户 lin 的 $HOME/foo

# 当前工作目录
~+/foo => $PWD/foo

# 上一个工作目录
~-/foo => ${$OLDPWD-'~-'}/foo

参数展开 Shell Parameter Expansion

  • 间接参数扩展 ${!parameter}, 类似于指针的指针

    #!/bin/bash
    
    var='hello'
    parameter='var'
    echo ${parameter} # var
    echo ${!parameter} # hello
    
  • 参数长度 ${#parameter}

    #!/bin/bash
    
    var='hello'
    echo ${#var} # 5
    
  • 空参数处理

    • ${a:-word}: 若 a 为空, 则结果为 word
    • ${b:=word}: 若 b 为空, 则结果为 word, 并且 b='word'
    • ${c:?word}: 若 c 为空, 则报错并退出
    • ${d:+word}: 若 d 不为空, 则结果为 word

    案例:

    #!/bin/bash
    
    b=${a:-word}
    echo "a: $a b: $b" # a:  b: word
    
    d=${c:=word}
    echo "c: $c d: $d" # c: word d: word
    
    g='haha'
    h=${g:+word}
    echo "g: $g h: $h" # g: haha h: word
    
    f=${e:?word} # a.sh: line 13: e: word
    
    echo "this isn't echo" # 不会输出, 因为已经报错退出了
    

    运行:

    $ bash a.sh
    a:  b: word
    c: word d: word
    g: haha h: word
    a.sh: line 17: e: word
    
  • 参数切片

    • ${a:offset:length}: 从第 offset 个字符(不包含offset)开始, 截取 length 个
    • ${a:offset}: 没写 length, 默认截取到最后
    #!/bin/bash
    a='abcdefg'
    echo ${a:2} # cdefg
    echo ${a:2:3} # cde
    
  • 参数部分删除

    • ${parameter%patter} 最小程度从后面截取 patter
    • ${parameter%%patter} 最大程度从后面截取 patter
    • ${parameter#patter} 最小程度从前面截取 patter
    • ${parameter##patter} 最大程度从前面截取 patter

    所谓的最大程度和最小程度, 只有和正则相结合时才有效果, 如果 patter 只是一个字符串, 那么最大程度和最小程度都是一样的。

    #!/bin/bash
    
    a='111'
    echo ${a%1} # 11
    echo ${a%%1} # 11
    echo ${a#1} # 11
    echo ${a##1} # 11
    
    b='hello-world-helloo'
    echo ${b%-*} # hello-world
    echo ${b%%-*} # hello
    echo ${b#*-} # world-helloo
    echo ${b##*-} # helloo
    

命令替换 Command Substitution

有两种形式, 也就是前面提到的 $(...)``

数学计算 Arithmetic Expansion

即前面提到的 $(())

文件名展开 Filename Expansion

() * ? []外壳文件名模式匹配

当有单词没有被引号包裹, 且其中出现了 *, ?, [], {} 字符, 则 shell 会去按照正则匹配的方式查找文件名进行替换, 如果没找到则保持不变。

  • *: 匹配任意数量的任意字符。
  • ?: 匹配单个任意字符。
  • []: 匹配一组指定范围内的任意字符。
  • {}: 扩展为逗号分隔的任意字符串列表。
#!/bin/bash

echo ?.*
echo *[.]*
echo {a,b}.txt
$ bash a.sh
a.js a.sh a.txt b.txt
a.js a.sh a.txt b.txt index.html math.sh package.json package-lock.json test.js
a.txt b.txt

调试和前端集成

这部分只做引子, 暂时不会给出具体案例

调试

  1. 打印输出。使用 echo, printf 命令
  2. 使用 set 命令。一般会在开头添加 set -uxe -o pipefail 配置。

set 命令用于设置和修改 shell 的选项和参数。-u 参数作用是 "遇到不存在的变量就会报错, 并停止执行"; -x 作用 "运行结果之前, 先输出执行的那一行命令"; -e 作用 "只要发生错误, 就终止执行"; -o pipefail 作用 "管道符连接的时, 只要一个子命令失败, 则整个管道命令就失败, 脚本会终止执行"

  1. 利用vscode debug 插件
  2. 安装插件(1必选3可选): Bash Debug 支持单步调试; 还有三个可选的插件: shellman 用于代码提示和自动补全, shellcheck 代码语法校验, shell-format 代码格式化。
  3. 编写 launch.json 配置文件
  4. 确保 bash 在 4.x 以上版本。可通过 bash --version 查看版本

前端集成

在前端中也可以使用 shell 命令做相关操作。

  • node 中通过 execspawn 调用 shell 命令。这两个函数均在 child_process 包中。

    • exec 启动一个子 shell 进程执行传入的命令, 并且将执行结果保存在缓中区中, 并且缓中区是有大小限制(200KB)的, 执行完毕通过回调函数返回。
    • spawn 默认不使用 shell, 而是直接启动子进程执行命令, 且会直接返回一个流对象, 支持写入或者读取流数据, 这个在大数据量交与的场景比较适合。
  • shell 脚本中调用 node 命令

    #! /bin/bash
    node ./exec.js
    echo 'exec.js running'
    
  • 借助 zx 等库进行 javascript, shell script 的融合

    • 借助 shell 完成系统操作, 比如文件io, 内存, 盘系统状查询等
    • 借助 nodejs 完成应用层能力, 比如网络io, 计算等
    #!/usr/bin/env zx
    // 通过 $`command` 来调用 shell
    let files = await $`ls -a | grep node`
    console.log( chalk.blue(`files: ${files}.`))
    await sleep(1000)
    await fetch('https://google.com')
    let answer = await question('do you want to create new dir ?')
    if (answer === 'y') {
      await $`mkdir temp_dir`
    }
    

课程总结

简单认识 Shell | 青训营笔记

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!