[Shell] [笔记]UNIX/Linux/OSX中的Shell编程

版权声明: https://blog.csdn.net/Blanchedingding/article/details/85564354

UNIX/Linux/OSX中的Shell编程(第4版)

Stephen G. Kochan Patrick Wood著

基础

1. 使用文件

  • UNIX系统只识别三种基本类型文件:普通文件(系统中包含数据、文本、程序指令或其它内容的文件)、目录文件和特殊文件(对UNIX有特殊意义,通常和某种形式的I/O相关联)

  • 文件名字符数不能超过255个

2. 使用目录

  • 登录系统后,自动处于个人主目录中
  • 以"/“起始的路径名为绝对路径名,不以”/"开头的路径为相对路径,相对于当前工作目录
  • "…“指向当前目录的上一级目录,”."引用当前目录

3. 文件名替换

*

echo *
#部分替换,限制匹配
echo chap*

? 或 []

匹配单个字符

echo a?
# 列出以小写字母开头且不易数字结尾的所有文件
ls [a-z]*[!0-9]

[!chars]

不在chars中的任意单个字符

4. 文件名中的空格

两种解决方法:

  • 放在引号中
cat "my test document"
  • 使用反斜杠转义
cat my\ test\ document

5. 标准输入/输出和I/O重定向

  • Ctrl+d终结标准输入
  • 输出重定向
# 内容覆盖
who > users

# 追加形式
echo line 2 >> users
  • 输入重定向
wc -l < users

I/O重定向

写法 含义
< file 标准输入重定向到file
> file 标准输出重定向到file
>| file 标准输出重定向到file,file内容清空
>> file 追加
<< word 标准输入重定向到随后的行中,知道某行只包含了word
<& digit 标准输入重定向到与文件描述符digit相关联的文件
>& digit 标准输出重定向到与文件描述符digit相关联的文件
<&- 关闭标准输入
>&- 关闭标准输出
<> file 打开file进行读取和写入
  • file上不会执行文件名替换
  • 行内输入重定向
    • 为了不让Shell解释输入行中的内容,可以在文档结尾单词前使用反斜线:
      $ cat <<\FOOBAR
      > \\\
      > `date`
      > $HOME
      > FOOBAR
      \\\
      `date`
      $HOME
      
    • 如果>>后面的第一个字是连接符-,那么输入中的其拿到制表符都会被Shell删除

6. 标准错误

  • 默认同终端或终端程序相关联

  • 即使标准输出被重定向到文件中,错误小心仍然显示到终端

  • command 2> file 将标准错误重定向到文件中

  • 另一种写法用于确保所有的错误信息都出现在终端,哪怕监本已经将其输出重定向了文件或管道:echo "Error message" 1>&2(将应该输出到文件描述符#1的错误信息重定向到文件描述符#2)

7. 管道

过滤器:从标准输入读取数据,将结果写入标准输出

# wc为过滤器,cat/sort都可以作为过滤器
# who/date/cd/pwd/echo/rm/mv/cp都不算
who | wc -l

8. Shell

  • 只要系统允许用户登录,UNIX系统(init程序)就会在每个终端端口自动启动一个getty程序,getty是一个设备驱动程序,能够让login程序在其所分配的终端上显示login:等待用户输入

  • login比对/etc/passwd中的条目验证登录名密码,/etc/passwd中有验证成功后要启动的程序(每行最后一个冒号后面),没有就默认使用标准Shell,即/bin/sh

  • 登录Shell会在系统中查找并读取两个特殊文件:/etc/profile会检查新邮件、默认的文件创建掩码、建立默认PATH及其它管理员希望登录完成的工作;.profile位于用户主目录下,其对环境做出的修改会一直持续到登出Shell

  • Shell的职责:解释型编程语言、程序执行、变量及文件名替换、I/O重定向、管道、环境控制

    • Shell首先进行变量和文件名替换,查找文件名替换字符*、?或[…]

    • Shell在磁盘搜索命令之前,会先判断是否为内建命令(cd/pwd/echo)

    • I/O重定向:wc程序在wc -l < users中只接收了-l一个参数,他会转而去统计标准输入的内容,Shell会将其的标准输入重定向为文件users

    • 管道:将上一个命令的标准输出连接到下一个命令的标准输入,然后执行两者

    • 环境控制:能够定制个人环境

    • 解释型:Shell有自己的内建语言,这种语言是解释型的,Shell会分析所遇到的每一条语句,然后执行所发现的有效的命令

  • Shell先进行管道和I/O重定向,然后是变量替换、命令替换,再是文件名替换,接着将命令行解析成参数,Shell会从命令行中删除空格、制表符和换行符(空白字符),然后切分成参数交给所请求的命令

  • Shell的命令搜索次序

    • 首先检查命令是否为保留字(如for或do)
    • 如果不是保留字,也没有引用,接着检查别名列表,如果在其中找到匹配的别名,就执行替换操作,**如果别名定义是以空格结尾,Shell还会尝试对下一个单词执行别名替换。**针对替换后的最终结果,再次检查保留字列表,如果不是保留字,继续进行第3步
    • 针对命令名检查函数列表,如果找到,执行其中的同名函数
    • 检查是否为内建命令(如cd和pwd)
    • 最后,搜索PATH来定位命令
    • 如果还没找到,输出错误信息command not found

9. 正则表达式

记法 含义 例子
. 匹配任意字符(Shell则认为?能匹配任意单个字符) a…
^ 匹配行首 ^wood
$ 匹配行尾 x$
* 重复之前的正则表达式零次或多次 xx* (一个或多个连续x)
+ 重复之前的正则表达式一次或多次 xx+ (两个或多个连续x)
[chars] 匹配chars中的任意字符 [a-z] (小写字母)
[^chars] 匹配不在chars中的任意字符 [^0-9] (非数字字符)
\{min, max\} 重复之前的正则表达式至少min次,至多max次 [0-9]\{3, 9\} (连续3-9个数字)
\(…\) 将括号中保存下来的字符保存到接下来的寄存器中(1-9) ^\(.\)\1 (行首前两个相同的字符)

10. 常用字符的八进制形式

字符 八进制值
响铃Bell 7
退格Backspace 10
制表符Tab 11
回车换行Newline 12
换行Linefeed 12
换页Formfeed 14
回车Carriage Return 15
取消Escape 33

11. 退出状态

  • 退出码为0表示程序运行成功
  • 对于管道而言,退出状态对应的是管道中的最后一个命令
  • 对于文件复制命令cp,可能的错误情况是1(文件没找到)、2(文件不可读)、3(目标目录没找到)、4(目标目录不可写)、5(一般性错误)

12. 波浪符替换

  • Shell会检查命令行中的每一个单词及变量是否以未引用的开头,如果是,将单词或变量中其余直到/的部分视为登录名,并在系统文件/etc/passwd中查找该用户。如果存在,使用其主目录替换以及登录名。如果不存在,不做任何修改
  • 单独的或/之后的会被HOME变量替换

Tips

  1. 一行中输入多个命令,使用;分隔

  2. 向后台发送命令:

# 返回[job number] PID
$ sort bigdata > out &
[1] 1258
  1. chmod +x <file> 使文件具有可执行权限

  2. 没有参数的echo会产生一个空行

  3. 不想看到命令结果,可以将输出重定向到系统的垃圾桶:/dev/null,任何写入这个文件的东西都会消失!

    要强制写入或从终端读取,可以利用/dev/tty,该文件总是指向终端程序

  4. 调试:sh -x跟踪执行过程,这启动了一个-x选项的新shell执行指定程序,命令在执行的同时打印到终端,前面加了一个+。

  5. Shell的两个特殊操作符:&&||,根据之前命令成功与否来执行后续的命令

  6. 如果文件中的第一行的前两个字符是#!,那么该行余下的部分指定了该文件的解释器:#!/bin/bash

命令

1. date

显示日期和时间

$ date
Thu Dec 27 19:31:25 CST 2018

2. who

获取当前已登录到系统中所有用户信息
用户ID、用户所在的tty编号(UNIX系统为用户所在终端或网络设备分配的唯一标识数字)、登录时间

$ who
root     pts/0        2018-12-27 19:41 (10.131.253.92)

[root@CENTOS-MINION-2 ~]# who am i
root     pts/0        2018-12-27 19:41 (10.131.253.92)

3. echo

  • 回显字符,压缩单词间多余的空白字符;
$ echo one    two       three
one two three
  • 会自动在最后一个参数后面加上一个用于终止的换行符,可以在最后加上转义字符\c不输出换行符(\c是由echo解释的,而非Shell,必须将其引用起来交给echo),有的系统不会显示这些echo转义字符

echo命令的转义字符

字符 输出
\b 退格
\c 忽略输出中最后的换行符
\f 换页
\n 回车换行
\r 回车
\t 制表符
\\ 反斜线
\0nnn ASCII值为nnn的字符,nnn是1-3位的八进制数

4. ls

列举文件;

-C强制以多列形式输出

-l 输出文件详细信息:

  • total:文件占用的存储块(1024字节)数
  • 文件类型:d为目录,-为文件,特殊文件是b/c/l/p
  • 访问权限:文件所有者、与文件所有者同组的其他用户、系统其他用户
  • 链接数
  • 文件所有者
  • 文件所属组
  • 文件大小
  • 文件最后的修改时间
$ ls
anaconda-ks.cfg
apache-maven-3.5.2-bin.tar.gz
docker-ce-17.03.2.ce-1.el7.centos.x86_64.rpm
docker-ce-selinux-17.03.2.ce-1.el7.centos.noarch.rpm
get-pip.py
go1.9.2.linux-amd64.tar.gz
jdk-8u131-linux-x64.tar.gz
nohup.out
openscap_data
privoxy-3.0.26-stable
privoxy-3.0.26-stable-src.tar.gz

$ls -l
total 314124
-rw-------.  1 root root      1089 Aug 10  2016 anaconda-ks.cfg
-rw-r--r--.  1 root root   8738691 Apr 12  2018 apache-maven-3.5.2-bin.tar.gz
-rw-r--r--.  1 root root  19529520 Apr 12  2018 docker-ce-17.03.2.ce-1.el7.cento                                                                                        s.x86_64.rpm
-rw-r--r--.  1 root root     29108 Apr 12  2018 docker-ce-selinux-17.03.2.ce-1.e                                                                                        l7.centos.noarch.rpm
-rw-r--r--.  1 root root   1780465 Apr 13  2018 get-pip.py
-rw-r--r--.  1 root root 104247844 Apr 12  2018 go1.9.2.linux-amd64.tar.gz
-rw-r--r--.  1 root root 185540433 Apr 12  2018 jdk-8u131-linux-x64.tar.gz
-rw-------.  1 root root     28908 Nov 30 11:01 nohup.out
drwxr-xr-x.  2 root root        39 Aug 10  2016 openscap_data
drwxr-xr-x. 10 root root      4096 Apr 13  2018 privoxy-3.0.26-stable
-rw-r--r--.  1 root root   1741772 Aug 27  2016 privoxy-3.0.26-stable-src.tar.gz

5. cat

(concatenate)显示文件内容

$cat dingtest
if (( i == 100 ))
then
  echo "$i == 100"
else
  echo "$i != 100"
fi

如果没有指定参数,$*为空,cat也接受不到任何参数,这会使它从标准输入中读取

cat $* |
while read line
do
    echo "$line"
done

6. wc

统计行数、单词数、字符数;命令选项必须出现在文件名参数之前

$wc dingtest
 6 17 69 dingtest
 
$wc -l dingtest
6 dingtest

$wc -w dingtest
17 dingtest

$wc -c dingtest
69 dingtest

7. cp

复制文件,saved_names如果存在会被覆盖

$cp names saved_names

# 将文件collect从目录../programs复制到当前目录中
$cp ../programs/collect .

8. mv

文件重命名,hold_it如果存在会被覆盖;
在目录间移动文件

$ mv saved_names hold_it

#将三个文件移动到../misc目录中
$ mv wb collect mon ../misc

9. rm

删除文件

$ rm hold_it
  • -f忽略不存在的文件,强制删除,不给出提示。

  • -r 指示rm将参数中列出的全部目录和子目录均递归地删除。

  • -i进行交互式删除。因为一旦文件被删除,它是不能被恢复的。为了防止这种情况的发生,可以使用“i”选项来逐个确认要删除的文件。如果用户输入“y”,文件将被删除。

  • rm -r <dir>删除指定目录和其中的所有文件(如果没有使用- r选项,则rm不会删除目录。 )

  • rm -rf *删除当前目录下的所有文件

10. pwd

显示工作目录

$pwd
/ding

11. cd

更改目录,参数-表示上一个目录,无参数表示回到HOME目录

12. mkdir

创建目录

13. rmdir

删除目录,要先删除目录中包含的所有文件,否则会报错;

14. ln

文件链接,一个文件有连个不同的名字,不会占用两倍磁盘空间;

对于普通链接,被链接的文件必须与链接文件处于同一个文件系统中,否则会报错;

可以随意删除两个连接文件中的任何一个,另外一个不会因此消失

$ ln wb writeback
$ ls -l
...
-rwxr-xr-x 2 steve DP3822 89 JUL 20 15:22 wb
-rwxr-xr-x 2 steve DP3822 89 JUL 20 15:22 writeback

要在不同文件系统的文件之间创建链接,使用-s选项,这叫做符号链接(ls -l文件类型为l),指向原始文件;

例子中文件大小为15,内容其实就是字符串/users/steve/wb;

如果原始文件被删除,符号链接无效,但符号链接本身不会删除->悬挂符号链接;

  • -Ll获得符号链接所指向文件的详细信息
$ ln -s /users/steve/wb ./symwb
$ ls -l
...
lrwxr-xr-x 1 pat DP3822 15 JUL 20 15:22 symb -> /users/steve/wb

$ ls -Ll
...
-rwxr-xr-x 2 steve DP3725 15 JUL 20 13:30 wb

为特定目标目录中的多个文件创建链接:

ln <files> <directory>

15. ps

给出系统中所运行进程的信息:PID、TTY、进程所使用的计算机时间、进程名称;

  • -f打印更多信息:PPID(父进程ID)、进程开始时间
$ ps
  PID TTY          TIME CMD
 4581 pts/0    00:00:00 bash
 9255 pts/0    00:00:00 ps
 
$ ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root      4581  3875  0 19:56 pts/0    00:00:00 -bash
root     10717  4581  0 21:38 pts/0    00:00:00 ps -f

16. cut

从每行内提取字段:cut -cchars file

  • -d指定字段分隔符;
  • -f指定待提取的字段
# 提取前8个字符
$ who | cut -c1-8 

# 提取前8个字符,然后提取第18个到行尾的字符
$ who | cut -c1-8,18-

# 从/etc/passwd文件中提取每行的由:分割的第1和6字段
$ cut -d: -f1,6 /etc/passwd 

17. paste

将文件中每行合并在一起,由制表符分隔

  • -d指定分隔符;
  • -s将同一个文件中的所有行合并到一起,默认制表符分隔
$ paste names numbers
Tony    123456
Emanuel 55555

# 粘贴ls命令的输出(paste将来自标准输入`-`的所有行进行合并),使用空格作为分隔符
$ ls | paste -d' ' -s -

18. tr

过滤器,转换标准输入中的字符;从标准输入获得输入,结果写入标准输出,不改动原始文件

  • -s压缩多次连续出现的字符;
  • -d删除输入流中的个别字符
# 将:换成制表符,11为制表符的八进制值
tr : '\11'

# 小写换成大写,加引号避免Shell将其解释为模式
tr '[a-z]' '[A-Z]' < intro

# 大写转小写,小写转大写
tr '[a-zA-Z]' '[A-Za-z]' 

# A-M转成N-Z,N-Z转成A-M
tr '[A-Z]' '[N-ZA-M]' 

# 所有:转成制表符,使用单个制表符替代多个制表符
tr -s ':' '\11'

# 删除换页符
tr -d '\14' 

19. grep

在一个或多个文件中搜索指定的模式

  • -i忽略大小写;
  • -v获得不匹配的行;
  • -l得到匹配指定模式的行所在的文件;
  • -n显示行号
# 在当前目录的所有文件中搜索单词shell
grep shell *

# *放在单引号中避免Shell误解
grep '*' stars

20. sort

字母顺序排序,特殊字符按内部编码排序,空格32,双引号34

  • -u消除重复行
  • -r逆序排列
  • -o指定输出文件
  • -n将行中第一个字段视为数字,对应的数据进行算数排序
  • sort -k2 <file>从第2个字段开始排序
  • -t后的字符被视为分隔符,sort -k3n -t: /etc/passwd对第3个以冒号分隔的字段进行算数排序

21. uniq

uniq in_file (out_file)查找或删除重复行(连续出现的重复行,所以一般需要先sort才能被判断为重复),可做过滤器

  • -d只把重复的行写入输出
  • -c统计出现的次数
sort names | uniq

sort names |uniq -d

# 找出数据文件中词频最高的单词
tr '[A-Z]' '[a-z]' datafile | sort | uniq -c | head

22. expr

数学等式解算器,操作数和操作符之间必须用空格分隔!!!

  • :操作符,针对出现在第二个操作数中的正则表达式匹配第一个操作数中的字符,默认返回匹配到的字符数
$ expr $i + 1
1

$  expr "$file" : ".*"
0

23. shift

向左移动位置参数,同时,$#的值也会自动减1;

  • 如果$#已经为0的情况下使用shift,会发出错误信息:prog: shift: bad numberprog是执行不当的shift命令的程序名)

  • shift 3:一次移动3个位置

24. test

测试单个或多个条件

  • test命令的所有操作数和操作符必须是独立的,用空白字符分隔的!

  • 将test的参数放在双引号中是一种良好的编程实践

# test命令的所有操作数和操作符必须是独立的,用空白字符分隔的
4 test "$name" = julio

$ test 1 = 1
$ echo $?
0

# =的优先级高于-z,所以test会将命令作为等量关系测试来处理,希望在=之后还有一个参数,所以报了错
$ symbol==
$ test -z "$symbol"
$ echo $?
1
# 下面的写法可以防止这种情况发生
$ test X"$symbol" = X
  • test的另一种格式:[ express ]

    • express两边与[]之间必须有空格
    $ [ -z "" ]
    $ echo $?
    0
    
  • test字符串操作符

操作符 如果满足下列条件,则退出码为0
string1 = string2 string1等于string2
string1 != string2 string1不等于string2
string string不为空
-n string string不为空
-z string string为空
  • test整数操作符
操作符 如果满足下列条件,则退出码为0
int1 -eq int2 int1等于int2
int1 -ge int2 int1大于等于int2
int1 -gt int2 int1大于int2
int1 -le int2 int1小于等于int2
int1 -lt int2 int1小于int2
int1 -ne int2 int1不等于int2

在使用整数操作符时,将变量的值视为整数的是test命令,而不是Shell

  • test文件操作符
操作符 如果满足下列条件,则退出码为0
-d file file是一个目录
-e file file存在
-f file file是一个普通文件
-r file 可由进程读取
-s file 不是空文件
-w file 可由进程写入
-x file 可执行
-L file 是一个符号链接
  • 逻辑否定操作符![ ! -r /users/steve/phonebook ]

  • 逻辑“与”操作符-a[ ! -f "$file" -a $(who > $file) ],(如果-f测试没有通过,就不会执行who命令)

  • 逻辑“或”操作符-o优先级比-a

  • 括号:改变求值顺序,括号两边必须有空格,且要将括号本身引用起来,因为test要求条件语句的每一个元素都是独立的参数,且括号对Shell有特殊含义(新开一个子Shell)

[ \( "$count" -ge 0 \) -a \( "$count" -lt 10 \) ]

25. exit

-exit n:返回退出码n

26. 空命令:

什么都不干,可以用在谋改革判断分支中,因为每个匹配的语句分支都需要对应的命令

27. sleep

sleep n:挂起程序执行n秒

28. nohup

希望某个程序在登出后继续运行,使用nohup命名运行该程序

29. break

退出循环,break n退出第n层内循环

for file
do 
    ...
    while [ "$count" -lt 10 ]
    do
        ...
        if [ -n "$error" ]
        then
            # 当error不为空,退出while和for循环
            break 2
        fi
    done
    ..
done

30. continue

跳过当前迭代中剩下的命令,continue n跳过最内侧的n个循环中的命令,继续往下执行

31. getopts

内建命令,专门用来在循环中执行处理命令行参数

getopts options variable

  • options中单个字符表示选项,如果选项后还需要参数,字符后要加冒号。mt:表示程序支持-m``-t选项,其中-t还需要一个参数

  • 如果减号后的字符没有在options中列出,会在返回为0的退出状态之前将问号保存在variable中,向标准错误写入错误信息,告诉用户指定的选项有问题

  • 如果getopts没有在选项后找到要求的参数,会将问号保存到变量中并向标准错误输出错误信息。否则,就将选项字符保存在变量中,把用户指定的参数放在一个叫做OPTARG的特殊变量中

  • 特殊变量OPTIND初始值为1,随后每当getopts返回时都会被更新为下一个要处理的命令行参数的序号

while getopts mt: option
do
    case "$option" in
        m) mailopt=TRUE;;
        t) interval=$OPTARG;;
        \?) exit 1;;
    esac
done

32. read

  • read variables从标准输入中读取一行,将第一个单词分配给variables中第一个变量,将第二个单词分配给第二个变量,以此类推。

  • 碰到文件结尾,或用户键入ctrl+dread会返回为0的退出状态码

cat $* |
while read line
do
    echo "$line"
done
  • 如果行中包含反斜线或前导空白字符,反斜线会由Shell解释,前导空白字符会从读入的行中删除。read -r避免解释反斜线。(可以修改变量IFS保留前导空格)

33. basename

剥离参数的所有目录部分,得到其基础文件名

34. printf

  • 格式化输出信息,格式化字符串中的每个百分号(格式规范)都有一个相应的参数,除了特殊规范%%,他会显示一个百分号。

  • 不会在尾部加一个换行符,能够理解转义序列

printf的格式规范字符

字符 功能
%d 整数
%u 无符号整数
%o 八进制整数
%x 十六进制整数,使用a-f
%X 十六进制整数,使用A-F
%c 单个字符
%s 字符串字面量
%b 包含转义字符的字符串
%% 百分号

%s不处理转义字符,%b强制解释转义字符

  • 转换规范:%[flags] [width] [.precision] type
$ printf "%+d\n%+d\n" 10 -10
+10
-10

$ printf "%-15.15s\n" "this is more than 15 chars long."
this is more th

$ printf "%*s%*.*s\n" 12 "test one" 10 2 "test two"
    test one        te

printf的格式规范修饰符

修饰符 含义
Flags
- 左对齐值
+ 在整数前加上+或-
(space) 在正数前加上空格,负数前面加上-,对齐
# 在八进制数前加上0,在十六进制数前加上0x或0X
width 字段最小宽度;*表示使用下一个参数作为宽度
precision 显示整数时使用的最小位数,值左侧用0填充;显示字符串时使用的最大字符数;*表示使用下一个参数作为精度

35. export

导出变量,只要子Shell已启动,导出的变量都会被复制到子Shell,而局部变量不会

  • export -p得到列表,包含了导出的变量和值,这些是在用户登录后由登录Shell所导出的
$ export -p
declare -x CLASSPATH=".:/usr/local/jdk1.8.0_131//jre/lib/rt.jar:/usr/local/jdk1.8.0_131//lib/dt.jar:/usr/local/jdk1.8.0_131//lib/tools.jar"
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/root"
declare -x HOSTNAME="CENTOS-MINION-2"
declare -x JAVA_HOME="/usr/local/jdk1.8.0_131/"
...

36. .(dot)命令

. file:在当前Shell中执行file的内容

37. exec

  • exec program:使用新程序替换现有的程序,不会有进程处于挂起状态,由于UNIX系统执行进程的方式,exec所替换的程序启动时间也更快

  • 还可以用来关闭标准输入,然后使用其他你想要读取的文件重新打开它

exec < infile
exec > report

#将标准输入重新分配回终端
exec < /dev/tty

38. set

  • Shell的内建命令,设置各种Shell选项、重新为位置参数$1、$2…赋值
$ set one two three
$ echo $1:$2:$3
one:two:three
  • set -x打开跟踪模式;set +x关闭跟踪模式;

  • 无参数的set会输出一个按照字母顺序排序的变量列表,是存在于当前环境中的局部变量或导出变量

  • --:告诉set对于后续出现的连接符或参数形式的单词,均不视为其选项;是考虑到可能会出现以-起始的行、全部是空白字符或者空行

  • set -o mode:启动行编辑模式,mode可以是vi或emacs

39. readonly

  • 指定在程序随后的执行过程中,值都不会发生改变的那些变量,如:readonly PATH HOME,如果之后再视图给这两个变量赋值,会输出错误信息

  • 变量的只读属性不会传给子Shell

  • 只要将变量设为只读,就没有“后悔药”

40. unset

从环境中删除谋某个变量;不能对只读变量、IFS/MAILCHECK/PATH/PS1/PS2使用unset,

$ x=100
$ echo $x
100
$ unset x 
$ echo $x
$
  • -f:删除函数

41. eval

  • eval args:使得Shell对args求值,然后执行求值结果;常用于从变量中构造命令行,实际上可以实现对命令行的二次扫描;
  • 如果变量中包含了任何必须由Shell解释的字符,就必须用到eval
$ cat last
eval echo \$$#
$ last one two three four
four
#得到最后一个文件
$ last *
zoo_report
  • 还可以用来创建指向变量的“指针”
$ x=100
$ ptrx=x
$ eval echo \$$ptrx
100
$ eval $ptrx=50
$ echo $x
50

42. wait

  • wait job:暂停执行,直到job所标识的进程运行结束,job可以是进程ID或作业ID。如果没有提供job,Shell会等待所有子进程结束

43. trap

  • trap commands signals:告诉Shell只要接收到signals中列出的信号,就执行commands(如果不止一个命令,要全部放入引号中),信号以名称或编号形式指定
#exit是必须的,否则程序会一直停留在接收到信号时的执行位置上
trap "rm $WORKDIR/work$$ $WORKDIR/dataout$$; exit" INT

信号编号与名称

信号# 信号名称 产生原因
0 EXIT 退出Shell
1 HUP 挂起
2 INT 中断(如按下DELETE键或ctrl+c)
3 QUIT 退出
6 ABRP 中止
9 KILL 销毁进程
14 ALRM 超时
15 TERM 软件终止信号(默认由kill发送)
  • 不使用参数,trap会打印出当前的信号处理方式

  • 如果第一个参数是空串:trap "" signals,当Shell接收到signals指定的信号时,会将其忽略

  • 如果忽略了某个信号,子Shell也会忽略;如果指定了某个信号的处理程序,子Shell自动采用默认处理方式,而不是指定的处理程序

  • 使用trap signals,会将signals中列出的信号处理方式恢复成默认行为

  • Shell在执行trap时扫描命令行,在接收到信号列表中的信号时还会再扫描一遍:

trap "echo $count lines processed >> $LOGFILE; exit" HUP INT TERM

#为了防止count被提前替换,将命令放入单引号中
trap 'echo $count lines processed >> $LOGFILE; exit' HUP INT TERM

44. return

return n:n为函数的返回状态,可以用变量$?访问它

45. type

接受一个或多个命令作为参数,告诉你这些命令是什么类型

$ type echo
echo is a shell builtin

$ type ls
ls is aliased to `ls --color=auto'

$ type cat
cat is /usr/bin/cat

$ type nu
nu is a function

46. history

访问命令历史记录

$ history
....

$  history 5
  885  type echo
  886  type ls
  887  type cat
  888  history
  889  history 5

47. fc

  • 为历史记录中的若干命令启动一个编辑器或将历史命令列表写入终端。后一种,使用-l指定命令列表

  • -n忽略命令编号

  • fc -s old=new first执行选中的命令,无需事先编辑

fc -l 50-53
#最近20条命令写入到标准输出
fc -n -l -20
#将最近的sed命令送入vi中编辑
fc -e vi sed
#将编号104的命令中的abc替换成def,然后重新执行
fc -s abc=def 104

48. r (Korn Shell才有)

重新显示上一条命令并立即执行

#运行上一条cat命令
$ r cat
cat doc/planA
#将cat命令中第一次出现的planA替换成planB
$ r cat planA=planB

49. typeset (Korn Shell才有)

  • -i:声明整数类型
  • 整数表达式可以分配给整数类型的变量,甚至不需要((…))
$ typeset -i i
$ i=hello
ksh: i: bad number

50. alias

alias name=string:自定义命令

  • 如果别名以空格结束,则会对其之后的单词执行别名替换
  • 把命令引用起来或放在反斜线之后可以避免进行别名替换
alias ll='ls -l'

51. unalias

unalias name:删除别名
unalias -a:删除所有别名

52. jobs

打印出尚未完成的作业状态

53. kill

终止后台作业

54. fg

ctrl+z挂起当前运行的作业,fg在前台恢复执行当前作业

55. bg

在后台恢复执行当前作业


语句结构

1. if

if command-t1
then
    command
    command
fi

# else
if command-t1
then
    command
    ...
else
    command
    ...
fi

# elif-else
if command-t1
then
    command
    ...
elif command-t2
then
    command
    ...
else
    command
    ...
fi
  • command-tx命令的退出状态会被测试,如果为0,则执行then和fi之间的命令

2. case

case value in
pattern1) command
          ...
          command;;
pattern2) command
          ...
          command;;
...
patternn) command
          ...
          command;;
esac
  • ;;类似于break

  • 模式匹配:与文件名替换相同,?任意单个字符、*零个或多个任意字符、[...]中括号中出现的任意单个字符,符号|用于两个模式之间,效果等同于逻辑“或”

3. for

for var in word1 word2 ... wordn
do
    command
    ...
done
  • 不使用列表,Shell会自动遍历命令行中输入的所有参数,和for var in "$@"一样:
for var
do
    command
    ...
done
  • 因为Shell将循环视为一种独立的小程序,任何出现在代码块关闭语句之(done/fi/esac)后的内容都可以使用重定向,也可以利用&将循环放入后台,甚至是作为命令管道的一部分
# 放入后台
for file in memo[1-4]
do
    run $file
done &

# 重定向
for i in 1 2 3 4
do
    echo $i
done > loopout

# 数据导出
for i in 1 2 3 4
do
    echo $i
done | wc -l
  • 单行循环

列表最后一项后面加分号,循环中每个命令后面加分号,do后面不加

for i in 1 2 3 4; do echo $i; done

4. while

while command-t
do
    command
    ...
done

5. until

until command-t
do
    command
    ...
done

只要command-t返回码不为0,就会不停执行代码块

6. 算术表达式

内建整数算术操作,算术扩展:$((express)),(express两边和括号之间不必有空格),出现在算术扩展中的有效元素只有操作符、数字和变量,未定义的变量或空串值被视为0

$ i=1
$ j=$((i+1))
$ echo $j
2

express中可以包含常量、Shell变量(不需要美元符号)及操作符,按照优先级从高到低的次序,操作符如下:

符号 含义
- 减号
~ 按位取反
! 逻辑反
* / % 乘、除、求余
+ - 加、减
<< >> 左移、右移
<= >= < > 比较
== != 等于、不等于
& 按位与
^ 按位异或
| 按位或
&& 逻辑与
|| 逻辑或
expr1 ? expr2 : expr3 条件运算符
= *= /= %= 赋值
+= <<= >>= 赋值
&= ^= |= 赋值
  • 整数算数
    Bash和Korn Shell都可以不使用算数扩展求值算术表达式,因为不会执行扩展,其本身可以作为命令使用;这样就可以将算数表达式用于if、while和until命令中:
$ x=10
$ ((x = x * 12))
$ echo $x
120

if ((i == 100))
then
    ...
fi

不同基数的数字:base#number,尽管被设置为按照八进制显示,但分配给该变量的值默认基数仍然是十进制,除非另行指定

$ typeset -i i=8#100
$ echo $i
8#100
$ i=50
$ echo $i
8#62

7. 函数

name () { command; ... command; }
  • 如果函数体和花括号出现在同一行,{和第一条命令之间必须至少有一个空白字符,最后一条命令和}之间必须有一个分号

  • 函数仅存在于所定义它的Shell中,无法传给子Shell,因为函数是在当前Shell中执行的

  • 在函数内部使用exit,不仅会终止函数的执行,还会使调用该函数的Shell程序退出;如果只想退出函数,可以使用return n命令,n为函数的返回状态

  • 可以将函数写进ENV文件中,启动新Shell就能直接使用

  • bash和Korn Shell可以只用局部变量:typeset j

  • Korn Shell在变量FPATH中搜索匹配函数名的文件

8. 数组

  • 数组元素以0为起始
  • [*]作为下标,在命令行中生成数组的的所有元素
  • ${#array[*]}获得array中的元素个数
  • 包含非连续值的数组成为稀疏数组
  • 双引号中使用数组元素可以省略美元符号和花括号,而且,如果声明的是整数数组,就算不在双引号中也可以省略。另外,下标表达式中的变量前面不需要使用美元符号
$ typeset -i array
$ array[0]=100
$ array[1]=50
$ (( array[2] = array[0] + array[1] ))
$ echo ${array[2]}
150
$ i=1
$ echo ${array[i]}
50
$ array[3]=array[0]+array[2]
$ echo ${array[3]}
250

引用

Shell能够识别4中不同的引用字符,单引号、双引号、反斜线和反引号

1. 单引号

  • 出现在单引号中的特殊字符,Shell会将其全部忽略

  • 传参时,引号会被Shell删除,并不会传入程序中;

$ t='* 233'

# 执行变量替换后,引号没有了,会执行变量名替换,sad!!!
$ echo $t
dingtest dingtest26787 petalk 233

2. 双引号

  • Shell会解析双引号中的美元符号、反引号、反斜线,所以,Shell会在双引号中完成变量名替换,但不会进行文件名替换
$ t='* 233'

# 执行变量替换后,引号没有了,会执行变量名替换,sad!!!
$ echo $t
dingtest dingtest26787 petalk 233

# 加了双引号就好了
$ echo "$t"
* 233

3. 反斜线

  • 作为前缀使用,在功能上相当于单个字符周围放置单引号

  • 作为输入行的最后一个字符,Shell将其视为续行符

  • 双引号中使用反斜线去除特殊字符(反斜线、美元符号、反引号、换行符、双引号)的含义(删除反斜线),如果后面没有特殊字符,Shell忽略该反斜线(不删除反斜线),继续往后处理

4. 命令替换

4.1. 反引号
  • 告诉Shell将其中的命令使用命令输出代替
$ echo The date is `date`
The date is Sun Dec 30 21:49:19 CST 2018
4.2. $(…)结构
  • 比反引号更易于嵌套
  • 双引号中会解释命令替换
$ echo $(who | wc -l)
1

变量、参数与环境

变量

1. 基础

  • 变量名以字母或下划线开头,后面跟上零个或多个字母数字或下划线

  • count=1等号两边不能有空格

  • Shell没有数据结构的概念,都是字符串

  • 对变量赋值时,Shell不执行文件名替换

  • 没有值的变量叫做未定义变量,值为空(null);Shell执行变量替换时,为空的值会被从命令行删除

  • ${variable}结构,在变量名最后一个字符后面跟着的是字母及数字字符或下划线时使用,使得变量可以正常替换

参数

即便是执行自己的Shell程序,Shell也会进行正常的命令行处理,这意味着在指定程序参数时可以利用这些处理步骤,进行文件名替换和变量替换。

但是,参数传递过程中会丢失引号,可以在自己的Shell程序中视情况给变量引用加上双引号

1. 位置参数

  • Shell会自动将第一个参数保存在特殊的Shell变量1中,这是在Shell完成正常的命令行处理后(I/O重定向、变量替换、文件名替换等)被赋值的。
  • $1表示使用程序的第一个参数进行替换
  • ${n}
    • 当给程序传递的参数多于9个时,要访问参数10需要写成${10}

2. 特殊的参数变量

参数 含义
$# 传递给程序的参数数量或者由set语句设置的参数数量
$* 引用所有的位置参数$1、$2…
$@ “$@“以”$1”…的形式引用所有的位置参数
$0 所执行的程序名
$$ 所执行程序的进程ID
$! 送入后台执行的最近一个程序的进程ID
$? 最近执行的非后台命令的退出状态码
$- 生效的当前选项标志(参见set语句)
  • 变量$#

    • 包含了命令行中输入的参数个数
  • 变量$*

    • 引用传给程序的所有参数
  • 变量$@

    • for arg in $*中传进来的参数会丢失引号,可以使用"$@"代替,那么传入程序的会是"$1"、"$2"...,关键在于$@两边的引号,如果没有了引号,该变量效果$*无异
    for arg in "$@"
    do
        echo $arg
    done
    

3. 参数替换

参数 含义
p a r a para 或 {para} 替换成para的值
${para:-value} 若para已设置且不为空,使用它的值;否则,使用value
${para-value} 若para已设置(可为空),替换为它的值,否则,替换为value
${para:=value} 若para已设置且不为空,使用它的值;否则,替换为value并将其赋给para
${para=value} 若para已设置(可为空),使用它的值,否则,替换为value,并将其赋给para
${para:?value} 若para已设置且不为空,使用它的值,否则,将value写入标准错误,然后退出。
${para?value} 若para已设置(可为空),替换为它的值;否则,将value写入标准错误并推出。
${para:+value} 若para已设置且不为空,替换成value;否则,替换为空。与${para:-value}效果相反
${para+value} 若para已设置(可为空),替换成value;否则,替换为空。
${#para} 替换为para的长度
${para#pattern} 从para左边开始删除pattern的最短匹配,余下内容作为参数替换的结果,不会修改变量的值
${para##pattern} 从para左边开始删除pattern的最长匹配
${para%pattern} 从para右边开始删除pattern的最短匹配
${para%%pattern} 从para右边开始删除pattern的最长匹配
  • ${para:=value}:不能单独作为命令,因为执行完替换操作后,Shell会尝试执行替换结果;要作为一个单独的命令,需要使用空命令:
: ${PHONEBOOK:=$HOME/phonebook}
  • p a r a : ? v a l u e {para:?value}和 {para?value}:如果忽略value,则向标准错误写入parameter: parameter null or not set

  • 模式匹配:pattern中可以使用Shell文件名替换字符(*、?、[…]、!和@)

$ var=testcase
$ echo ${var%e}
testcas
#变量的值不变
$ echo $var
testcase
$ echo ${var%s*e}
testca
$ echo ${var%%s*e}
te
$ echo ${var#?e}
stcase
$ echo ${var#*s}
tcase
$ echo ${var##*s}
e
#没有匹配会输出整个变量名
$ echo ${var#teas}
testcase

环境

1. 子Shell与导出变量

  • 除了Shell内建命令之外的其他命令通常都是在一个全新的Shell实例中执行的,我们称其为子Shell

  • 子Shell执行完毕时,它所创建的变量无法再被访问

  • 局部变量与导出变量

    • 未被导出的变量都是局部变量
    • 导出的变量及其值会被复制到子Shell的环境中,在其中可以访问并修改这些导出变量。但是这些修改不会影响到父Shell中的变量
    • 导出变量不仅保持在直接生成的子Shell中,对于由这些子Shell所生成的子Shell也不例外
    • 变量可以在赋值前后随时导出,但是只取其导出时的值,不再理会之后做出的改变
  • 可以通过将赋值语句放在命令名之前,将Shell变量添加到该命令的环境中

PHONEBOOK=$HOME/misc/phone rolo
#等同于
(PHONEBOOK=$HOME/misc/phone; export PHONEBOOK; rolo)

2. 环境变量

变量名 含义
PS1 命令行提示符的字符序列
PS2 辅命令行提示符
HOME 主目录,用户登录后所处的位置
PATH 搜索用户命令的目录列表,以冒号分隔
PWD 当前目录
OLDPWD 前一个当前目录的完整路径
CDPATH cd命令没有给出目录的完整路径时在该列表中搜索
TERM 终端类型,ansi/vt100/xterm等等
TZ 决定当前时区
IFS 内部字段分隔符
ENV 当以交互方式启动的时候,Shell会在当前环境下所执行的文件名
HISTSIZE 可保存的闲钱输入过的命令数量
HISTFILE 保存命令历史的文件
FPATH Korn Shell在变量FPATH中搜索匹配函数名的文件

一旦登出,对PS1等做出的改变会失效,除非将修改保存到.profile文件中

  • HOME:主目录,用户登录后所处的位置,不带参数的cd命令将该目录作为默认目的地

  • PATH:不将当前目录放在最先搜索的位置,避免特洛伊木马攻击

echo $PATH
/bin:/usr/bin:.
  • CDPATH:不会在登录时自动设置好(echo $CDPATH为空),需要明确将其设置为一系列目录

  • TZ:最简单的TZ的设置由时区名(长度至少3个字符)和一个数字(指定了小时数),现代Linux也可以指定地理区域来指定时区

  • IFS:当Shell解析read命令输入、命令替换输出以及执行变量替换式,会用到;在执行变量赋值时并不使用

3. (…)和{…;}

命令分组

  • (...):小括号中放入一条或多条命令,这些命令会在子Shell中执行

  • {...;}:命令在当前Shell执行,如果花括号中的命令全写在同一行,左花括号后一定要有一个空格,分号必须出现在最后一个命令的末尾

{ echo ".ls 2"; cat memo; } | nroff -Tlp | lp 
  • I/O重定向和管道可以作用在一组命令上:
#三个程序被放入后台,标准错误重定向到errors文件
(prog1; prog2; prog3) 2>errors &

编辑器

1. ed

旧式行编辑器,交互式

# 将所有的p.o(.匹配任意单个字符)修改成XXX
# 1,$(范围限定符,表示从文章的第一行到最后一行)指明在全文范围内应用替换操作,替换操作格式为s/old/new/g
# s表明是替换操作,斜线用来界定替换内容和被替换内容,g表明执行全局替换,而不仅仅是替换某一行
1,$s/p.o/XXX/g

#打印全文,查看修改结果
1,$p

2. sed

流编辑器,非交互式,不会修改原始输入文件,结果写入标准输出

-n默认不打印任何行,与p命令搭配使用,打印出符合指定范围或模式的所有行

d命令删除文本行

# 将intro文件中的Unix替换成UNIX
# 单引号避免Shell解释其中的反斜线
sed 's/Unix/UNIX/' intro

# 只打印前两行
sed -n '1,2p' intro

# 删除行1和2
sed '1,2d' intro

# 将包含jan的所有行中的第一个-1改成-5
sed '/jan/s/-1/-5/'

# 在制表符的位置显示\t不可打印字符显示为\nn(nn为字符的八进制值)
sed -n l <file>

3. vi

行编辑器,set -o vi开启

基本的vi行编辑命令

命令 含义
h 向左移动一个字符
l 向右移动一个字符
b 向左移动一个单词
w 向右移动一个单词
0 移动到行首
$ 移动到行尾
x 删除光标所在的字符
dw 删除贯标所在的单词
rc 将光标所在的字符修改成c
a 进入输入模式并在当前字符之后输入文本
i 进入输入模式并在当前字符之前输入文本
k 获得历史记录中上一条命令
j 获得历史记录中下一条命令
/string 搜索历史记录中包含string的最近一条命令,如果string没指定,使用先前的搜索内容

4. emacs

行编辑器,set -o emacs开启

基本的emacs行编辑命令

命令 含义
ctrl+b 向左移动一个字符
ctrl+f 向右移动一个字符
esc f 向前移动一个单词
esc b 向后移动一个单词
ctrl+a 移动到行首
ctrl+e 移动到行尾
ctrl+d 删除当前字符
esc d 删除当前单词
erase char (用户自定义#或ctrl+h),删除上一个字符
ctrl+p 从历史记录中获得上一条命令
ctrl+n 从历史记录中获得下一条命令
ctrl+r string 搜索历史记录,查找包含string的最近一条命令

猜你喜欢

转载自blog.csdn.net/Blanchedingding/article/details/85564354