Shell
(Unix Shell
)是一种命令行解释器,是Unix
操作系统下最传统的人机接口。
Shell
脚本是解释执行的,不需要编译,和大部分的编程语言很相似,也有基本的变量和流程控制语句。
平时使用
Shell
有两种方式:
- 输入命令,执行,这种方式称为交互式(
Interactive
);- 批处理(
Batch
)方式,用户事先写好Shell
脚本文件,然后顺序执行脚本中的命令。
第一个
Shell
环境是Thompson Shell
,在贝尔实验室开发并于1971年
发布现代
Shell
最突出的祖先是被称为sh
的BourneShell
,这是以在AT&T
工作的创始人Stephen Bourne
命名的
Shell
一直在基于这个概念,不断添加各种新功能,演变出很多种的Shell
例如:很早版本的
OS X
中使用的是:
tcsh
作为默认的Shell
。这是由csh(C shell)
,一种类似C
语言的Shell
演变而来在
OS X 10.3
版与10.4
版之后,默认的Shell
是:
bash
,由GNU
开发除了默认的
bash
,现在macOS
中,默认的Shell
变成了zsh
这是一种由
Paul Falstad
于1990年
开发的。它是一个Bourne
式Shell
,它使用bash
和previous shell
的特性,并添加了更多的特性:
- 拼写检查功能
- 内置的编程特性
- 友好的交互
与此同时,
macOS
还提供了很多其他种类的Shell
:ls -ls /bin/*sh ------------------------- 680 -r-xr-xr-x 1 root wheel 623472 8 11 2020 /bin/bash 520 -rwxr-xr-x 1 root wheel 529424 8 11 2020 /bin/csh 112 -rwxr-xr-x 1 root wheel 110848 8 11 2020 /bin/dash 1440 -r-xr-xr-x 1 root wheel 1300256 8 11 2020 /bin/ksh 16 -rwxr-xr-x 1 root wheel 31440 8 11 2020 /bin/sh 520 -rwxr-xr-x 1 root wheel 529424 8 11 2020 /bin/tcsh 736 -rwxr-xr-x 1 root wheel 637840 8 11 2020 /bin/zsh
.bashrc、.bash_profile和.zshrc作用与区别
在使用命令行工具时,我们可能会遇到一些教程,可能需要你把一些配置写入到.bashrc
、.bash_profile
或者.zshrc
等。那么这几个文件到底有什么作用和区别?
从文件名称判断
.bashrc
、.bash_profile
是给bash
来使用的。而.zshrc
是给zsh
来使用的查看
bash
版本:bash ------------------------- The default interactive shell is now zsh. To update your account to use zsh, please run `chsh -s /bin/zsh`. For more details, please visit https://support.apple.com/kb/HT208050. bash-3.2$
查看
zhs
的安装路径:which zsh ------------------------- /bin/zsh
查看
bash
的安装路径:which bash ------------------------- /bin/bash
zsh
切换bash
,重新打开终端即可chsh -s /bin/bash
bash
切换zsh
,重新打开终端即可chsh -s /bin/zsh
交互式登录和非登录Shell
当调用
Shell
时,Shell
从一组启动文件中读取信息并执行命令。读取什么文件就取决于Shell
是作为交互式登录还是非登录调用。换言之,
Shell
分为交互式的或非交互式的:
- 交互式
Shell
是读取和写入到用户终端的Shell
程序,用户在终端上输入命令,并在回车后立即执行- 非交互式
Shell
是与终端不相关的Shell
程序。例如:执行脚本时交互式
Shell
可以是登录Shell
,也可以是非登录Shell
当用户通过
ssh
或本地远程登录到终端时,或者使用--login
选项启动时,将调用登录Shell
zsh --login
当
bash
作为交互式登录Shell
调用时,bash
会先查找/etc/profile
文件,如果该文件存在,它将运行文件中列出的命令,然后搜索~/.bash_profile
、~/.bash_login
以及~/.profile
文件,顺序读取当
bash
作为交互式非登录Shell
调用时,会读取~/.bashrc
cat /etc/profile ------------------------- # System-wide .profile for sh(1) if [ -x /usr/libexec/path_helper ]; then eval `/usr/libexec/path_helper -s` fi if [ "${BASH-no}" != "no" ]; then [ -r /etc/bashrc ] && . /etc/bashrc fi
所以说,
.bashrc
和.bash_profile
之间的区别是,.bash_profile
当bash
作为交互式登录Shell
调用时被读取并执行,而.bashrc
对于交互式非登录Shell
被执行
大多数
Linux/Unix
发行版都使用~/.profile
代替~/.bash_profile
。~/.profile
所有Shell
都读取该文件,而~/.bash_profile
只有bash
才会读取该文件
~/.zshrc
是zsh
的交互式Shell
的用户配置
对于
bash
,它们的工作方式如下:
读取适当的内容,执行
A
,然后执行B
,然后执行C
,依此类推。B1
,B2
,B3
表示仅执行找到的那些文件中的第一个。+----------------+-----------+-----------+------+ | |Interactive|Interactive|Script| | |login |non-login | | +----------------+-----------+-----------+------+ |/etc/profile | A | | | +----------------+-----------+-----------+------+ |/etc/bash.bashrc| | A | | +----------------+-----------+-----------+------+ |~/.bashrc | | B | | +----------------+-----------+-----------+------+ |~/.bash_profile | B1 | | | +----------------+-----------+-----------+------+ |~/.bash_login | B2 | | | +----------------+-----------+-----------+------+ |~/.profile | B3 | | | +----------------+-----------+-----------+------+ |BASH_ENV | | | A | +----------------+-----------+-----------+------+ | | | | | +----------------+-----------+-----------+------+ | | | | | +----------------+-----------+-----------+------+ |~/.bash_logout | C | | | +----------------+-----------+-----------+------+
对于
zsh
,它们的工作方式如下:读取适当的内容,执行
A
,然后执行B
,然后执行C
,依此类推。+----------------+-----------+-----------+------+ | |Interactive|Interactive|Script| | |login |non-login | | +----------------+-----------+-----------+------+ |/etc/zshenv | A | A | A | +----------------+-----------+-----------+------+ |~/.zshenv | B | B | B | +----------------+-----------+-----------+------+ |/etc/zprofile | C | | | +----------------+-----------+-----------+------+ |~/.zprofile | D | | | +----------------+-----------+-----------+------+ |/etc/zshrc | E | C | | +----------------+-----------+-----------+------+ |~/.zshrc | F | D | | +----------------+-----------+-----------+------+ |/etc/zlogin | G | | | +----------------+-----------+-----------+------+ |~/.zlogin | H | | | +----------------+-----------+-----------+------+ | | | | | +----------------+-----------+-----------+------+ | | | | | +----------------+-----------+-----------+------+ |~/.zlogout | I | | | +----------------+-----------+-----------+------+ |/etc/zlogout | J | | | +----------------+-----------+-----------+------+
如何确认当前是登录还是非登录
Shell
?确认当前终端
tty
使用的Shell
类型:echo $0 ------------------------- zsh
- 前面有
-
,表示当前已登录- 前面没有
-
,表示当前非登录使用
login
命令登录login ------------------------- login: Zang Password:***
再次查看
echo $0 ------------------------- -zsh
配置建议
bash
:
- 将配置选项放到
~/.bashrc
中,然后在~/.bash_profile
中通过source
调用
zsh
:
- 建议仍然将配置选项放到
~/.bashrc
,~/.bash_profile
中通过source
调用,最后在~/.zshrc
中source
调用~/.bash_profile
编译器与解释器
编译型语言需要编译处理:
- 源代码(
source code
)-> 预处理器(preprocessor
)-> 编译器(compiler
)-> 目标代码(object code
)-> 链接器(Linker
)-> 可执行程序(executables
)
解释型语言需要解释器处理:
- 源代码(
source code
)-> 解释器(interpreter
)
Shell初探
Shebang
(Hashbang
):一个由井号和叹号构成的字符序列#!
出现在文本文件的第一行的前两个字符#!/bin/bash
操作系统的程序加载器会分析
Shebang
后的内容,将这些内容作为解释器指令。并调用该指令,并将载有Shebang
的文件路径作为该解释器的参数
env
:不同对操作系统,脚本解释器可能被安装于系统的不同的目录,设置到系统的PATH
中
env
可以在系统的PATH
目录中查找#!/usr/bin/python #!/usr/bin/env pyhon
- 上述命令,使用在用户路径中找到的第一个
Python
版本可以通过指定版本号:
#!/usr/bin/env pythonX.x
env
也可以指定搜索目录:#!/usr/bin/env -S -P/usr/local/bin:/usr/bin:${PATH} python
- 在
/usr/local/bin
、/usr/bin
、系统PATH
搜索Python
Shell
子进程:是从父子进程的概念出发的,unix
操作系统的进程从init
进程开始(init
进程为1
,而进程号0
为系统原始进程,以下讨论的进程原则上不包括进程0
)均有其对应的子进程,就算是由于父进程先行结束导致的孤儿进程,也会被init
领养,使其父进程ID
为1
因为所有的进程均有父进程,事实上,所有进程的创建,都可视为子进程创建过程。
unix
操作系统进程的创建,大致都是进行fork + exec
类系统调用理解子进程的创建执行,需要至少细分到二个步骤,包括:
- 通过
fork
创建子进程环境,- 通过
exec
加载并执行进程代码。
Shell
子进程(以下均称subshell
):顾名思义,就是在当前的Shell
下,去打开另一个新Shell
PS1
提示符定义:提示符设置[\u@\h \w \A #\#]\$
\u
:用户账号名称\h
:主机名缩写\w
:完整工作路径\A
:24小时
时间\#
:第几个命令\$
:提示符,如果是root
,提示符为#
,否则是$
HOME
:代表用户主文件夹。
SHELL
:当前使用的是那个SHELL
PATH
:执行文件查找路径查看
PS1
提示符:echo $PS1 ------------------------- %{%f%b%k%}$(build_prompt)
read [-pt]
:读取键盘变量
-p
:提示符-t
:等待秒数写入:
read website Cat
读取:
echo $website ------------------------- Cat
注释
单行注释
# echo "单行注释"
多行注释
- 方式一:
: << ! 多行注释方式一: echo "多行注释" !
- 方式二:
: << COMMENT 多行注释方式二: echo "多行注释" COMMENT
- 方式三:
: ' 多行注释方式三: echo "多行注释" '
- 方式四:
if false; then 多行注释方式四: echo "多行注释" fi
- 方式五
((0)) && { 多行注释方式五: echo "多行注释" }
特殊符号的使用
双引号:
""
首先,单引号和双引号,都是为了解决中间有空格的问题。
因为空格在
Shell
中作为一个很典型的分隔符,比如string1=this is astring
,这样执行就会报错。为了避免这个问题,因此就产生了单引号和双引号。他们的区别在于,单引号将剥夺其中的所有字符的特殊含义,而双引号中的$(参数替换)
和反引号(命令替换与$()
作用一样)是例外。所以,两者基本上没有什么区别,除非在内容中遇到了参数替换符$
和命令替换符反引号双引号常用于包含一组字符串,在双引号中,除了
$
、\
、`
(反引号)有特殊含义外,其余字符(如换行符、回车符等)没有特殊含义
echo
输出1&2
,由于&
是Shell
中的特殊符号,直接运行命令会报错的echo 1&2 ------------------------- [1] 56375 1 cd: no such entry in dir stack [1] + 56375 done echo 1
可以使用双引号
""
,以此解决可能会产生歧义的问题echo "1&2" ------------------------- 1&2
使用双引号
""
进行多行输入echo " 1&2 " ------------------------- 1&2
- 输出时,前后都有回车。因为换行符、回车符在双引号中没有特殊含义,也会直接输出
反引号:
``
反引号的功能是命令替换,在反引号
``
中的内容通常是命令行,程序会优先执行反引号中的内容,并使用运行结果替换掉反引号处的内容echo "I am `echo Cat`" ------------------------- I am Cat
- 在按照
Shell
从上往下,从左往右执行规则的前提下,优先执行反引号``
的内容
单引号:
''
单引号的功能与双引号类似,不过单引号中的所有字符都没有特殊含义
echo ' i am `echo Cat` ' ------------------------- i am `echo Cat`
- 反引号
``
在单引号''
中没有生效,因为单引号中的所有字符都没有特殊含义
$ + 小括号
:$()
作用与反引号
``
一样,也是命令替换echo "i am $(echo Cat)" ------------------------- i am Cat
$ + 双小括号
:$(())
$(())
的功能是进行算术运算,括号中的内容为数学表达式echo "20 + 5 * 6 = $((20 + 5 * 6))" ------------------------- 20 + 5 * 6 = 50
$((20 + 5 * 6))
返回双括号内算数运算的结果
$ + 中括号
:$[]
$[]
的功能与$(())
一样,都是用于算术运算echo "20 + 5 * 6 = $[20 + 5 * 6]" ------------------------- 20 + 5 * 6 = 50
$ + 大括号
:${}
${}
的功能是变量引用,类似于$
符,但是${}
比$
的替换范围更精准
小括号:
()
用来定义一个数组变量
双小括号:
(())
双小括号命令允许在比较过程中使用高级数学表达式
中括号:
[]
单个的中括号的功能与
test
命令一样,都是用作条件测试
双中括号:
[[]]
双中括号提供了针对字符串比较的高级特性,使用双中括号
[[]]
进行字符串比较时,可以把右边的项看做一个模式,故而可以在[[]]
中使用正则表达式
大括号:
{}
大括号用于括起一个语句块
冒号:
(:)
作为内建命令:占位符、参数扩展和重定向
Shell
里面的括号
${a}
:变量a
的值, 在不引起歧义的情况下可以省略大括号$(cmd)
:命令替换, 和cmd
效果相同$((exp))
:增强括号的用法和expr exp
效果相同,计算数学表达式exp
的数值。其中exp
只要符合C语言
的运算规则即可,甚至三目运算符和逻辑表达式都可以计算。可以省略$
例如:
for((i=0;i<5;i++)) # 如果不使用双括号 for i in seq 0 4 for i in {0..4}
if (($i<5)) # 如果不使用双括号 if [ $i -lt 5 ]
expr
命令:是一款表达式计算工具,使用它完成表达式的求值操作
expr 1 + 2 + 3 - 4 ------------------------- 2
语法要求,中间的空格必须加
eval
命令:对后面的
cmdLine
进行两遍扫描,如果第一遍扫描后,cmdLine
是个普通命令,则执行此命令;如果cmdLine
中含有变量的间接引用,则保证间接引用的语义
type
命令:显示命令属性,会显示该命令所在的位置
type [-aftpP] name [name,...]
-a
:打印name
的所有可能情况-f
:不会去查找function
-t
:打印alias
、keyword
、function
、built-in
、file
这五种类型-p
:如果type -t name
输出file
,那么会打印name
所在路径-P
:不管type -t name
是不是输出file
,都会去搜索name
所在路径。例如:type -P ls
,尽管type -t ls
打印的是alias
(因为alias
的优先级高于file
),但是仍然会搜索出ls
所在的路径/bin/ls
type -a ls ------------------------- ls is an alias for ls -G ls is /bin/ls
如果
type
不加任何选项,直接加一个或者多个name
,那么会依次打印这些name
的类型。只有所有name
的类型都能成功打印,type
才返回成功。否则,只要任何一个name
类型无法打印,那么就返回失败
echo
命令:按照规则打印字符串
echo [-ne][字符串]
-n
:不要在最后自动换行-e
:若字符串中出现以下字符,则特别加以处理:\a
:发出警告;\b
:删除前一个字符;\c
:不产生进一步输出(\c
后面的字符不会输出);\f
:换行但光标仍旧停留在原来的位置;\n
:换行且光标移至行首;\r
:光标移至行首,但不换行;\t
:插入tab
;\v
:与\f
相同;\\
:插入\
字符;\nnn
:插入nnn
(八进制)所代表的ASCII
字符;用
echo
命令打印带有色彩的文字:
- 文字色:
echo -e "\e[1;31mThis is red text\e[0m"
\e[1;31m
:将颜色设置为红色
\e[0m
:将颜色重新置回
颜色码:重置=0
,黑色=30
,红色=31
,绿色=32
,黄色=33
,蓝色=34
,洋红=35
,青色=36
,白色=37
- 背景色:
echo -e "\e[1;42mGreed Background\e[0m"
颜色码:重置=0
,黑色=40
,红色=41
,绿色=42
,黄色=43
,蓝色=44
,洋红=45
,青色=46
,白色=47
- 文字闪动:
echo -e "\033[37;31;5mLogic Cat 帅帅帅...\033[39;49;0m"
红色数字处还有其他数字参数:0
关闭所有属性、1
设置高亮度(加粗)、4
下划线、5
闪烁、7
反显、8
消隐
grep
命令:用于查找文件里符合条件的字符串
创建
Common Symbol
目录,Common Symbol
目录下创建test.m
文件,写入以下代码:#import <Foundation/Foundation.h> void SomeNewFunction_weak_import(void) __attribute__((weak_import)); void SomeNewFunction_weak_import(void) { NSLog(@"SomeNewFunction_weak_import"); } void SomeNewFunction_weak(void) __attribute__((weak)); void SomeNewFunction_weak(void) { NSLog(@"SomeNewFunction_weak"); }
在
test.m
文件中,搜索weak
字符串
grep "$KEY_WORD" --color=auto -- $FILE
grep weak --color=auto -- ../Common\ Symbol/test.m ------------------------- void SomeNewFunction_weak_import(void) __attribute__((weak_import)); void SomeNewFunction_weak_import(void) { NSLog(@"SomeNewFunction_weak_import"); void SomeNewFunction_weak(void) __attribute__((weak)); void SomeNewFunction_weak(void) { NSLog(@"SomeNewFunction_weak");
-- ../Common\ Symbol/test.m
:其中--
表示当前命令已经没有参数要添加了,后面的内容都是传递给命令的- 可以通过
--
的方式,将内容和参数区分开路径中包含空格,则有三种不同的解决策略:
- 加
\
转义grep weak --color=auto -- ../Common\ Symbol/test.m
- 加双引号
grep weak --color=auto -- "../Common Symbol/test.m"
- 加单引号
grep weak --color=auto -- '../Common Symbol/test.m'
错误写法:
grep weak --color=auto -- "../Common\ Symbol/test.m" grep weak --color=auto -- '../Common\ Symbol/test.m' ------------------------- grep: ../Common\ Symbol/test.m: No such file or directory
- 使用双引号或单引号,里面不能再加
\
转义
标准输出 & 输入 & 错误输出
标准输出和标准错误输出可以将内容重定向输出到指定的设备(如打印机)或文件中,标准输入可以使用文件或其他输入替换手动输入。
标准输出(
stdout
):代码为1
,使用>
或>>
使用
>
或1>
以覆盖的方式,将正确的数据输出到指定到文件或设备echo "LGCat" > Cat echo "LGCat123" > Cat
- 目录下生成
Cat
文件,只会记录最后输出的LGCat123
使用
>>
或1>>
以累加到方式,将正确到数据输出到指定到文件或者设备上echo "LGCat" >> Cat1 echo "LGCat123" >> Cat1
- 目录下生成
Cat1
文件,将两次输出内容累加记录
标准错误输出(
stderr
):代码为2
,使用2>
或2>>
使用
2>
以覆盖的方式,将错误的数据输出到指定到文件或设备echo "LGCat" 2> Cat
使用
2>>
以累加的方式,将错误的数据输出到指定到文件或设备echo "LGCat" 2>> Cat1
标准输入(
stdin
):代码为0
,使用<
或<<
使用
<
标准输入cat < Cat ------------------------- LGCat123
使用
cat > file
或cat >> file
标准输入cat > Cat1237 111 222
- 使用
> file
等待输入,将输入的内容保存到file
中,使用control + d
结束输入。>
覆盖>>
累加使用
<<
结束输入,后接一个结束输入符cat > Cat1237 << "cat" 111 222 cat
- 将
cat
作为结结束提示符- 按照日常规范建议使用
eof
表示结束提示符
单箭头和双箭头的区别:
对于输出:
- 单箭头:当指定的文件不存在时,创建新文件写入数据;当文件存在时,清空原文件的内容写入数据
- 双箭头:当指定的文件不存在时,创建新文件写入数据;当文件存在时,在原件内容的最后追加写入数据
对于输入:
- 单箭头:将文件或其他输入作为标准输入(
<
的左边必须是命令,<
右边的输入内容作为命令的输入)- 双箭头:结束输入
将标准输出和错误输出重定向到一个文件上:
将
stdin
和stderr
无序输出到one.log
:grep "Cat" file.log > one.log 2>one.log
将
stdin
和stderr
序有输出到one.log
:grep "Cat" file.log > one.log 2>&1
- 首先
stdin
重定向到one.log
,然后使用2>&1
表示stderr
重定向至stdin
,stderr
在stdin
之后输入到one.log
中简写:
grep "Cat" file.log &> one.log
使用
>/dev/null
将错误的数据丢弃,只显示正确的数据echo "123" > /dev/null
/dev/null
代表linux
的空设备文件,所有往这个文件里面写入的内容都会丢失,俗称“黑洞”使用
&>
或者>&
将正确的数据和错误的数据写入同一个文件echo "111" &> Cat echo "222" >& Cat
使用
1>&2
正确返回值传递给2
输出通道,&2
表示2
输出通道echo "111222" 1>&2
使用
2>&1
错误返回值传递给1
输出通道,&1
表示1
输出通道echo "111222" 2>&1
cmd;cmd
不考虑命令相关性,连续执行。echo "Cat1";echo "Cat2" ------------------------- Cat1 Cat2
当前一个命令执行成功会回传一个
$?=0
的值e ------------------------- zsh: command not found: e ------------------------- echo $? ------------------------- 127
cmd1 && cmd2
如果第一个命令的$?
为0
,则执行第二个命令echo "Cat1" && echo "Cat2" ------------------------- Cat1 Cat2
cmd1 || cmd2
如果第一个命令的$?
为0
,则不执行第二个命令。否则执行第二个命令e || echo "Cat" ------------------------- zsh: command not found: e Cat
|
:管道仅能处理前面一个命令传来的正确信息,将正确信息作为stdin
传给下一个命令echo "test.m" | grep weak
- 管道命令只处理前一个命令正确输出,不处理错误输出
- 管道命令右边命令,必须能够接收标准输入流命令才行
- 大多数命令都不接受标准输入作为参数,只能直接在命令行输入参数,这导致无法用管道命令传递参数
xargs
:是将标准输入转为命令行参数echo "Cat1237" | xargs cat ------------------------- 111 222
-
:减号在一些命令中表示从标准输入(stdin
)中获取内容echo "Cat1237" | xargs cat - ------------------------- 111 222
三种运行方式
sh
:
- 使用
$sh script.sh
执行脚本时,当前Shell
是父进程,生成一个子Shell
进程,在子Shell
中执行脚本- 脚本执行完毕,退出子
Shell
,回到当前Shell
。$./script.sh
与$sh script.sh
等效。也叫fork
方式
source
:
- 使用
$source script.sh
方式,在当前上下文中执行脚本,不会生成新的进程。脚本执行完毕,回到当前Shell
$sh script.sh
与$source script.sh
等效。不需要有"执行权限"
exec
:
- 使用
exec ./scriptsh
方式,会用command
进程替换当前Shell
进程,并且保持pid
不变- 执行完毕,直接退出,不回到之前的
Shell
环境
执行权限
sh/bash/zsh
:不需要执行权限./script.sh
:需要执行权限source script.sh
:不需要执行权限exec
:需要执行权限
变量的定义
Shell
变量默认为字符串。Shell
不关心这个串是什么含义declare a=3 b=4 c c=a+b echo $c ------------------------- a+b
Shell
默认的数值运算是整数类型。所以若要进行数学运算,必须使用一些命令,例如:declare
、expr
、双括号等declare a=`expr 3 + 4` b=$((1+1)) echo $a;echo $b ------------------------- 7 2
Shell
变量可分为两类:
- 局部变量:只在创建它们的
Shell
中可用。在函数内定义,函数执行后就被删除- 环境变量:可以在创建它们的
Shell
及其派生出来的任意子进程中使用。在整个脚本执行期间,只要没有被删除就一直存在export a=3 echo $a ------------------------- 3
定义规则:
- 变量名必须以字母或下划线字符开头,其余的字符可以是字母、数字(
0~9
)或下划线字符。任何其他的字符都标志着变量名的终止- 大小写敏感
- 给变量赋值时,等号周围不能有任何空白符
- 通常大写字符为系统默认变量。个人习惯
set
:查看所有变量(含环境变量与自定义变量),以及设置Shell
变量的新变量值
-a
:标示已修改的变量,以供输出至环境变量-b
:使被中止的后台程序立刻回报执行状态-e
:若指令传回值不等于0
,则立即退出Shell
-f
:取消使用通配符-h
:自动记录函数的所在位置-H Shell
:可利用!
加<指令编号>
的方式来执行history
中记录的指令-k
:指令所给的参数都会被视为此指令的环境变量-l
:记录for
循环的变量名称-m
:使用监视模式-n
:只读取指令,而不实际执行-p
:启动优先顺序模式-P
:启动-P
参数后,执行指令时,会以实际的文件或目录来取代符号连接-t
:执行完随后的指令,即退出Shell
-u
:当执行时使用到未定义过的变量,则显示错误信息-v
:显示Shell
所读取的输入值-x
:执行指令后,会先显示该指令及所下的参数set 11 22 33 44 echo $1 echo "$#" echo "$@" eval echo "\$$#" ------------------------- 11 4 11 22 33 44 44
declare/typeset [-aixrp]
变量
-a
:将变量定义成数组-i
:将变量定义成整数-x
:将变量定义成环境变量-r
:将变量定义成readonly
-p
:显示变量定义的方式和值+
:取消变量属性,但是+a
和+r
无效,无法删除数组和只读属性,可以使用unset
删除数组,但是unset
不能删除只读变量declare -i a=3 b=4 c c=a+b echo $c ------------------------- 7
declare -p a ------------------------- typeset -i a=3
local
关键字,用来在作用域内创建变量,出了作用域被销毁
Shell
中定义的变量,默认都是全局变量,即使在函数体中定义,外部也可以访问。如果想定义仅供函数体中可访问的本地变量,需要加上local
关键字function DoWork { local LG_CAT="LG_Cat" return 0 }
export
为Shell
变量或函数设置导出属性,成为环境变量。无法对未定义的函数添加导出属性。同时,重要的一点是,export
的效力仅及于该次登陆操作。注销或者重新开一个窗口,export
命令给出的环境变量都不存在了
-f
:代表[变量名称]
为函数名称-n
:删除变量的导出属性。变量实际上并未删除,只是不会输出到后续指令的执行环境中-p
:显示全部拥有导出属性的变量-pf
:显示全部拥有导出属性的函数-nf
:删除函数的导出属性--
:在它之后的选项无效
通配符
*
:匹配任意字符串,包括空字符串,不包含对/
字符的匹配。?
:匹配任意单个字符,不能匹配/
字符。[abc]
:匹配a
或者b
或者c
字符。[^abc]
:不匹配a
或者b
或者c
字符。[a-z]
:匹配26
个英文小写字符中任意一个echo * ------------------------- Cat Cat1 Cat1237
用
set
命令可以查看所有的变量
unset var
命令可以清除变量var
,var
相当于没有定义过
readonly var
可以把var
变为只读变量,定义之后不能对var
进行任何更改
函数的声明
函数的声明形式:
- 有
funtion
,可以不写()
。没有function
,必须写()
- 函数名和
{
之间必须有空格- 不得声明形式参数
- 必须在调用函数地方之前,声明函数
- 无法重载
- 后来的声明会覆盖之前的声明
- 没有返回值的函数,默认返回函数内最后一条指令的返回值。有返回值的函数,只能返回整数
- 需要获得函数值,只能通过
$?
获得。通过=
获得是空值有
funtion
,可以不写()
function 函数名 { 函数体 }
没有
function
,必须写()
函数名() { 函数体 }
完整写法
function 函数名() { 函数体 }
案例:
function DoWork { LG_CAT="LG_Cat" echo "logic" return 2 } DoWork echo "$?" echo `DoWork` echo "$?" ------------------------- logic 2 logic 0
可以将
Shell
中函数,看作是定义一个新的命令,因此各个输入参数直接用空格分隔。命令里面获得参数方法可以通过:$0...$n
得到。$0
代表函数本身
$#
:传入的参数的个数$*
:所有的位置参数(作为单个字符串)$@
:所有的位置参数(每个都作为独立的字符串)$?
:当前Shell
进程中,上一个命令的返回值,如果上一个命令成功执行则$?
的值为0
,否则为其他非零值$$
:当前Shell
进程的pid
$!
:后台运行的最后一个进程的pid
$-
:显示Shell
使用的当前选项$_
:之前命令的最后一个参数function DoWork { echo "特殊变量:\n \$#:$#\\n \$0:$0\\n \$1:$1\\n \$2:$2\\n \$*:$*\\n \$@:$@\\n \$$:$$\\n \$-:$-\\n \$_:$_ " return 2 } DoWork "Cat" "LGCat" ------------------------- 特殊变量: $#:2 $0:DoWork $1:Cat $2:LGCat $*:Cat LGCat $@:Cat LGCat $$:70639 $-:569JNRXZghiklm $_:LGCat
参数扩展
通过符号$
获得参数中存储的值
间接参数扩展:
${parameter-string}
:当parameter
未设置则替换成string
,不更改parameter
值。否则,不做处理${parameter=string}
:当parameter
未设置则替换成string
,更改parameter
值。否则,不做处理${parameter?string}
:当parameter
没有设置,则把string
输出到标准错误中。否则,不做处理${parameter+string}
:当parameter
为空的时替换成string
。否则,不做处理${!parameter}
:执行双重替换bash
,这意味着它接受parameter
的字符串并将其用作变量名。zsh
不支持使用
bash
:declare attr=a a=b echo ${!attr} ------------------------- b
兼容
bash
和zsh
:declare attr=a a=b eval echo "\$$attr" ------------------------- b
冒号后面跟
=
、+
、-
、?
(不能有空格):
${parameter:-string}
:当parameter
未设置或者为空则替换成string
,不更改parameter
值${parameter:=string}
:当parameter
未设置或者为空则替换成string
,更改parameter
值${parameter:?string}
:若变量parameter
不为空,则使用变量parameter
的值。若为空,则把string
输出到标准错误中,并从脚本中退出${parameter:+string}
:当parameter
不为空的时替换成string
。若为空时则不替换或者说是替换空值
子串扩展:
${parameter:offset}
和${parameter:offset:length}
- 从
offset
位置开始截取长度为length
的子串,如果没有提供length
,则是从offset
开始到结尾offset
可以是负值,且必须与冒号有间隔或者用()
包裹。开始位置是从字符串末尾开始算起,然后取长度为length
的子串。例如:-1
代表是从最后一个字符开始。parameter
是@
,也就是所有的位置参数时,offset
必须从1
开始
替换:
${parameter/pattern/string}
、${parameter//pattern/string}
、${parameter/pattern}
和${parameter//pattern}
- 大小写敏感
string
为空时,则相当于将匹配的子串删除。parameter
之后如果是/
,则只匹配遇到的第一个子串parameter
之后如果是//
,则匹配所有的子串
删除:
${parameter#pattern}
、${parameter##pattern}
、${parameter%pattern}
和${parameter%%pattern}
#
是去掉左边,%
是去掉右边- 单一符号是最小匹配,两个符号是最大匹配
参数长度:
${#parameter}
判断
多分支语句判断:
除最后一个分支外(这个分支可以是普通分支,也可以是
*)
分支),其它的每个分支都必须以;;
结尾。;;
代表一个分支的结束,不写的话会有语法错误最后一个分支可以写
;;
,也可以不写。因为无论如何,执行到esac
都会结束整个case in
语句语法:
case $变量 in "第一个变量内容") 程序 ;; #结束 *) # 用来托底,没有匹配到数据 ;; esac
案例:
case $LG_CAT in "cat") echo "名字为cat" ;; "LGCat") echo "名字为LGCat" ;; *) echo "未找到名字" ;; esac
[]
:判断符号,两个等号和一个等号,效果类似
- 中括号里面的每个组件都需要空格分隔
- 中括号的变量,使用双引号
- 中括号的常量,使用单引号或双引号
一个条件判断:
if [ condation ]; then 成立 else 不成立 fi
多条件判断:
if [ condation ]; then 成立 elif [ condation ]; then 成立 else 不成立 fi
案例:
function DoWork { return 2 } DoWork if [[ $? -ne 0 ]]; then echo "函数调用出错" fi
LG_CAT=Cat if [[ "${LG_CAT}" == "Cat" ]]; then echo "${LG_CAT}" fi
test
命令:
test n1 -eq n2
:
-eq
:相等-ne
:不等-gt
:大于-lt
:小于-ge
:大于等于-le
:小于等于字符串判断:
-z string
:判断string
是否为为空,为空则为true
-n string
:判断string
是否非空,为空则为false
string1 = string2
:字符串是否相等,相等为true
string1 != string2
:字符串是否不等,相等为false
多重条件判断:
-a
:两个条件同时成立,为true
-o
:两个条件任何一个成立,为true
!
:反向文件类型判断:
-e
:文件名是否存在-f
:该文件名是否存在且是否为文件-d
:该名称是否存在且为目录-L
:该名称是否存在且是否为链接文件文件权限检测:
-r
:是否存在是否有可读权限-w
:是否存在是否有可写权限-x
:是否存在是否有可执行权限-s
:是否存在且为非空白文件两文件比较
-nt
:文件1
是否比文件2
新-ot
:文件1
是否比文件2
旧-ef
:文件1
和文件2
是否为同一个文件if [[ -e "./test与判断.sh" ]]; then echo "文件存在" else echo "文件不存在" fi
判断中没有函数体会报错,可以使用
:
占位if [ true ]; then : fi
循环
当条件成立,就进行循环:
while [ condation ] #判断条件 do #循环开始 程序 done #循环结束
案例:
COUNTER=0 while [ $COUNTER -lt 5 ] do echo $COUNTER COUNTER=`expr $COUNTER + 1` done
当条件成立,就终止循环:
until [ condation ] #判断条件 do #循环开始 程序 done #循环结束
案例:
a=0 until [ ! $a -lt 10 ] do echo $a a=`expr $a + 1` done
按照指定次数循环:
for var in con1 con2 con3 ... do 程序 done
for (( 初始值; 限制值; 执行步长 )) do 程序 done
案例:
for loop in 1 2 3 4 5 do echo "The value is: $loop" done
for (( i = 1; i <= 5; i++ )) do echo "i=$i" done
总结:
如何学习
Shell
- 学语法
- 看脚本
- 抄
常用命令参考
Shell
有两种方式
- 交互式(
Interactive
)- 批处理(
Batch
)方式
.bashrc
、.bash_profile
和.zshrc
作用与区别
.bashrc
、.bash_profile
是给bash
来使用的.zshrc
是给zsh
来使用的交互式登录和非登录
Shell
- 交互式登录
Shell
:bash
会先查找/etc/profile
文件,如果该文件存在,它将运行文件中列出的命令。然后搜索~/.bash_profile
、~/.bash_login
以及~/.profile
文件,顺序读取- 非登录
Shell
:读取~/.bashrc
三种运行方式
sh
:生成一个子Shell
进程source
:不会生成新的进程exec
:用command
进程替换当前Shell
进程执行权限
sh/bash/zsh
:不需要执行权限./script.sh
:需要执行权限source script.sh
:不需要执行权限exec
:需要执行权限