语言分类
任何代码最终都要被“翻译”成二进制的形式才能在计算机中执行。
有的编程语言,如 C/C++、Pascal、Go语言、汇编等,必须在程序运行之前将所有代码都翻译成二进制形式,也就是生成可执行文件,用户拿到的是最终生成的可执行文件,看不到源码。
这个过程叫做编译(Compile),这样的编程语言叫做编译型语言
,完成编译过程的软件叫做编译器(Compiler)。
而有的编程语言,如 Shell、JavaScript、Python、PHP等,需要一边执行一边翻译,不会生成任何可执行文件,用户必须拿到源码才能运行程序。程序运行后会即时翻译,翻译完一部分执行一部分,不用等到所有代码都翻译完。
这个过程叫做解释,这样的编程语言叫做解释型语言
或者脚本语言
(Script),完成解释过程的软件叫做解释器。
编译型语言的优点是执行速度快、对硬件要求低、保密性好,适合开发操作系统、大型应用程序、数据库等。
脚本语言的优点是使用灵活、部署容易、跨平台性好,非常适合 Web 开发以及小工具的制作。
Shell 就是一种脚本语言,我们编写完源码后不用编译,直接运行源码即可。
shell脚本概述
1️⃣shell的种类非常多,我这里学习bash
sh 第一个流行的 Shell,是UNIX上的标准shell
csh
tcsh
ash
bash 是Linux上的默认shell
在现代的 Linux 上,sh 已经被 bash 代替,/bin/sh往往是指向/bin/bash的符号链接
查看当前默认使用的shell,输出 SHELL 环境变量即可查看
echo $SHELL
/bin/bash
2️⃣shell脚本的文件扩展名为sh,扩展名并不影响使用,见名知意就好
3️⃣shell脚本都以#!/bin/bash作为第一行,用于指定解释器,第一行的#不代表注释,除了第一行,其他各行如果以#开头都表示注释
4️⃣命令不需要分号结尾
5️⃣如何进入shell。我们平时输入命令的地方就是使用shell的地方,比如命令行终端,可以直接在里面输入shell命令。下面写出的代码不仅仅可以在shell脚本里写,也可以在终端输入
5️⃣创建第一个shell脚本
创建一个名为first.sh的文件
vi first.sh
输入如下内容并保存
#!/bin/bash //这一行用于指定解释器信息,否则调用方式为/bin/bash first.sh
pwd
#!
是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即表示使用哪一种Shell;
后面的/bin/bash
就是指明了解释器的具体位置。如果不加第一行,那么在调用命令时需要指定使用哪个解释器
修改权限为可执行
chmod 766 first.sh
全路径执行该文件(因为该命令还未加入环境变量配置的路径中,系统无法自行找到)
/Users/goat/Desktop/first.sh
命令行输出结果为当前路径/Users/goat/Desktop
6️⃣定义变量,注意=两边都不能有空格
name="chenlong"
age=12
还可以使用语句给变量赋值,语句需要用$()包起来,或者``飘号包起来
path1=$(pwd)
或者
path1=`pwd`
变量的使用,使用一个定义好的变量,需要在变量名前加$
name="chenlong"
name2=$name
echo $name
echo $(name)
只读变量,使用readonly命令可以将变量定义为只读,一旦定义不可被修改
name="chenlong"
readonly name
删除变量,不能删除只读变量
unset name
变量类型
运行shell时会同时存在三种变量
1、局部变量 在脚本或命令中定义,只在当前shell实例中生效
2、环境变量 所有的程序,包括shell启动的程序都能访问环境变量,
shell脚本也可以定义环境变量
3、shell变量 由shell程序设置的特殊变量
7️⃣字符串,可以用单引号,可以用双引号,也可以不用引号
name=chenlong
name="chenlong"
name='chenlong'
单引号字符串的特殊情况:
变量在单引号中无效,会原样输出,对单引号使用转义字符也无效
单引号字符串中不可出现单个的单引号,要成对出现,作为字符串拼接使用
获取字符串长度
name="chenlong"
${#name}
截取字符串
name="chenlong"
${name:1:4} //从第2个字符开始截取4个字符
字符串拼接
在脚本语言中,字符串的拼接(也称字符串连接或者字符串合并)往往都非常简单,例如:
在 PHP 中,使用.
即可连接两个字符串;
在 JavaScript 中,使用+
即可将两个字符串合并为一个。
然而,在 Shell 中你不需要使用任何运算符,将两个字符串并排放在一起就能实现拼接
name="Shell"
url="http://c.biancheng.net/shell/"
str1=$name$url #中间不能有空格
str2="$name $url" #如果被双引号包围,那么中间可以有空格
str3=$name": "$url #中间可以出现别的字符串
str4="$name: $url" #这样写也可以
str5="${name}Script: ${url}index.html" #这个时候需要给变量名加上大括号
8️⃣数组
bash支持一维数组,不支持多维,数组元素用空格分开
array_name=("chenlong" "hua" "wu")
读取数组
${array_name[下标值]}
使用@
或*
可以获取数组中的所有元素,例如:
${nums[*]}
${nums[@]}
nums=(29 100 13 8 91 44)
echo ${nums[@]} #输出所有数组元素
nums[10]=66 #给第10个元素赋值(此时会增加数组长度)
echo ${nums[*]} #输出所有数组元素
echo ${nums[4]} #输出第4个元素
运行结果:
29 100 13 8 91 44
29 100 13 8 91 44 66
91
获取数组长度,跟获取数组长度很像
${#array_name[@]} 或者 ${#array_name[*]}
${#array_name[下标值]} //获取单个元素长度
示例
nums=(29 100 13)
echo ${#nums[*]}
#向数组中添加元素
nums[10]="http://c.biancheng.net/shell/"
echo ${#nums[@]}
echo ${#nums[10]}
#删除数组元素
unset nums[1]
echo ${#nums[*]}
运行结果:
3
4
29
3
数组合并
拼接数组的思路是:先利用@或*,将数组扩展成列表,然后再合并到一起。具体格式如下:
array_new=(${array1[@]} ${array2[@]})
array_new=(${array1[*]} ${array2[*]})
示例
array1=(23 56)
array2=(99 "http://c.biancheng.net/shell/")
array_new=(${array1[@]} ${array2[*]})
echo ${array_new[@]} #也可以写作 ${array_new[*]}
运行结果:
23 56 99 http://c.biancheng.net/shell/
9️⃣条件命令
test用于检查条件是否成立,可以检查数值、字符串和文件
if 语句的判断条件,从本质上讲,判断的就是命令的退出状态。
下面是数值测试常用参数
-eq 等于则为真
-ne 不等则为真
-gt 大于则为真
-ge 大于或等于则为真
-lt 小于则为真
-le 小于或等于则为真
示例
num1=100
num2=200
if test $[num1] -eq $[num2]
then
echo "相等"
else
echo "不相等"
fi
下面是字符串测试常用参数
= 等于则为真
!= 不等于则为真
-z 字符串长度为零则为真
-n 字符串长度不为零则为真
示例
name1="chenlong"
name2="chenlong"
if test $name1 = $name2
then
echo "字符串相等"
else
echo "字符串不相等"
fi
if test -z $name1
then
echo "字符串长度为零"
else
echo "字符串长度不为零"
fi
文件测试常用命令
-e 文件存在则为真
-r 文件存在且可读
-w 文件存在且可写
-x 文件存在且可执行
-s 文件存在且至少有一个字符
-d 目录存在
-f 为普通文件
-c 文件存在且为字符型特殊文件
-b 文件存在且为块特殊文件
示例
if test -e ./test.txt
then
echo "文件存在"
else
echo "文件不存在"
fi
逻辑连接符
-a 与
-o 或
! 非
流程控制
shell流程控制的各个条件分支执行代码不可为空,如果为空则不要写这个分支
break命令跳出循环
continue命令跳出当前循环
if 条件
then
代码...
else
代码...
fi
if 条件
then
代码...
elif 条件
then
代码...
else
代码...
fi
for num in 1 2 3 1 2
do
echo "$num"
done
while()
do
done
//无限循环
while true
do
done
或者
for ((;;))
until 条件 //条件为真则停止
do
done
case 值 in
值1)
代码...
;;
值2)
代码...
;;
esac
函数
函数返回,可以显式的return返回,并且返回值是整数,整数范围0~255。也可以不加return返回,如果不加那么默认返回最后一条命令的运行结果
不带return的函数
say(){
echo "说话"
}
带return的函数,函数调用后,可以通过$?拿到返回值
add(){
echo "请输入第一个数字"
read num1
echo "请输入第二个数字"
read num2
echo "求和"
return $(($num1+$num2))
}
add
echo "结果为 $?"
函数调用直接使用函数名即可,需要注意的是函数需要先定义再使用
say
函数参数,在函数内部可以通过$n
的形式获取参数,$1表示第一个参数...
需要注意的是$后面跟的数字如果超过1位数,那么要用大括号括起来,否则无效
funWithParam(){
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
echo "第十个参数为 $10 !"
echo "第十个参数为 ${10} !"
echo "第十一个参数为 ${11} !"
echo "参数总数有 $# 个!"
echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
其他处理参数的字符
$# 参数个数
$* 以一个单字符串显示所有向脚本传递的参数
$$ 脚本运行的当前进程id号
$! 后台运行的最后一个进程id号
$@ 与$*相同,但是使用时加引号,并在引号中返回每个参数。
$- 显示Shell使用的当前选项,与set命令功能相同。
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误
学习了上面的函数等知识后,下面探究一下shell命令的选项和参数本质上是什么
一个 Shell 内置命令就是一个内部的函数,一个外部命令就是一个应用程序。内置命令后面附带的所有数据(所有选项和参数)最终都以参数的形式传递给了函数,外部命令后面附带的所有数据(所有选项和参数)最终都以参数的形式传递给了应用程序。
也就是说,不管是内置命令还是外部命令,它后面附带的所有数据都会被“打包”成参数,这些参数有的传递给了函数,有的传递给了应用程序。
有编程经验的读者应该知道,C语言或者 C++ 程序的入口函数是
int main(int argc, char *argv[])
,传递给应用程序的参数最终都被 main 函数接收了。从这个角度看,传递给应用程序的参数其实也是传递给了函数。有了以上认知,我们就不用再区分函数和应用程序了,我们就认为:不管是内置命令还是外部命令,它后面附带的数据最终都以参数的形式传递给了函数。实现一个命令的一项重要工作就是解析传递给函数的参数。
注意,命令后面附带的数据并不是被合并在一起,作为一个参数传递给函数的;这些数据是由空格分隔的,它们被分隔成了几份,就会转换成几个参数。例如getsum -s 1 -e 100要向函数传递四个参数,read -n 1 sex要向函数中传递三个参数。
并且,命令后面附带的数据都是“原汁原味”地传递给了函数,比如getsum -s 1 -e 100要传递的四个参数分别是 -s、1、-e、100,减号-也会一起传递过去,在函数内部,减号-可以用来区分该参数是否是命令的选项。
至于在函数内部如何解析这些参数,对于外部命令来说那就是 C/C++ 程序员的工作了。
shell命令的本质
- Shell 自带的命令称为内置命令,它在 Shell 内部可以通过函数来实现,当 Shell 启动后,这些命令所对应的代码(函数体代码)也被加载到内存中,所以使用内置命令是非常快速的
- 更多的命令是外部的应用程序,一个命令就对应一个应用程序。运行外部命令要开启一个新的进程,所以效率上比内置命令差很多。
- 用户输入一个命令后,Shell 先检测该命令是不是内置命令,如果是就执行,如果不是就检测有没有对应的外部程序:有的话就转而执行外部程序,执行结束后再回到 Shell;没有的话就报错,告诉用户该命令不存在
一个外部的应用程序究竟是如何变成一个 Shell 命令的呢?
应用程序就是一个文件,只不过这个文件是可以执行的。既然是文件,那么它就有一个名字,并且存放在文件系统中。用户在 Shell 中输入一个外部命令后,只是将可执行文件的名字告诉了 Shell,但是并没有告诉 Shell 去哪里寻找这个文件。
难道 Shell 要遍历整个文件系统,查看每个目录吗?这显然是不能实现的。
为了解决这个问题,Shell 在启动文件中增加了一个叫做 PATH 的环境变量,该变量就保存了 Shell 对外部命令的查找路径,如果在这些路径下找不到同名的文件,Shell 也不会再去其它路径下查找了,它就直接报错。
shell命令提示符
第一层命令提示符
[mozhiyan@localhost ~]$
或者
[mozhiyan@localhost ~]#
- [] 是提示符的分隔符号,没有特殊含义。
- mozhiyan 表示当前登录的用户,我现在使用的是 mozhiyan 用户登录。
- @ 是分隔符号,没有特殊含义。
- localhost 表示当前系统的简写主机名(完整主机名是 localhost.localdomain)。
- ~ 代表用户当前所在的目录为主目录(home 目录)。如果用户当前位于主目录下的 bin 目录中,那么这里显示的就是bin。
$ 是命令提示符。Linux 用这个符号标识登录的用户权限等级:
如果是超级用户(root 用户),提示符就是#;
如果是普通用户,提示符就是$。
第二层命令提示符
- 有些命令不能在一行内输入完成,需要换行,这个时候就会看到第二层命令提示符。第二层命令提示符默认为>
[mozhiyan@localhost ~]$ echo "Shell教程"
Shell教程
[mozhiyan@localhost ~]$ echo "
> http://
> c.biancheng.net
> "
http://
c.biancheng.net
第一个 echo 命令在一行内输入完成,不会出现第二层提示符。第二个 echo 命令需要多行才能输入完成,提示符>用来告诉用户命令还没输入完成,请继续输入。
echo 命令用来输出一个字符串。字符串是一组由" "包围起来的字符序列,echo 将第一个"作为字符串的开端,将第二个"作为字符串的结尾。对于第二个 echo 命令,我们将字符串分成多行,echo 遇到第一个"认为是不完整的字符串,所以会继续等待用户输入,直到遇见第二个"
shell脚本的执行方式
1、在终端直接调用,通过这种方式运行脚本,脚本文件第一行的#!/bin/bash一定要写对,好让系统查找到正确的解释器
2、直接在终端运行 Bash 解释器,将脚本文件的名字作为参数传递给 Bash
[mozhiyan@localhost demo]$ /bin/bash test.sh #使用Bash的绝对路径
通过这种方式运行脚本,不需要在脚本文件的第一行指定解释器信息,写了也没用。
3、在当前进程中运行 Shell 脚本
这里需要引入一个新的命令——source 命令。source 是 Shell 内置命令的一种,它会读取脚本文件中的代码,并依次执行所有语句。你也可以理解为,source 命令会强制执行脚本文件中的全部命令,而忽略脚本文件的权限。
source 命令的用法为:
source filename
也可以简写为:
. filename
两种写法的效果相同。对于第二种写法,注意点号.和文件名中间有一个空格。
shell四种运行方式(启动方式)
http://c.biancheng.net/view/3045.html
交互式的登录 Shell;
交互式的非登录 Shell;
非交互式的登录 Shell;
非交互式的非登录 Shell
shell常用配置文件
与 Bash Shell 有关的配置文件主要有
/etc/profile
~/.bash_profile
~/.bash_login
~/.profile
~/.bashrc
/etc/bashrc
/etc/profile.d/*.sh
不同的启动方式会加载不同的配置文件。
~表示用户主目录。是通配符,/etc/profile.d/.sh 表示 /etc/profile.d/ 目录下所有的脚本文件(以.sh结尾的文件)。
登录式的shell
Bash 官方文档说:如果是登录式的 Shell,首先会读取和执行 /etc/profiles,这是所有用户的全局配置文件,接着会到用户主目录中寻找 /.bash_profile、/.bash_login 或者 ~/.profile,它们都是用户个人的配置文件。
不同的 Linux 发行版附带的个人配置文件也不同,有的可能只有其中一个,有的可能三者都有,笔者使用的是 CentOS 7,该发行版只有 ~/.bash_profile,其它两个都没有。
如果三个文件同时存在的话,到底应该加载哪一个呢?它们的优先级顺序是 ~/.bash_profile > ~/.bash_login > ~/.profile。
如果 ~/.bash_profile 存在,那么一切以该文件为准,并且到此结束,不再加载其它的配置文件。
如果 ~/.bash_profile 不存在,那么尝试加载 /.bash_login。/.bash_login 存在的话就到此结束,不存在的话就加载 ~/.profile。
注意,/etc/profiles 文件还会嵌套加载 /etc/profile.d/*.sh
同样,~/.bash_profile 也使用类似的方式加载 ~/.bashrc
非登录的 Shell
如果以非登录的方式启动 Shell,那么就不会读取以上所说的配置文件,而是直接读取 ~/.bashrc。
~/.bashrc 文件还会嵌套加载 /etc/bashrc
http://c.biancheng.net/shell/