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选项启动时,将调用登录Shellzsh --login当
bash作为交互式登录Shell调用时,bash会先查找/etc/profile文件,如果该文件存在,它将运行文件中列出的命令,然后搜索~/.bash_profile、~/.bash_login以及~/.profile文件,顺序读取当
bash作为交互式非登录Shell调用时,会读取~/.bashrccat /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/lstype -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 -- $FILEgrep 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 ------------------------- 7declare -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 "函数调用出错" fiLG_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是否非空,为空则为falsestring1 = string2:字符串是否相等,相等为truestring1 != 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 程序 donefor (( 初始值; 限制值; 执行步长 )) do 程序 done案例:
for loop in 1 2 3 4 5 do echo "The value is: $loop" donefor (( 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:需要执行权限
