Linux shell学习

shell简介

基本定义

  • 一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。
  • Shell 既是一种命令语言,又是一种程序设计语言

  • Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务

真正能够控制计算机硬件(CPU、内存、显示器等)的只有操作系统内核(Kernel),图形界面和命令行只是架设在用户和内核之间的一座桥梁。由于安全、复杂、繁琐等原因,用户不能直接接触内核(也没有必要),需要另外再开发一个程序,让用户直接使用这个程序;该程序的作用就是接收用户的操作(点击图标、输入命令),并进行简单的处理,然后再传递给内核。如此一来,用户和内核之间就多了一层“代理”,这层“代理”既简化了用户的操作,也保护了内核。用户界面和命令行就是这个另外开发的程序,就是这层“代理”。在Linux下,这个命令行程序叫做 Shell

shell的作用

  • 调用其他程序,给其他程序传递数据或参数,并获取程序的处理结果;
  • 在多个程序之间传递数据,把一个程序的输出作为另一个程序的输入;
  • Shell 本身也可以被其他程序调用。

Shell 本身支持的命令并不多,但是它可以调用其他的程序,每个程序就是一个命令,这使得 Shell 命令的数量可以无限扩展,其结果就是 Shell 的功能非常强大,完全能够胜任 Linux 的日常管理工作,如文本或字符串检索、文件的查找或创建、大规模软件的自动部署、更改系统设置、监控服务器性能、发送报警邮件、抓取网页内容、压缩文件等。

Shell 主要用来开发一些实用的、自动化的小工具,而不是用来开发具有复杂业务逻辑的中大型软件,例如检测计算机的硬件参数、一键搭建Web开发环境、日志分析等。

shell脚本

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

  • 编译型语言:

    • 如 C/C++、Pascal、Go语言、汇编等,必须在程序运行之前将所有代码都翻译成二进制形式,也就是生成可执行文件,用户拿到的是最终生成的可执行文件,看不到源码。
    • 这个过程叫做编译(Compile),这样的编程语言叫做编译型语言,完成编译过程的软件叫做编译器(Compiler)。
    • 编译型语言的优点是执行速度快、对硬件要求低、保密性好,适合开发操作系统、大型应用程序、数据库等。
  • 解释型语言或者脚本语言(Script)

    • 如 Shell、JavaScript、Python、PHP等,需要一边执行一边翻译,不会生成任何可执行文件,用户必须拿到源码才能运行程序。程序运行后会即时翻译,翻译完一部分执行一部分,不用等到所有代码都翻译完。
    • 这个过程叫做解释,这样的编程语言叫做解释型语言或者脚本语言(Script),完成解释过程的软件叫做解释器
    • 脚本语言的优点是使用灵活、部署容易、跨平台性好,非常适合Web开发以及小工具的制作。
    • Shell 就是一种脚本语言,我们编写完源码后不用编译,直接运行源码即可。

Shell 脚本很适合处理纯文本类型的数据,而 Linux 中几乎所有的配置文件、日志文件(如 NFS、Rsync、Httpd、Nginx、MySQL 等),以及绝大多数的启动文件都是纯文本类型的文件。

Shell 脚本是实现 Linux 系统自动管理以及自动化运维所必备的工具,Linux 的底层以及基础应用软件的核心大都涉及 Shell 脚本的内容。

Shell 脚本的优势在于处理偏操作系统底层的业务,例如,Linux 内部的很多应用(有的是应用的一部分)都是使用 Shell 脚本开发的,因为有 1000 多个 Linux 系统命令为它作支撑。

常见的shell:sh, bash, csh, tcsh, ash

Linux由多个组织机构开发,不同的组织机构为了发展自己的 Linux 分支可能会开发出功能类似的软件,它们各有优缺点,用户可以自由选择。Shell 就是这样的一款软件,不同的组织机构开发了不同的 Shell,它们各有所长,有的占用资源少,有的支持高级编程功能,有的兼容性好,有的重视用户体验。

Shell 既是一种脚本编程语言,也是一个连接内核和用户的软件。

常见的 Shell 有 sh、bash、csh、tcsh、ash 等。

sh

sh 的全称是 Bourne shell,由 AT&T 公司的 Steve Bourne开发,为了纪念他,就用他的名字命名。

sh 是 UNIX 上的标准 shell,很多 UNIX 版本都配有 sh。sh 是第一个流行的 Shell。

csh

sh 之后另一个广为流传的 shell 是由柏克莱大学的 Bill Joy (Bill Joy 是一个风云人物,他创立了 BSD 操作系统,开发了 vi 编辑器,还是 Sun 公司的创始人之一)。设计的,这个 shell 的语法有点类似C语言,所以才得名为 C shell ,简称为 csh。

BSD 是 UNIX 的一个重要分支,后人在此基础上发展出了很多现代的操作系统,最著名的有 FreeBSD、OpenBSD 和 NetBSD,就连 Mac OS X 在很大程度上也基于BSD。

tcsh

tcsh 是 csh 的增强版,加入了命令补全功能,提供了更加强大的语法支持。

ash

一个简单的轻量级的 Shell,占用资源少,适合运行于低内存环境,但是与下面讲到的 bash shell 完全兼容。

bash

bash shell 是 Linux 的默认 shell. bash 兼容 sh :针对 sh 编写的 Shell 代码可以不加修改地在 bash 中运行

bash 和 sh 的一些不同之处:

  • bash 扩展了一些命令和参数;
  • bash 并不完全和 sh 兼容,它们有些行为并不一致,但在大多数企业运维的情况下区别不大,特殊场景可以使用 bash 代替 sh。

shell查看

Shell 是一个程序,一般都是放在/bin或者/user/bin目录下,当前 Linux 系统可用的 Shell 都记录在/etc/shells文件中。/etc/shells是一个纯文本文件,你可以在图形界面下打开它,也可以使用 cat 命令(cat(英文全拼:concatenate)命令用于连接文件并打印到标准输出设备上)查看。

通过 cat 命令来查看当前 Linux 系统的可用 Shell:

1
2
3
4
5
$cat /etc/shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash

在现代的 Linux 上,sh 已经被 bash 代替,/bin/sh往往是指向/bin/bash的符号链接。

如果希望查看当前 Linux 的默认 Shell,那么可以输出 SHELL 环境变量:

1
2
$ echo $SHELL
/bin/bash

输出结果表明默认的 Shell 是 bash。

echo是一个 Shell 命令,用来输出变量的值,SHELL是 Linux 系统中的环境变量,它指明了当前使用的 Shell 程序的位置,也就是使用的哪个 Shell。

终端使用shell

一种进入 Shell 的方法是让 Linux 系统退出图形界面模式,进入控制台模式,这样一来,显示器上只有一个简单的带着白色文字的“黑屏”,就像图形界面出现之前的样子。这种模式称为 Linux 控制台(Console)。

现代 Linux 系统在启动时会自动创建几个虚拟控制台(Virtual Console),其中一个供图形桌面程序使用,其他的保留原生控制台的样子。虚拟控制台其实就是 Linux 系统内存中运行的虚拟终端(Virtual Terminal)。

从图形界面模式进入控制台模式也很简单,往往按下Ctrl + Alt + Fn(n=1,2,3,4,5...)快捷键就能够来回切换。

例如,CentOS 在启动时会创建 6 个虚拟控制台,按下快捷键Ctrl + Alt + Fn(n=2,3,4,5,6)可以从图形界面模式切换到控制台模式,按下Ctrl + Alt + F1可以从控制台模式再切换回图形界面模式。可以发现,1号控制台被图形桌面程序占用了。

Ubuntu中Ctrl + Alt + F7对应图形界面。

输入用户名和密码,登录成功后就可以进入 Shell 了。$是命令提示符,我们可以在它后面输入 Shell 命令。

在图形界面模式下,输入密码时往往会显示为,密码有几个字符就显示几个;而在控制台模式下,输入密码什么都不会显示,好像按键无效一样,但只要输入的密码正确就能够登录。

图形界面也是一个程序,会占用CPU时间和内存空间,当 Linux 作为服务器系统时,安装调试完毕后,应该让 Linux 运行在控制台模式下,以节省服务器资源。正是由于这个原因,很多服务器甚至不安装图形界面程序,管理员只能使用命令来完成各项操作。

在Ubuntu中也可以用快捷键Ctrl + Alt + t快速启动一个终端。打开终端后即可输入Shell命令。

shell提示符

启动终端模拟包或者从 Linux 控制台登录后,便可以看到 Shell 提示符。

对于普通用户,Base shell 默认的提示符是美元符号$;对于超级用户(root 用户),Bash Shell 默认的提示符是井号#(可使用sudo su切换到超级用户)。该符号表示 Shell 等待输入命令。

同的 Linux 发行版使用的提示符格式不同。例如在 Ubuntu中,默认的提示符格式为:dongshifu@dong:~$

这种格式包含了以下三个方面的信息:

  • 启动 Shell 的用户名,也即 dongshifu;
  • 本地主机名称,也即dong;
  • 当前目录,波浪号~是主目录的简写表示法。

shell脚本编辑与运行

打开文本编辑器,新建文件,扩展名为sh(sh代表shell),扩展名并不影响脚本执行,见名知意即可。

输入shell代码:

1
2
#!/bin/bash
echo "Hello World !"

“#!” 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种Shell。echo命令用于向窗口输出文本。

运行shell脚本的方法:

  • 作为可执行程序

将上面的代码保存为test.sh,并 cd 到相应目录:

1
2
chmod +x ./test.sh  #使脚本具有执行权限
./test.sh #执行脚本

注意,一定要写成./test.sh,而不是test.sh。运行其它二进制的程序也一样,直接写test.sh,linux系统会去PATH里寻找有没有叫test.sh的,而只有/bin, /sbin, /usr/bin,/usr/sbin等在PATH里,你的当前目录通常不在PATH里,所以写成test.sh是会找不到命令的,要用./test.sh告诉系统说,就在当前目录找。

通过这种方式运行bash脚本,第一行一定要写对,好让shell查找到正确的解释器。

  • 作为解释器参数

这种运行方式是,直接运行解释器,其参数就是shell脚本的文件名,如:

1
/bin/bash test.sh

这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。

例子:用read命令从stdin获取输入并赋值给PERSON变量,最后在stdout输出:

1
2
3
4
#!/bin/bash
echo "what is your name?"
read PERSON
echo "Hello, $PERSON"

shell变量:shell变量的定义、删除变量、只读变量、变量类型

脚本语言在定义变量时通常不需要指明类型,直接赋值就可以,Shell 变量也遵循这个规则。

在 Bash shell 中,每一个变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储。这意味着,Bash shell 在默认情况下不会区分变量类型,即使你将整数和小数赋值给变量,它们也会被视为字符串,这一点和大部分的编程语言不同。

如果有必要,你也可以使用 declare 关键字显式定义变量的类型,但在一般情况下没有这个需求。

定义变量

三种定义变量方式:

1
2
3
variable=value
variable='value'
variable="value"

variable 是变量名,value 是赋给变量的值。如果 value 不包含任何空白符(例如空格、Tab缩进等),那么可以不使用引号;如果 value 包含了空白符,那么就必须使用引号包围起来。使用单引号和使用双引号有区别。

注意,赋值号的周围不能有空格

Shell 变量的命名规范和大部分编程语言都一样:

  • 变量名由数字、字母、下划线组成;
  • 必须以字母或者下划线开头;
  • 不能使用 Shell 里的关键字(通过 help 命令可以查看保留关键字)。

使用变量

使用一个定义过的变量,只要在变量名前面加美元符号$即可,如:

1
2
skill="Java"
echo "I am good at ${skill}Script"

变量名外面的花括号{ }是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,如果不给 skill 变量加花括号,写成echo "I am good at $skillScript",解释器就会把 $skillScript 当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。

修改变量的值

已定义的变量,可以被重新赋值,如:

1
2
3
4
lang=shell
echo ${lang}
lang=python
echo ${lang}

第二次对变量赋值时不能在变量名前加

单引号与双引号的区别

1
2
3
4
5
6
7
8
#!/bin/bash

test="you are so cute"
chare='hi,${test}'
chare2="hi,${test}"

#hi,${test}
#hi,you are so cute

以单引号' '包围变量的值时,单引号里面是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。这种方式比较适合定义显示纯字符串的情况,即不希望解析变量、命令等的场景。

以双引号” “包围变量的值时,输出时会先解析里面的变量和命令,而不是把双引号中的变量名和命令原样输出。这种方式比较适合字符串中附带有变量和命令并且想将其解析后再输出的变量定义。

如果变量的内容是数字,那么可以不加引号;如果真的需要原样输出就加单引号;其他没有特别要求的字符串等最好都加上双引号,定义变量时加双引号是最常见的使用场景。

将命令的结果赋值给变量

shell支持将命令的执行结果赋值给变量,常见的方式为:

1
2
variable=`command`
variable=$(command)

eg:

1
2
test=$(ls -al)
echo $test

只读变量

使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

1
2
3
4
5
test="you are so cute"
readonly test
test="you"

#bash: test: 只读变量

删除变量

使用 unset命令可以删除变量。语法:

1
unset variable_name

变量被删除后不能再次使用;unset 命令不能删除只读变量。

变量类型

运行shell时,会同时存在三种变量:

1) 局部变量

局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。

2) 环境变量

所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。

3) shell变量

shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

shell特殊变量:Shell $0, $#, $​*, $@, $?, $$和命令行参数

变量 含义
$0 当前脚本的文件名
$n 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。
$# 传递给脚本或函数的参数个数。
$* 传递给脚本或函数的所有参数。
$@ 传递给脚本或函数的所有参数。被双引号(“ “)包含时,与 $* 稍有不同,下面将会讲到。
$? 上个命令的退出状态,或函数的返回值。
$$ 当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。

命令行参数

运行脚本时传递给脚本的参数称为命令行参数。命令行参数用 $n$ 表示,例如,$1 表示第一个参数,$2 表示第二个参数,依次类推。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash

echo "file name: $0"
echo "First parameter: $1"
echo "Second parameter: $2"
echo "Quoted values: $@"
echo "Quoted values: $*"
echo "Total number of parameters: $#"

#运行结果
./test1.sh shifu dong
file name: ./test1.sh
First parameter: shifu
Second parameter: dong
Quoted values: shifu dong
Quoted values: shifu dong
Total number of parameters: 2

$* ,$@的区别

$* 和 $@ 都表示传递给函数或脚本的所有参数,不被双引号(“ “)包含时,都以”$1” “$2” … “$n” 的形式输出所有参数。

但是当它们被双引号(“ “)包含时,”$*” 会将所有的参数作为一个整体,以”$1 $2 … ​$n”的形式输出所有参数;”​$@” 会将各个参数分开,以”$1” “$2” … “$n” 的形式输出所有参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/bin/bash
echo "\$*=" $*
echo "\"\$*\"=" "$*"

echo "\$@=" $@
echo "\"\$@\"=" "$@"

echo "print each param from \$*"
for var in $*
do
echo "$var"
done

echo "print each parm from \$@"
for var in $@
do
echo $var
done

echo "print each parm form \"\$*\""
for var in "$*"
do
echo $var
done

echo "print each parm from \"\$@\""
for var in "$@"
do
echo $var
done

运行结果:
./test2.sh "A" "B"
$*= A B
"$*"= A B
$@= A B
"$@"= A B
print each param from $*
A
B
print each parm from $@
A
B
print each parm form "$*"
A B
print each parm from "$@"
A
B