iOS高级强化--012:Shell入门教程

ShellUnix Shell)是一种命令行解释器,是Unix操作系统下最传统的人机接口。

Shell脚本是解释执行的,不需要编译,和大部分的编程语言很相似,也有基本的变量和流程控制语句。

平时使用Shell有两种方式:

  • 输入命令,执行,这种方式称为交互式(Interactive);
  • 批处理(Batch)方式,用户事先写好Shell脚本文件,然后顺序执行脚本中的命令。

第一个Shell环境是Thompson Shell,在贝尔实验室开发并于1971年发布

现代Shell最突出的祖先是被称为shBourneShell,这是以在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 Falstad1990年开发的。它是一个BourneShell,它使用bashprevious 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_profilebash作为交互式登录Shell调用时被读取并执行,而.bashrc对于交互式非登录Shell被执行

大多数Linux/Unix发行版都使用~/.profile代替~/.bash_profile~/.profile所有Shell都读取该文件,而~/.bash_profile只有bash才会读取该文件

~/.zshrczsh的交互式Shell的用户配置

对于bash,它们的工作方式如下:

读取适当的内容,执行A,然后执行B,然后执行C,依此类推。B1B2B3表示仅执行找到的那些文件中的第一个。

+----------------+-----------+-----------+------+
|                |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调用,最后在~/.zshrcsource调用~/.bash_profile
编译器与解释器

编译型语言需要编译处理:

  • 源代码(source code)-> 预处理器(preprocessor)-> 编译器(compiler)-> 目标代码(object code)-> 链接器(Linker)-> 可执行程序(executables

解释型语言需要解释器处理:

  • 源代码(source code)-> 解释器(interpreter
Shell初探

ShebangHashbang):一个由井号和叹号构成的字符序列#!出现在文本文件的第一行的前两个字符

#!/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领养,使其父进程ID1

因为所有的进程均有父进程,事实上,所有进程的创建,都可视为子进程创建过程。unix操作系统进程的创建,大致都是进行fork + exec类系统调用

理解子进程的创建执行,需要至少细分到二个步骤,包括:

  • 通过fork创建子进程环境,
  • 通过exec加载并执行进程代码。

Shell子进程(以下均称subshell):顾名思义,就是在当前的Shell下,去打开另一个新Shell

PS1提示符定义:提示符设置[\u@\h \w \A #\#]\$

  • \u:用户账号名称
  • \h:主机名缩写
  • \w:完整工作路径
  • \A24小时时间
  • \#:第几个命令
  • \$:提示符,如果是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:打印aliaskeywordfunctionbuilt-infile这五种类型
  • -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 

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 > filecat >> file标准输入

cat > Cat1237
111
222
  • 使用> file等待输入,将输入的内容保存到file中,使用control + d结束输入。
  • >覆盖
  • >>累加

使用<<结束输入,后接一个结束输入符

cat > Cat1237 << "cat"
111
222
cat
  • cat作为结结束提示符
  • 按照日常规范建议使用eof表示结束提示符

单箭头和双箭头的区别:

对于输出:

  • 单箭头:当指定的文件不存在时,创建新文件写入数据;当文件存在时,清空原文件的内容写入数据
  • 双箭头:当指定的文件不存在时,创建新文件写入数据;当文件存在时,在原件内容的最后追加写入数据

对于输入:

  • 单箭头:将文件或其他输入作为标准输入(<的左边必须是命令,<右边的输入内容作为命令的输入)
  • 双箭头:结束输入

将标准输出和错误输出重定向到一个文件上:

stdinstderr无序输出到one.log

grep "Cat"  file.log > one.log 2>one.log

stdinstderr序有输出到one.log

grep "Cat"  file.log > one.log 2>&1
  • 首先stdin重定向到one.log,然后使用2>&1表示stderr重定向至stdinstderrstdin之后输入到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默认的数值运算是整数类型。所以若要进行数学运算,必须使用一些命令,例如:declareexpr、双括号等

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
}

exportShell变量或函数设置导出属性,成为环境变量。无法对未定义的函数添加导出属性。同时,重要的一点是,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命令可以清除变量varvar相当于没有定义过
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

兼容bashzsh

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

  • 学语法
  • 看脚本

常用命令参考

  • Linux命令
  • Linux教程
  • Linux命令搜索

Shell有两种方式

  • 交互式(Interactive
  • 批处理(Batch)方式

.bashrc.bash_profile.zshrc作用与区别

  • .bashrc.bash_profile是给bash来使用的
  • .zshrc是给zsh来使用的

交互式登录和非登录Shell

  • 交互式登录Shellbash会先查找/etc/profile文件,如果该文件存在,它将运行文件中列出的命令。然后搜索~/.bash_profile~/.bash_login以及~/.profile文件,顺序读取
  • 非登录Shell:读取~/.bashrc

三种运行方式

  • sh:生成一个子Shell进程
  • source:不会生成新的进程
  • exec:用command进程替换当前Shell进程

执行权限

  • sh/bash/zsh:不需要执行权限
  • ./script.sh:需要执行权限
  • source script.sh:不需要执行权限
  • exec:需要执行权限

你可能感兴趣的:(iOS高级强化--012:Shell入门教程)