源码在这里Github(forked from shehaodan/full-shell-demo)
操作系统的内核(kernel)是真正操作硬件的,所以内核需要被保护,只能通过shell来和内核沟通。例如上面用的chmod,chown等命令都是独立的应用程序,可以通过shell(命令行模式)来操作这些应用程序。
之所以要学习命令行shell,因为各操作系统界面不一样,但各linux发行商提供的bash都是一样的(Linux的shell版本Bourne Again Shell,简称bash),所以学会命令行shell,可以一招鲜吃遍天。
- 概述
- 变量
- 字符串
- 数组
- 内置命令
- 数学计算
- 条件语句
概述
Shell程序一般都是放在/bin或者/usr/bin目录下,当前Linux系统可用的Shell都记录在/etc/shells文件中:
> cat /etc/shells # List of acceptable shells for chpass(1). # Ftpd will not allow users to connect who are not using # one of these shells. /bin/bash /bin/csh /bin/ksh /bin/sh /bin/tcsh /bin/zsh
当前Linux的默认Shell:
> echo $SHELL /bin/zsh
简单的脚本Github(forked from shehaodan/full-shell-demo)试一下:
#!/bin/bash echo "Hello World !" echo "What is your name?" read PERSON echo "Hello, $PERSON" # 执行 zsh first-test.sh # 或 cd 进根目录,执行 ./zsh first-test.sh (因为第一行#!/bin/bash,所以会用bash执行) # 以上两种方式都是开新进程执行shell脚本,可以使用source命令在当前进程中执行脚本:source ./first-test.sh
变量
在Bash shell中,变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储。
使用定义过的变量,只要在变量名前面加$即可。变量名外面的花括号{ }是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界
name='张三' echo $name echo "hello ${skill}" # Shell是严格区分单双引号的,单引号里面是什么就输出什么,适用于纯字符串,无法用在变量的输出上。字符串里包含变量,参照上面用双引号
只读的变量需要加上readonly,删除变量用unset:
#!/bin/bash anoVar="unset test" unset anoVar echo $anoVar # 什么都没输出 myVar="readonly test" readonly myVar myVar="change var" # 执行到这里报错
局部变量要用local,否则默认都是全局变量:
#!/bin/bash function func(){ local a=99 b=100 } func # 执行函数 echo $a # 什么都没输出 echo $b # 100
想在其他子进程中使用当前的变量(例如环境变量environment variable),需要export出去。
环境变量被创建时所处的Shell进程称为父进程,如果在父进程中再创建一个新的进程来执行Shell命令,那么这个新的进程被称作Shell子进程。当Shell子进程产生时,它会继承父进程的环境变量,即说环境变量可从父进程传给子进程,但只能向下传递而不能向上传递。
#!/bin/bash a=22 #定义一个全局变量 echo $a #在当前Shell中输出a,成功输出22 bash #进入Shell子进程 echo $a #在子进程中输出a,失败输出空 exit #退出Shell子进程,返回上一级Shell export a #将a导出为环境变量 bash #重新进入Shell子进程 echo $a #在子进程中再次输出a,成功输出22 exit #退出Shell子进程 exit #退出父进程,结束整个Shell会话
运行shell脚本文件可以给它传递参数,脚本文件内部可以使用$n的形式来获取参数。
Shell脚本内部的函数没有形参和实参,即定义Shell函数时不能带参数,只能在调用函数时却传递参数,函数内部就也使用$n的形式获取参数。
#!/bin/bash echo "do $0" echo "name $1" echo "age $2" echo "params number $#" echo "$*" echo "$@" echo "$?" echo "process id $$" > zsh ./variable/position-var.sh Tim 10 # do ./variable/position-var.sh # name Tim # age 10 # params number 2 # tim 10 # tim 10 # 0 # process id 16354
上面看出参数是从$1开始的,$0是特殊变量,特殊变量一览:
$0:当前脚本的文件名
$n(n>1):传递给脚本或函数的参数
$#:传递给脚本或函数的参数个数
$*:传递给脚本或函数的所有参数
$@:传递给脚本或函数的所有参数。当被双引号””包含时,$@ 与 $* 稍有不同
$?:上个命令的退出状态(成功返回 0,失败返回1),或函数的返回值。
$$:当前Shell进程 ID
例子中$*和$@的输出结果一样,不被双引号””包围时,它们之间没有任何区别,都是将接收到的每个参数看做一份数据,彼此之间以空格来分隔。但是当它们被双引号””包含时:
$*:会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据。
$@:仍然将每个参数都看作一份数据,彼此之间是独立的。
#!/bin/bash echo "print each param from \"\$*\"" for var in "$*" do echo "$var" done echo "print each param from \"\$@\"" for var in "$@" do echo "$var" done > zsh ./variable/special-var-1.sh 1 2 3 4 # print each param from "$*" # 1 2 3 4 # print each param from "$@" # 1 # 2 # 3 # 4
字符串
Shell是严格区分单双引号的,单引号里面是什么就输出什么,适用于纯字符串,无法用在变量的输出上。字符串里包含变量需要用双引号。
获取字符串长度:${#string_name}
url="https://zxljack.com/" echo ${#url} #20
拼接字符串只需要两个放在一起(中间不能有空格)即可:
#!/bin/bash name="Site" url="http://www.baidu.com/" str1=$name$url # 中间不能有空格 str2="$name $url" # 如果被双引号包围,那么中间可以有空格 str3=$name": "$url # 中间可以出现别的字符串 str4="$name: $url" # 这样写也可以 str5="${name}Script: ${url}index.html" # 需要给变量名加上大括号 echo $str1 echo $str2 echo $str3 echo $str4 echo $str5 # Sitehttp://www.baidu.com/ # Site http://www.baidu.com/ # Site: http://www.baidu.com/ # Site: http://www.baidu.com/ # SiteScript: http://www.baidu.com/index.html
字符串截取有两种方式:从左边开始计数时,起始数字是 0(这符合程序员思维)。从右边开始计数时,起始数字是 1(这符合常人思维)。计数方向不同,起始数字也不同,但不管从哪边开始计数,截取方向都是从左到右。
格式 | 说明 | 粒子 |
---|---|---|
${string: start : length} | 从字符串的左边第 start 个字符开始,向右截取 length 个字符 |
url="https://zxljack.com/" echo ${url: 8: 7} # zxljack |
${string: start} | 从字符串的左边第 start 个字符开始截取,直到最后 |
url="https://zxljack.com/" echo ${url: 8} # zxljack.com/ |
${string: 0-start :length} | 从字符串的右边第 start 个字符开始截取,直到最后 |
url="https://zxljack.com/" echo ${url:0-12} # zxljack.com/ |
${string#*chars} | 从字符串第一次出现 *chars 的位置开始,截取 *chars 右边的所有字符 |
url="https://zxljack.com/" echo ${url#*//} # zxljack.com/ |
${string##*chars} | 从字符串最后一次出现 *chars 的位置开始,截取 *chars 右边的所有字符 |
url="https://zxljack.com/" echo ${url##*//} # zxljack.com/ |
${string%chars*} | 从字符串最后一次出现 chars* 的位置开始,截取chars * 左边的所有字符 |
url="https://zxljack.com/" echo ${url%//*} # https: |
${string%%chars*} | 从字符串第一次出现 chars* 的位置开始,截取 chars *左边的所有字符 |
url="https://zxljack.com/" echo ${url%%//*} # https: |
数组
()圆括号来定义数组,数组元素间用空格来分隔,获取数组元素和长度:
${array_name[index]} # 获取数组的某个值、 ${nums[*]} # 获取数组的所有元素 ${nums[@]} # 获取数组的所有元素 ${#array_name[@]} # 获取数组长度 ${#array_name[*]} # 获取数组长度 #!/bin/bash nums=(29 100 13 8 91 44) echo ${nums[@]} # 29 100 13 8 91 44 nums[10]=66 echo ${nums[*]} # 29 100 13 8 91 44 66 echo ${nums[4]} # 8 echo ${#nums[*]} # 10 echo ${#nums[@]} # 10 echo ${#nums[10]} # 2
数组合并的思路是:先利用@或*,将数组扩展成列表,然后再合并到一起。
array_new=(${array1[@]} ${array2[@]}) array_new=(${array1[*]} ${array2[*]}) #!/bin/bash array1=(23 56) array2=(99 "abc") array_new=(${array1[@]} ${array2[*]}) echo ${array_new[@]} # 23 56 99 abc
删除数组:
unset array_name[index] # 删除数组元素 unset array_name # 删除数组
内置命令
type来显示外部命令的路径,内置命令不显示路径:
> type cd cd is a shell builtin > type cal cal is /usr/bin/cal
通常来说,内建命令会比外部命令执行得更快,执行外部命令时不但会触发磁盘I/O,还需要fork出一个单独的进程来执行,执行完成后再退出。而执行内建命令相当于调用当前Shell进程的一个函数。
::扩展参数列表,执行重定向操作
.:读取并执行指定文件中的命令(在当前 shell 环境中)
alias:为指定命令定义一个别名
bg:将作业以后台模式运行
bind:将键盘序列绑定到一个 readline 函数或宏
break:退出 for、while、select 或 until 循环
builtin:执行指定的 shell 内建命令
caller:返回活动子函数调用的上下文
cd:将当前目录切换为指定的目录
command:执行指定的命令,无需进行通常的 shell 查找
compgen:为指定单词生成可能的补全匹配
complete:显示指定的单词是如何补全的
compopt:修改指定单词的补全选项
continue:继续执行 for、while、select 或 until 循环的下一次迭代
declare:声明一个变量或变量类型
dirs:显示当前存储目录的列表
disown:从进程作业表中刪除指定的作业
echo:将指定字符串输出到 STDOUT,如果不希望换行,可以加上-n 参数
enable:启用或禁用指定的内建shell命令
eval:将指定的参数拼接成一个命令,然后执行该命令
exec:用指定命令替换 shell 进程
exit:以指定的退出状态码退出,接受一个0-255间的整数作为退出码。默认 0 表示成功,任何为非 0 的都表示执行失败
export:设置子 shell 进程可用的变量
fc:从历史记录中选择命令列表
fg:将作业以前台模式运行
getopts:分析指定的位置参数
hash:查找并记住指定命令的全路径名
help:显示帮助文件
history:显示命令历史记录
jobs:列出活动作业
kill:向指定的进程 ID(PID) 发送一个系统信号
let:计算一个数学表达式中的每个参数
local:在函数中创建一个作用域受限的变量
logout:退出登录 shell
mapfile:从 STDIN 读取数据行,并将其加入索引数组
popd:从目录栈中删除记录
printf:使用格式化字符串显示文本
pushd:向目录栈添加一个目录
pwd:显示当前工作目录的路径名
read:从 STDIN 读取一行数据并将其赋给一个变量
readarray:从 STDIN 读取数据行并将其放入索引数组
readonly:从 STDIN 读取一行数据并将其赋给一个不可修改的变量
return:强制函数以某个值退出,这个值可以被调用脚本提取
set:设置并显示环境变量的值和 shell 属性
shift:将位置参数依次向下降一个位置
shopt:打开/关闭控制 shell 可选行为的变量值
source:读取并执行指定文件中的命令(在当前 shell 环境中)
suspend:暂停 Shell 的执行,直到收到一个 SIGCONT 信号
test:基于指定条件返回退出状态码 0 或 1
times:显示累计的用户和系统时间
trap:如果收到了指定的系统信号,执行指定的命令
type:显示指定的单词如果作为命令将会如何被解释
typeset:声明一个变量或变量类型。
ulimit:为系统用户设置指定的资源的上限
umask:为新建的文件和目录设置默认权限
unalias:刪除指定的别名
unset:刪除指定的环境变量或 shell 属性
wait:等待指定的进程完成,并返回退出状态码