Shell脚本和编程|青训营笔记

110 阅读6分钟

笔记内容:

  • Shell基础概念
  • 命令和语法
  • 执行过程和原理
  • 调试和前端集成

Shell基础概念

概念

  • 终端:获取用户输入、展示运算结果的硬件设备
  • 终端模拟器:Mac Terminal、iTerm2等,关联虚拟tty 的输入输出软件
  • Shell:commandinterpreter .处理来自终端模拟器的输入.解释执行之后输出结果给终端
  • Bash:shell的一种具体实现
  • tty:teletypewriters的简称,和终端等价,原来指的是电传打字机。在linux中是输入/输出环境

发展:

  • Ken Thompson(来自贝尔实验室)在1971年为UNIX开发了第一个shell,称为V6 shell
  • Stephen Bourne在贝尔实验室为V7UNIX所开发的 Bourneshell ,即sh
  • 开源组织GNU为了取代 Bourne shell开发的Bourne-Again shell,即 Bash

image.png

构成:

unix shell既是命令解释器又是编程语言,作为命令preter,shell提供了丰富的GNU实用程序集的用户界面。

image.png

命令和语法

变量

类型作用域声明方式规范
自定义变量当前shell=字符串、整型、浮点型、日期型
环境变量当前shell及其子shellexport、declare -x
系统环境变量所有shell启动加载

父子shell:

image.png

自定义变量

declare [+/一]选项 变量
选项含义
+给变量设定类型属性
-取消变量的类型属性
-a将变量声明为数组类型
-i将变量声明为整数型
-x将变量声明为环境变量
-r将变量声明为只读变量
-p显示指定变量的被声明的类型

代码示例:

#!/bin/bash
#变量名=变量值(等号左右不能有空格)
page_size=1
page_num=2

#将命令复制给变量
ls=ls

#将命令结果赋值给变量
file_list=$ ( ls -a)

#默认字符串,不会进行+运算
total=page_size*page_num× //错误的写法

#声明变量为整型
let total=page_size*page_num

declare -i total=page_size*page_num //错误的写法
#导出环境变量

export total
declare -x total

系统环境变量

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

配置文件加载

source ~/.bashrc

image.png

运算符和引用

类型符号作用用法
算数运算符+-* / %|&常规运算
逻辑运算符ll&& !
比较运算符==!=<>
引号双引号""部分引用,仅仅$ ` |保留作用foo="${a}123"
单引号''完全引用,原样输出foo=‘fooS$a'
反引号``执行命令foo=ls -a
圆括号(())算数运算foo=$((1+2))
()执行命令$(ls -a)
命令连接||cmdl执行完且返回码非0,则继续执行cmdl || cmd2
&&cmdl执行完且返回码为0,则继续执行cmd1 && cmd2
;cmdl、 cmd2串行执行cmdl ; cmd2
后台运行&让命令在后台运行,可与nohup一起使用cmd &

管道

管道与管道符: “│”,作用是将前一个命令的结果传递给后面的命令
语法: cmd1 | cmd2
要求:管道右侧的命令必须能接受标准输人才行比如 grep命令, ls、mv等不能直接使用,可以使用xargs预处理
注意: 管道命令仅仅处理stdout,对于stderr 会予以忽略, 可以使用set -o pipefail 设置 shel 遇到管道错误退出

#!/bin/bash
cat platform.access.log | grep ERROR
netstat -an | grep ESTABLISHED |wc -l
find . -maxdepth 1 -name "*.sh"| xargs ls -l

重定向

输出重定向符号作用输入重定向符号作用
>:覆盖写入文件<将文件作为命令的标准输入
>>:追加写入文件<<从标准输入中读入,知道遇见分界符才停止
2>:错误输出写人文件
&>:正确和错误输出统一写入到文件中

image.png

判断命令

shell中提供了test、[、[[ 三种判断符号,可用于:

  • 整数测试
  • 字符串测试
  • 文件测试
#!/ bin/ bash

#整数测试
test $n1 -eq $n2
test $n1 -lt $n2
test $n1 -gt $n2

#字符串测试
test -z $str_atest -n $str_a
test $str_a = $str_b

#文件测试
test -e /dmt && echo "exist"
test -f /usr/bin/npm && echo "file exist"

语法:

  • test condition
  • [ condition ]
  • [[ condition ]]

注意:

  • 中括号前后要有空格符;
  • [和 test是命令,只能使用自己支持的标志位, <、>、=只能用来比较字符串
  • 中括号内的变量,最好都是用引号括起来
  • [[更丰富,在整型比较中支持<、>、=,在字符串比较中支持=~正则

分支语句

#!/bin /bash

//语法1
level=0

if[ -n "$level" ]; then
    if ["$level" == 0]; then
        prefix=ERROR
    elif ["$level" == 1]; then
        prefix=INFO
    else
        echo "log level not supported"
    fi
fi

echo "[${prefix}] $message"


//语法2
name=john

case $name in
    "nick" )
        echo "hi nick"
    ;;
    "john" )
        echo "my name is john"
    ;;
    *)
        echo "404"
    ;;
esac

循环

  • while循环
    语法:while condition ; do程序段; done
    示例:
#!/bin/ bash

let num=0

while [ $num -lt 10 ];
do
    echo "current idx: $num"
    ((num++))
done
  • until循环
    语法:until condition ; do程序段; done
    示例:
#!/bin/ bash

let num=0

until [ $num -gt 10 ];
do
    echo "current idx: $num"
    ((num++))
done
  • for循环
    语法:for var in [words...]; do程序段;done
    示例:
!/bin/bash

//对列表进行循环
for foo in a b c
do
    echo $foo
done

//数值方式循环
for((i=0;i<10;i++))
do
    echo $i
done

函数

语法一:
funcName(){ echo "abc"; }
语法二:
function funcName() { echo "abc";}

代码示例:

#!/bin/ bash

printName() {
if [ $# -lt 2 ]; then
    echo "illegal parameter."
exit 1
fi
    echo "firstname is : $1"
    echo "lastname is: $2"
    
printName jacky chen

注意:

  • shell自上而下执行,函数必须在使用前定义
  • 函数获取变量和 shell script类似,$0表函数名,后续参数通过11、2 ...获取
  • 函数内 return 仅仅表示函数执行状态,不代表丽数执行结果
  • 返回结果一般使用echo、printf,在外面使用$()、``获取结果
  • 如果没有return ,函数状态是上一条命令的执行状态,存储在$?中

模块化

模块化的原理是在当前shell内执行函数文件
语法:source\[函数库的路径]

#!/bin/bash

#add函数
# @returnplatForm
function add ( ) {
    declare -i res=$1+$2
echo $res
}


#!/ bin / bash

source './ math. sh '

total=$(add 1 2)

echo $total

常用命令

命令使用
grep查找错误日志: grep -n "ERROR"-A3 -B3 cloudfun.log
统计次数: grep -n "ERROR" -c cloudfun.log
sort指定分隔符后以第三列进行排序: sort -t " " -k3
wc统计出现的行数、单词数、字符数: wc -lwm
head查看前十行: head -n 10 cloudfun.log
tail等待追加内容: tail -f -n 10 cloudfun.log
cut对数据行的内容进行处理cut -d ""-f 3
find文件和目录查找
xargs参数处理
which查找命令路径

执行过程和原理

执行

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

#! /bin/bash

#! /usr/bin/env bash

2、启动方式

# 文件名运行
./filename.sh

# 解释器运行
bash ./filename.sh

# source 运行
source ./filename. sh

执行过程

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

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

  • 执行命令

  1. builtin 直接执行
  2. 非builtin使用$PATH查找,然后启动子进程执行
  • 收集状态并返回

shell展开

  • 大括号展开(Brace Expansion) {...}
    描述:一般由三部分构成,前缀、一对大括号、后缀,大括号内可以是逗号分割的字符串序列,也可以是序列表达式 {x..y[..incr]}
    代码表示:
#字符串序列
a{b,c,d}e=>abe ace ade

#表达式序列,(数字可以使用incr调整增量,字母不行)
{1..5} =>1 2 3 4 5

{1..5..2}=>1 3 5

{a..e}=> a b c d el
  • 波浪号展开(Tilde Expansion) ~ 代码表示:
#当前用户主目录
~  =>  $HOME
~/ foo => $HOME/fool

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

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

#上一个工作目录
~-/foo => ${$OLDPWD-'~-'}/foo
  • 参数展开(Shell Parameter Expansion)
  1. 间接参数扩展${!parameter},其中引用的参数并不是parameter而是parameter的实际的值
  2. 参数长度${#parameter}
  3. 空参数处理
    ${parameter:-word} #为空替换
    ${parameter:=word} #为空替换,并将值赋给$parameter变量
    ${parameter:?word} #为空报错
    ${parameter:+word} #不为空替换
  4. 参数切片
    ${parameter:offset}
    ${parameter:offset:length}
  5. 参数部分删除
    ${parameter%word}#最小限度从后面截取word
    ${parameter%%word}#最大限度从后面截取word
    ${parameter#word}#最小限度从前面截取word
    ${parameter##word}#最大限度从前面截取word

代码表示(部分):

#!/ bin/ sh

str=abcdefg

sp1=${str##*d}

sp2=${str%%d*}

echo $sp1  #输出efg

echo $sp2  #输出abc
  • 命令替换(Command Substitution)

描述:在子进程中执行命令,并用得到的结果替换包裹的内容,形式上有两种: $(...)...
代码表示:

#!/bin/bash

echo $(whoimi)

foo(){
    echo "asdasd"
}

a=`foo`
  • 数学计算(Arithmetic Expansion)o $((..))

描述:使用$(())包裹数学运算表达式,得到结果并替换
代码表示:

#!/bin/ bash

echo $((1+2)) #3
  • 文件名展开(Filename Expansion) * ? [..]外壳文件名模式匹配

描述:当有单词没有被引号包裹,且其中出现了 ‘*’, ‘?’ , and ‘[’ 字符,则shell 会去按照正则匹配的方式查找文件名进行替换,如果没找到则保持不变。 代码表示:

#!/bin /bash

$echo D*

#输出当前目录下所有以D字母开头的目录、文件

调试和前端集成

调试

1.普通log,使用echo. printf 2.使用set命令 3. vscode debug插件

#!/bin/sh
a=1
d=( 1 2 3 4 5)
echo $a
echo $id[ 3]}echo $id[e]}
#输出
#1#4
# 1 2 3 4 5
set配置作用补充
-u遇到不存在的变量就会报错,并停止执行。-o nounset
-x运行结果之前,先输出执行的那一行命令。-o xtrace
-e只要发生错误,就终止执行-o errexit
-o pipefail管道符链接的,只要一个子命令失败,整个管道命令就失败,脚本就会终止执行。

代码示例:

#! /bin/ sh

set -uxe -o pipefail

echo "hello world"

VSCode配置

  • shellman:代码提示和自动补全
  • shellcheck:代码语法校验
  • shell-format:代码格式化
  • Bash Debug:支持单步调试
    • 安装vscode插件
    • 编写launch.json文件
    • 升级bash到4.x 以上版本
      代码示例:
#1.安装最新版本bash
brew install bash

#2.查看安装路径
which -a bash

#3.将新版本bash路径加入PATH
PATH="/usr/local/bin/bash: $PATH"

#4.配置vscode launch.json启动文件
{
    "version" : "0.2.0",
    "configurations":[
        {
            "type":"bashdb" ,
            "request":"launch" ,
            "name":"Bash-Debug (simplest configuration)",
            "cwd":"${workspaceFolder}" ,
            "program":"debug.sh"

        }
    ]
}

前端集成

  • node中通过exec. spawn调用shell命令
const f {exec} = require('child_process');

exec( 'ls',['-1'],(err,stdout,strerr)→>{
if (err){
    console.error(err);
}
stdout && console.log(stdout)
})

  • shell脚本中调用node命令
#!/bin/bash

set -e

node ./exec.js

echo 'success'
  • 借助zx等库进行javascript. shell script的融合
    • 借助shell完成系统操作.文件io、内存、磁盘系统状态查询等
    • 借助nodejs完成应用层能力.网络io、计算等
#!/usr/bin/env zx

//shell调用:$`command`
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`
}

课程总结

image.png