shell介绍

语言分类

任何代码最终都要被“翻译”成二进制的形式才能在计算机中执行。

有的编程语言,如 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/

你可能感兴趣的:(shell介绍)