Shell学习记录

Shell 这个单词的原意是外壳,跟 kernel(内核)相对应,比喻内核外面的一层,即用户跟内核交互的对话界面。

ch02学习记录

ln

用于链接文件,相比于cp避免了创建副本,相当于引用,多个文件使用同一个文件

image-20211106163238086

一些通配符

通配符的含义和正则类似,正则表达式30分钟入门教程

* 符号

和正则里的*一样, 用于匹配0个或以上的字符

?符号

和正则里的?类似, 不过在shell中用于匹配1个字符。正则中?用于匹配0个或一个字符

[] 符号

任选括号中元素中的一个,相比于?范围更明确

STDIN/STDOUT

image-20211108161710871
image-20211108161710871

who为例,who的输出用于表示所有当前登录进系统的用户

who示例
who示例

输出重定向

命令的输出通常是提交到标准输出设备,同时我们也可以进行重定向,将输出重定向到文件中。可类比为Go的管道操作

输入重定向

既然有输出重定向,必然有输入重定向,也可类比为Go的管道操作

ch02练习题

第一题

指定目录文件有以下内容

➜  ch02 ./print.sh
feb96
jan12.02
jan19.02
jan26.02
jan5.02
jan95
jan96
jan97
jan98
mar98
memo1
memo10
memo2
memo2.sv
  • echo *

    输出所有结果

  • echo *[!0-9]

    输出所有不已数字结尾的文件

    ➜  ch02 echo *[\!0-9]
    memo2.sv
    
  • echo m[a-df-z]*

    输出所有 m开头,后跟除e以外的所有字符

    ➜  ch02 echo m[a-df-z]*
    mar98
    
  • echo [A-Z]*

    输出文件名仅包含大些字符的元素

    ➜  ch02 echo [A-Z]*
    zsh: no matches found: [A-Z]*
    
  • echo jan*

    输出jan开头的所有文件名

    ➜  ch02 echo jan*
    jan12.02 jan19.02 jan26.02 jan5.02 jan95 jan96 jan97 jan98
    
  • echo *.*

    输出中间以.的文件

    ➜  ch02 echo *.*
    jan12.02 jan19.02 jan26.02 jan5.02 memo2.sv
    
  • echo ?????

    输出文件名长度为5的文件

    ➜  ch02 echo ?????
    feb96 jan95 jan96 jan97 jan98 mar98 memo1 memo2
    
  • echo *02

    输出以02结尾的文件

    ➜  ch02 echo *02
    jan12.02 jan19.02 jan26.02 jan5.02
    
  • echo jan?? feb?? mar??

    输出以jan开头长度5、feb开头长度为5,mar开头长度为5的文件

    ➜  ch02 echo jan?? feb?? mar??
    jan95 jan96 jan97 jan98 feb96 mar98
    
  • echo [fjm][ae][bnr]*

    输出第一个字符[fjm],第二个字符[ae]、第三个字符[bnr]的所有文件

    ➜  ch02 echo \[fjm]\[ae][bnr]*
    zsh: no matches found: [fjm][ae][bnr]*
    

第二题

下列命名序列的结果是什么

  • ls | wc -l

    统计当前文件夹内文件的数量

  • rm ???

    删除文件名长度为3的文件

  • who | wc -l

    统计登录用户

  • mv progs/* /users/Steve/backup

    移动文件

  • ls *.c | wc -l

    统计所有C文件数量

  • rm *.o

    删除所有.o后缀的文件

  • who | sort

    排序之后 输出登录用户

  • cp memo1 ..

    复制 memo1 到上一级目录

  • plotdata 2 > errors &

    后台运行

ch03学习记录

cut、paste、sed、tr、grep、sort、uniq的操作

正则再补充

在开始学习上述命令时,首先需要再梳理下正则。因为很多命令都可以通过正则进行高效的匹配我们需要的内容

在第2章中,通过shell了解到了几个常用的通配符:*[]?。不过上述的命令能够识别出的正则表达式要比shell所能够识别的要复杂的多。并且这些操作中有些符号的还以和shell的有一些区别

代码 说明 例子 匹配结果
. 匹配除换行符以外的任意字符(1个)
^ 匹配行首 ^word 仅出现在行首的word
$ 匹配行尾 x$ 仅出现在行尾的x
^INSERT$ 仅包含INSERT的行
^$ 空行
* 匹配除了换行符以外的任意字符(0 —- ∞个) x* 零个或多个连续的x
xx* 一个或多个连续的x
.* 零个或多个字符
w.*s w及紧随其后的零个或多个字符,再加上s
+ 匹配除了换行符以外的任意字符(1—- ∞个) x+ 一个或多个连续的x
xx+ 两个或多个连续的x
.+ 一个或多个字符
w.+s w及紧随其后的一个或多个字符,再加上s
匹配除了换行符以外的任意字符(0、1个) x? 1个或者2个x
[chars] 匹配chars中的任意字符 [tT] 小写或大些T
[a-z] 小写字母
[a-zA-Z] 小写或大写字母
chars 匹配不包含chars中的字符 0-9 非数字字符
a-zA-Z 非字母字符
\{…\} 匹配字符串精确的数目 x\{1,5\} 至少1个最多5个x
[0-9]\{3,9\} 连续的3~9个数字
[0-9]\{3\} 3位数字
[0-9]\{3,\} 至少3位数字
\(…\) 保存匹配的字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束

cut命令

cut命令在从数据文件或命令输出中提取各种字段,格式如下:

cut OPTION... [FILE]...

常用选项如下:

-f : 提取指定的字段,cut 命令使用 Tab 作为默认的分隔符。

-d : Tab 是默认的分隔符,使用这一选项可以指定自己的分隔符。

-b : 提取指定的字节,也可以指定一个范围。

-c : 提取指定的字符,可以是以逗号分隔的数字的列表,也可以是以连字符分隔的数字的范围。

–complement : 补充选中的部分,即反选。

–output-delimiter : 修改输出时使用的分隔符。

--only-delimited : 不输出不包含分隔符的列。

上述的命令中-c指定了我们想从file中的每行内提取哪些字符(根据位置)。

  • 单个数字,譬如-c5就指名在每行提取第5个字符,
  • 数字列表,使用,分割数字列表,这样我们匹配多个字符,-c1,5,6,25表示每行提取第1、5、6、25个字符。
  • 数字范围,使用-作为数字的范围,这样我们可以匹配一段的字符,-c20-50表示提取第20~50的字符,若是匹配到结尾,则使用-c20-

以当前登录的用户记录为例子

➜  ~ who
root     pts/0        2021-11-12 13:43 (113.200.78.120)
root     pts/1        2021-11-04 13:36 (tmux(17718).%0)
root     pts/2        2021-11-04 13:37 (tmux(17718).%1)
kwk      pts/3        2021-11-12 13:44 (113.200.78.120)
wkk      pts/4        2021-11-12 13:44 (113.200.78.120)
  • 匹配出哪些用户已经登录了系统,同时并不关心其所在的终端以及登录时间

    ➜  ~ who | cut -c 1-8
    root
    root
    root
    kwk
    wkk
    
  • 匹配出哪些用户已经登录了系统,并排序,同时并不关心其所在的终端以及登录时间

    ➜  ~ who | cut -c 1-8 | sort
    kwk
    root
    root
    root
    wkk
    

cut命令的-c选项适合从拥有固定格式的文件或命令中获取我们需要的字符,正如上述操作,因为我们知道前8个字符就是表示用户名,第10~16个字符表示终端类型,第18~29个字符表示登录时间,但是并不是所有的文件都是很好的格式的,譬如/etc/passwd,其包含了用户ID、主目录、以及特定用户登录后自动运行的程序等

➜  ~ cat /etc/passwd
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
uuidd:x:105:109::/run/uuidd:/usr/sbin/nologin
sshd:x:107:65534::/run/sshd:/usr/sbin/nologin
_chrony:x:108:117:Chrony daemon,,,:/var/lib/chrony:/usr/sbin/nologin
admin:x:1000:1000::/home/admin:/bin/bash
www:x:1001:1001::/home/www:/sbin/nologin
mysql:x:1002:1002::/home/mysql:/sbin/nologin
redis:x:1003:1003::/home/redis:/sbin/nologin
mongo:x:1004:1004::/home/mongo:/sbin/nologin
wkk:x:1005:1005::/home/wkk:/bin/sh
hhh:x:1006:1009::/home/hhh:/bin/sh
kwk:x:1007:1010::/home/kwk/:/bin/sh

经过观察,发现内部是通过:进行分割,所以我们可以通过:对每行的内容进行分割,然后再截取我们需要的内容

  • /etc/passwd中提取所有的用户名

    ➜  ~ cut -d: -f1 /etc/passwd
    root
    daemon
    ...
    www
    mysql
    redis
    mongo
    wkk
    hhh
    kwk
    
  • /etc/passwd中提取所有的用户名以及对应的主目录

    ➜  ~ cut -d: /etc/passwd -f1,6
    root:/root
    daemon:/usr/sbin
    ...
    www:/home/www
    mysql:/home/mysql
    redis:/home/redis
    mongo:/home/mongo
    wkk:/home/wkk
    hhh:/home/hhh
    kwk:/home/kwk/
    

一些实用的例子

  1. 提取 ps 命令中的 USER,PID和COMMAND:

    ➜  shell ps -L u n | tr -s " " | cut -d " " -f 2,3,14-
    USER PID COMMAND
    0 760 /sbin/agetty -o -p -- \u --noclear tty1 linux
    0 1022 /sbin/agetty -o -p -- \u --keep-baud 115200,38400,9600 ttyS0 vt220
    0 4518 -zsh
    0 12155 ps -L u n
    0 12156 tr -s
    0 12157 cut -d -f 2,3,14-
    0 17719 -zsh
    0 20660 -zsh
    
  2. 提取内存的 total,used和free值,并保存到一个文件中

    $ free -m | tr -s ' ' | sed '/^Mem/!d' | cut -d" " -f2-4 >> memory.txt
    $ cat memory.txt
    985 86 234
    

总结

  • cut 命令可以和很多其他Linux或Unix命令通过管道连接。可以通过管道传递一个或多个过滤器进行额外的文本处理。

  • cut 命令的局限性之一是它不支持指定多个字符作为分隔符。多个空格会被计算为多个字段分隔符,因此必须在 cut 命令前使用 tr 命令才能获得需要的输出。

paste命令

和cut想法,cut用于拆分,paste用于合并,格式如下:

 paste [-s][-d ][--help][--version][文件...]

常用参数如下:

-d或–delimiters=  用指定的间隔字符取代跳格字符。

-s或–serial  串列进行而非平行处理。

假设有一个名为names的文件,内容为一系列姓名:

➜  ch03 cat names
Tony
Emanuel
Lucy
Ralph
Fred

另一个名为numbers的文件,其中包含了names文件中姓名所对应的电话

➜  ch03 cat numbers
(307)   555-5456
(212)   555-3456
(212)   555-9959
(212)   555-7741
(212)   555-0040

另一个名为addresses的文件,其中包含names文件中姓名所对应好的地址

➜  ch03 cat addresses
55-23 Vine Street, Miami
39 University Place, New York
17 E. 25th Street, New York
38 Chauncey St., Bensonhurst
17 E. 25th Street, New York

将上述三者进行合并:

➜  ch03 paste names numbers addresses
Tony    (307)   555-5456    55-23 Vine Street, Miami
Emanuel    (212)   555-3456    39 University Place, New York
Lucy    (212)   555-9959    17 E. 25th Street, New York
Ralph    (212)   555-7741    38 Chauncey St., Bensonhurst
Fred    (212)   555-0040    17 E. 25th Street, New York
  • 使用-d自定义分隔符

    ➜  ch03 paste -d '+' names numbers addresses
    Tony+(307)   555-5456+55-23 Vine Street, Miami
    Emanuel+(212)   555-3456+39 University Place, New York
    Lucy+(212)   555-9959+17 E. 25th Street, New York
    Ralph+(212)   555-7741+38 Chauncey St., Bensonhurst
    Fred+(212)   555-0040+17 E. 25th Street, New York
    
  • 使用-s将文件串列处理

    ➜  ch03 paste -s names
    Tony    Emanuel    Lucy    Ralph    Fred
    

sed命令

sed 是一种在线编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有 改变,除非你使用重定向存储输出。Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等,用法如下:

sed [options] 'command' file(s)
sed [options] -f scriptfile file(s)

command中的选项如下:

a\ # 在当前行下面插入文本。
i\ # 在当前行上面插入文本。
c\ # 把选定的行改为新的文本。
d # 删除,删除选择的行。
D # 删除模板块的第一行。
s # 替换指定字符
h # 拷贝模板块的内容到内存中的缓冲区。
H # 追加模板块的内容到内存中的缓冲区。
g # 获得内存缓冲区的内容,并替代当前模板块中的文本。
G # 获得内存缓冲区的内容,并追加到当前模板块文本的后面。
l # 列表不能打印字符的清单。
n # 读取下一个输入行,用下一个命令处理新的行而不是用第一个命令。
N # 追加下一个输入行到模板块后面并在二者间嵌入一个新行,改变当前行号码。
p # 打印模板块的行。
P # (大写) 打印模板块的第一行。
q # 退出Sed。
b lable # 分支到脚本中带有标记的地方,如果分支不存在则分支到脚本的末尾。
r file # 从file中读行。
t label # if分支,从最后一行开始,条件一旦满足或者T,t命令,将导致分支到带有标号的命令处,或者到脚本的末尾。
T label # 错误分支,从最后一行开始,一旦发生错误或者T,t命令,将导致分支到带有标号的命令处,或者到脚本的末尾。
w file # 写并追加模板块到file末尾。  
W file # 写并追加模板块的第一行到file末尾。  
! # 表示后面的命令对所有没有被选定的行发生作用。  
= # 打印当前行号码。  
# # 把注释扩展到下一个换行符以前。

选项如下:

-n :使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到终端上。但如果加上 -n 参数后,则只有经过sed 特殊处理的那一行(或者动作)才会被列出来。
-e :直接在命令列模式上进行 sed 的动作编辑;
-f :直接将 sed 的动作写在一个文件内, -f filename 则可以运行 filename 内的 sed 动作;
-r :sed 的动作支持的是延伸型正规表示法的语法。(默认是基础正规表示法语法)
-i :直接修改读取的文件内容,而不是输出到终端。

有一个intro文件,具体内容如下:

➜  ch03 cat intro
The Unix operating system was pioneered by Ken
Theopson and Dennis Ritchie at Bell Laboratories
in the late 1960s. One of the primary goals in
the esign of the Unix system was to crate an
environment that promoted efficient program
development.

替换操作:s命令

  • 将所有的Unix替换为UNIX

    ➜  ch03 sed 's/Unix/UNIX/' intro
    The UNIX operating system was pioneered by Ken
    Theopson and Dennis Ritchie at Bell Laboratories
    in the late 1960s. One of the primary goals in
    the esign of the UNIX system was to crate an
    environment that promoted efficient program
    development.
    
  • 只输出那些发生替换的行

    ➜  ch03 sed -n 's/Unix/UNIX/p' intro
    The UNIX operating system was pioneered by Ken
    the esign of the UNIX system was to crate an
    
  • 切换oh-my-zsh中的主题,从默认主题切换为awesomepanda

    sed -i 's/ZSH_THEME="robbyrussell"/ZSH_THEME="awesomepanda"/g' ~/.zshrc
    

删除操作:d命令

  • 使用sed删除文件的前两行

    ➜  ch03 sed '1,2d' intro
    in the late 1960s. One of the primary goals in
    the esign of the Unix system was to crate an
    environment that promoted efficient program
    development.
    

tr命令

tr命令 可以对来自标准输入的字符进行替换、压缩和删除。它可以将一组字符变成另一组字符,经常用来编写优美的单行命令,作用很强大。用法如下:

tr(选项)(from to)

选项:

-c或——complerment:取代所有不属于第一字符集的字符;
-d或——delete:删除所有属于第一字符集的字符;
-s或--squeeze-repeats:把连续重复的字符以单独一个字符表示;
-t或--truncate-set1:先删除第一字符集较第二字符集多出的字符。

参数 :

  • from:指定要转换或删除的原字符集。当执行转换操作时,必须使用参数“字符集2”指定转换的目标字符集。但执行删除操作时,不需要参数“字符集2”;
  • to:指定要转换成的目标字符集。

简单使用

  • 将输入字符进行大小写转换:

    ➜  ch03 echo "HELLO world" | tr '[a-zA-Z]' '[A-Za-z]'
    hello WORLD
    
  • intro中的所有的字符e替换为x

    ➜  ch03 tr e x < intro
    Thx Unix opxrating systxm was pionxxrxd by Kxn
    Thxopson and Dxnnis Ritchix at Bxll Laboratorixs
    in thx latx 1960s. Onx of thx primary goals in
    thx xsign of thx Unix systxm was to cratx an
    xnvironmxnt that promotxd xfficixnt program
    dxvxlopmxnt.
    
  • 提取/etc/passwd中的用户和主目录,同时将分隔符:替换为' '

    ➜  ch03 cut -d : -f 1,6 /etc/passwd | tr : '    '
    root /root
    daemon /usr/sbin
    bin /bin
    sys /dev
    sync /bin
    

使用 -s进行压缩

譬如我们想将下述文件中连续的a替换一个b,如果按照上述的直白命令会导致如下问题:

➜  ch03 cat test | tr a b
this is bbbbbbbbbbbbbbbb file

所以我们可以使用-s将多个重复的元素使用一个单独的字符进行替换

➜  ch03 cat test | tr -s a b
this is b file

使用-d进行删除

可以使用-d操作删除输入流中的一些字符

➜  ch03 cat intro | tr -d ' '
TheUnixoperatingsystemwaspioneeredbyKen
TheopsonandDennisRitchieatBellLaboratories
inthelate1960s.Oneoftheprimarygoalsin
theesignoftheUnixsystemwastocratean
environmentthatpromotedefficientprogram
development.

grep

(global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。用于过滤/搜索的特定字符。可使用正则表达式能配合多种命令使用,使用上十分灵活。常见用法如下:

grep match_pattern file_name
grep "match_pattern" file_name

选项:

-a --text  # 不要忽略二进制数据。
-A <显示行数>   --after-context=<显示行数>   # 除了显示符合范本样式的那一行之外,并显示该行之后的内容。
-b --byte-offset                           # 在显示符合范本样式的那一行之外,并显示该行之前的内容。
-B<显示行数>   --before-context=<显示行数>   # 除了显示符合样式的那一行之外,并显示该行之前的内容。
-c --count    # 计算符合范本样式的列数。
-C<显示行数> --context=<显示行数>或-<显示行数> # 除了显示符合范本样式的那一列之外,并显示该列之前后的内容。
-d<进行动作> --directories=<动作>  # 当指定要查找的是目录而非文件时,必须使用这项参数,否则grep命令将回报信息并停止动作。
-e<范本样式> --regexp=<范本样式>   # 指定字符串作为查找文件内容的范本样式。
-E --extended-regexp             # 将范本样式为延伸的普通表示法来使用,意味着使用能使用扩展正则表达式。
-f<范本文件> --file=<规则文件>     # 指定范本文件,其内容有一个或多个范本样式,让grep查找符合范本条件的文件内容,格式为每一列的范本样式。
-F --fixed-regexp   # 将范本样式视为固定字符串的列表。
-G --basic-regexp   # 将范本样式视为普通的表示法来使用。
-h --no-filename    # 在显示符合范本样式的那一列之前,不标示该列所属的文件名称。
-H --with-filename  # 在显示符合范本样式的那一列之前,标示该列的文件名称。
-i --ignore-case    # 忽略字符大小写的差别。
-l --file-with-matches   # 列出文件内容符合指定的范本样式的文件名称。
-L --files-without-match # 列出文件内容不符合指定的范本样式的文件名称。
-n --line-number         # 在显示符合范本样式的那一列之前,标示出该列的编号。
-P --perl-regexp         # PATTERN 是一个 Perl 正则表达式
-q --quiet或--silent     # 不显示任何信息。
-R/-r  --recursive       # 此参数的效果和指定“-d recurse”参数相同。
-s --no-messages  # 不显示错误信息。
-v --revert-match # 反转查找。
-V --version      # 显示版本信息。   
-w --word-regexp  # 只显示全字符合的列。
-x --line-regexp  # 只显示全列符合的列。
-y # 此参数效果跟“-i”相同。
-o # 只输出文件中匹配到的部分。
-m <num> --max-count=<num> # 找到num行结果后停止查找,用来限制匹配行数

正则使用详见上述正则再补充内容

常见用法

  • 在文件中搜索某个字符,返回对应字符所在行

    ➜  ch03 cat intro | grep 'Unix'
    The Unix operating system was pioneered by Ken
    the esign of the Unix system was to crate an
    
  • 多个文件中查找(注意一些shell中具有特殊含义的字符,建议将需要匹配的内容放入''之中)

    ➜  ch03 grep 'The' *
    ed.cmd:There were a sensitivity and a beauty to her that have nothing to do with looks. She was one to be listened to, whose words were so easy to take to heart.
    ed.cmd:It is said that the true nature of being is veiled. The labor of words, the expression of art, the seemingly ceaseless buzz that is human thought all have in common the need to get at what really is so. The hope to draw close to and possess the truth of being can be a feverish one. In some cases it can even be fatal, if pleasure is one's truth and its attainment more important than life itself. In other lives, though, the search for what is truthful gives life.
    ed.cmd:I used to find notes left in the collection basket, beautiful notes about my homilies and about the writer's thoughts on the daily scriptural readings. The person who penned the notes would add reflections to my thoughts and would always include some quotes from poets and mystics he or she had read and remembered and loved. The notes fascinated me. Here was someone immersed in a search for truth and beauty. Words had been treasured, words that were beautiful. And I felt as if the words somehow delighted in being discovered, for they were obviously very generous to the as yet anonymous writer of the notes. And now this person was in turn learning the secret of sharing them. Beauty so shines when given away. The only truth that exists is, in that sense, free.
    ed.cmd:One Sunday morning, I was told that someone was waiting for me in the office. The young person who answered the rectory door said that it was "the woman who said she left all the notes." When I saw her I was shocked, since I immediately recognized her from church but had no idea that it was she who wrote the notes. She was sitting in a chair in the office with her hands folded in her lap. Her head was bowed and when she raised it to look at me, she could barely smile without pain. Her face was disfigured, and the skin so tight from surgical procedures that smiling or laughing was very difficult for her. She had suffered terribly from treatment to remove the growths that had so marred her face.
    intro:The Unix operating system was pioneered by Ken
    intro:Theopson and Dennis Ritchie at Bell Laboratories
    
  • 搜索多个文件并查找匹配文本在哪些文件中

    ➜  ch03 grep -l 'The' *
    ed.cmd
    intro
    
  • 输出除之外的所有行 -v 选项

    ➜  ch03 grep -v 'Unix' intro
    Theopson and Dennis Ritchie at Bell Laboratories
    in the late 1960s. One of the primary goals in
    environment that promoted efficient program
    development.
    
  • 只输出文件中匹配到的部分 -o 选项

    ➜  ch03 grep -o 'Unix' intro
    Unix
    Unix
    
  • 标记匹配颜色 —color=auto 选项

  • 统计文件或者文本中包含匹配字符串的行数 -c 选项

    ➜  ch03 grep -c 'Unix' intro
    2
    

sort

按照字典序排序,用法如下:

sort [OPTION]... [FILE]...
sort [OPTION]... --files0-from=F

选项:

-b, --ignore-leading-blanks    忽略开头的空白。
-d, --dictionary-order         仅考虑空白、字母、数字。
-f, --ignore-case              将小写字母作为大写字母考虑。
-g, --general-numeric-sort     根据数字排序。
-i, --ignore-nonprinting       排除不可打印字符。
-M, --month-sort               按照非月份、一月、十二月的顺序排序。
-h, --human-numeric-sort       根据存储容量排序(注意使用大写字母,例如:2K 1G)。
-n, --numeric-sort             根据数字排序。
-R, --random-sort              随机排序,但分组相同的行。
--random-source=FILE           从FILE中获取随机长度的字节。
-r, --reverse                  将结果倒序排列。
--sort=WORD                    根据WORD排序,其中: general-numeric 等价于 -g,human-numeric 等价于 -h,month 等价于 -M,numeric 等价于 -n,random 等价于 -R,version 等价于 -V。
-V, --version-sort             文本中(版本)数字的自然排序。

现有names文件,具体内容如下:

Charlie
Emanuel
Fred
Lucy
Ralph
Tony
Tony
  • 按照字典序排序

    ➜  ch03 sort names
    a:01:1.0
    aaa:10:1.1
    b:02:2.0
    bbb:20:2.2
    c:03:3.0
    ccc:30:3.3
    ddd:40:4.4
    eee:50:5.5
    eee:50:5.5
    
  • 忽略相同行使用-u选项或者uniq

    ➜  ch03 sort names
    Charlie
    Emanuel
    Fred
    Lucy
    Ralph
    Tony
    Tony
    
  • 按照字典序的逆序输出

    ➜  ch03 sort -r names
    Tony
    Tony
    Ralph
    Lucy
    Fred
    Emanuel
    Charlie
    
  • 使用-n按照数字大小进行排序(按照第一个字段进行排序)

    可以使用-k number 跳过number-1个字段,从第number个字段开始排序

    可以使用-t指定分隔符

    ➜  ch03 sort -n data
    -5      11
    2       12
    3       33
    5       27
    14      -9
    15      6
    
    ➜  ch03 sort -k2n data  ## 以第二个字段开始排序
    14      -9
    23      2
    15      6
    -5      11
    2       12
    5       27
    3       33
    
    ➜  ch03 sort -k3 -n -t : /etc/passwd | cut -d : -f1,3 # 按照 : 进行分割,并且以第三个字段进行排序,截取第一个字段第三个字段进行输出
    root:0
    daemon:1
    bin:2
    sys:3
    sync:4
    games:5
    

uniq

主要用途

  • 将输入文件(或标准输入)中邻近的重复行写入到输出文件(或标准输出)中。
  • 当没有选项时,邻近的重复行将合并为一个。

用法如下:

uniq [OPTION]... [INPUT [OUTPUT]]

选项:

-c, --count                在每行开头增加重复次数。
-d, --repeated             所有邻近的重复行只被打印一次。
-D                         所有邻近的重复行将全部打印。
--all-repeated[=METHOD]    类似于 -D,但允许每组之间以空行分割。METHOD取值范围{none(默认),prepend,separate}。
-f, --skip-fields=N        跳过对前N个列的比较。
--group[=METHOD]           显示所有行,允许每组之间以空行分割。METHOD取值范围:{separate(默认),prepend,append,both}。
-i, --ignore-case          忽略大小写的差异。
-s, --skip-chars=N         跳过对前N个字符的比较。
-u, --unique               只打印非邻近的重复行。
-z, --zero-terminated      设置行终止符为NUL(空),而不是换行符。
-w, --check-chars=N        只对每行前N个字符进行比较。
--help                     显示帮助信息并退出。
--version                  显示版本信息并退出。

参数:

INPUT(可选):输入文件,不提供时为标准输入。

OUTPUT(可选):输出文件,不提供时为标准输出。

还是按照sort中引用的names为例。以下是其内容:

Charlie
Emanuel
Tony
Fred
Lucy
Ralph
Tony
  • 输出names中的姓名,重复元素只输出一次即可

    ➜  ch03 uniq names  ## 因为两个Tony不是连续的,所以直接使用uniq无法去除重复元素
    Charlie
    Emanuel
    Tony
    Fred
    Lucy
    Ralph
    Tony
    
    ➜  ch03 sort names | uniq # 先通过sort排序,之后再使用uniq,将重复的元素放在一起
    Charlie
    Emanuel
    Fred
    Lucy
    Ralph
    Tony
    
  • 输出出现重复的内容

    ➜  ch03 sort names | uniq -d
    Tony
    
  • 词频统计

    ➜  ch03 sort names | uniq -c
          1 Charlie
          1 Emanuel
          1 Fred
          1 Lucy
          1 Ralph
          2 Tony
    

    找出文件中频率最高的元素

    tr '[A-Z]' '[a-z]' file | sort | uniq -c | head
    

ch04学习记录

为了简化一些操作,我们可以将一些命令统一放置在一个文件中,然后将该文件赋予可执行权限,这样,就避免了大量的重复操作,譬如当前有一个stats文件内容如下:

➜  ch04 cat stats
echo '现在的日期是: '  `date`
echo '现在共有: ' `who | wc -l` '个用户登录'
echo '当前的位置为: ' `pwd`
➜  ch04 chmod u+x stats
➜  ch04 ./stats
现在的日期是:  Sun Nov 14 20:08:38 CST 2021
现在共有:  14 个用户登录
当前的位置为:  /root/exper/shell/ch04

变量

和正常的编程语言一样,Shell中也可以使用变量,命名要求为以字母或_开头,后面跟上零个或多个字母及数字或下划线,赋值格式如下:

variable=value

一些注意事项:

  • =号两边不需要和类似Java的编码规范一样, 使用空格隔开,Shell中不能有空格
  • Shell中并无各种明确的数据类型,无论分配什么样的值,在Shell中都将其视为字符串
  • 变量的显示

    echo $variable
    # 显示 系统环境变量
    ➜  ch04 echo $PATH
    /root/.oh-my-zsh/custom/plugins/git-open:/opt/jdk-11.0.13/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
    # 显示多个变量
    ➜  ch04 echo $count $name
    1 孔维坤
    # 变量出现在任何其他命令中
    ➜  ch04 $my_pos=/root/expr/shell/ch04
    zsh: /root/expr/shell/ch04 not found
    ➜  ch04 ls $my_pos
    nu  stats
    
  • 变量的默认值

    ➜  ch04 echo :$null:
    ::
    # 未赋值的变量为空, 并且不会影响其他的命令
    ➜  ch04 who $null | wc $null -l
    2
    # 更好的声明空值变量, 关于""和''的区别,后续会阐述
    ➜  ch04 $null=""
    ➜  ch04 $null=''
    
  • ${variable}结构

    假设将$name 重命名为$nameX

    ➜  ch04 name=/Users/weikunkun/ShellProjects/ch04/aaa             
    ➜  ch04 $name     
    ➜  ch04 mv $name ${name}X
    
  • 内建的整数算术操作

    算术扩展的格式为:$((expression))

    运算符号包括:+、-、、/、%和*

    复合操作:+=、-=、*、=、/=

    自增自减:variable++、variable—

    ➜  ch04 echo $a 
    
    ➜  ch04 echo $((a = a + 1))
    1
    

ch05学习记录

本章节主要用于阐述字符引用,Shell中能够识别4种不同的引用字符

单引号和双引号

双引号和单引号的区别:

两者的功能类似,但是对于内容的保护,双引号要弱于单引号:单引号告诉Shell忽略引用的所有特殊字符,而双引号则忽略引用的大部分字符,但是如下的字符不能忽略

  • $
  • `
  • \
➜  ch04 echo one         two    three                           
one two three
➜  ch04 echo 'one        two            three'
one        two            three
➜  ch04 cur_path=$(pwd)                                          
➜  ch04 echo $cur_path                                           
/Users/weikunkun/ShellProjects/ch04
➜  ch04 echo '$cur_path'
$cur_path 
➜  ch04 echo "$cur_path" 
/Users/weikunkun/ShellProjects/ch04
➜  ch05 address="成都市 锦江区"
➜  ch05 echo $address 
成都市 锦江区
➜  ch05 echo '$adress'  
$adress
➜  ch05 echo "$address" 
成都市 锦江区
图示
图示

反斜线

用于转义

➜  ch05 echo $x   
*
➜  ch05 echo \$x  
$x
➜  ch05 line=one' 
quote> 'two
➜  ch05 echo $line
one
two
➜  ch05 line=one\ 
> two       
➜  ch05 echo $line
onetwo

命令替换

和Java中的printf类似,可以输出命令或者变量中的结果替换命令内部其他命令。

命令替换有两种形式

➜  ch05 `command`
➜  ch05 $(...)

# demo
➜  ch05 echo The date and time is: `date`
The date and time is: 2021年11月19日 星期五 15时17分39秒 CST
➜  ch05 echo The date and time is: $(date)
The date and time is: 2021年11月19日 星期五 15时17分43秒 CST

虽然两种写法都可以进行命令的替换,但是$()的写法要比第一种好

  1. 方便阅读
  2. $()易于嵌套,能够在命令中在进行命令的嵌套,同时括号内部可以支持多个命令,多命令之间使用;隔开,同时还可以使用|进行传递

譬如:

➜  ch05 vim nu
echo '################`command`################'
echo 现在有 `who | wc -l `用户登录
echo '现在有 `who | wc -l`用户登录'
echo "现在有 `who | wc -l`用户登录"
echo '################$(...)################'
echo 现在有 $(who | wc -l) 用户登录
echo '现在有 $(who | wc -l) 用户登录'
echo "现在有 $(who | wc -l) 用户登录"
➜  ch05 ./nu      
################`command`################
现在有 2用户登录
现在有 `who | wc -l`用户登录
现在有        2用户登录
################$(...)################
现在有 2 用户登录
现在有 $(who | wc -l) 用户登录
现在有        2 用户登录

expr命令

用于数学计算,优先级和正常算术运算同步

➜  ch05 expr 1 + 1                            
2
➜  ch05 expr 10 + 10 / 2
15
➜  ch05 expr 10 / 3     
3
➜  ch05 expr 10 / 3.0
expr: not a decimal number: '3.0'
➜  ch05 expr 10 * 3  
expr: syntax error
➜  ch05 expr 10 \* 3
30
➜  ch05 expr '10 * 3'
10 * 3
➜  ch05 expr "10 * 3"
10 * 3
➜  ch05 i=1     
➜  ch05 i=$(expr $i + 1)
➜  ch05 echo $i                           
2
➜  ch05 i=`expr $i + 100`
➜  ch05 echo $i
102

书本练习

text=" 123 456 "
name=
Tony
Ralph
Tony
Lucy
Fred
Emanuel
Charlie
  1. 写出一个命令,用来去掉shell变量text值中所有的空格字符。要保证把结果重新赋值给text。(分别使用tr和sed实现)

    ➜  ch05 text=" 123 456 "              
    ➜  ch05 text=$(echo $text | sed -e 's/[ ]*//g') 
    ➜  ch05 echo $text
    123456
    ➜  ch05 text=" 123 456 "           
    ➜  ch05 text=$(echo $text | tr -d ' ')          
    ➜  ch05 echo $text         
    123456
    
  2. 写出一个命令,用来计数shell变量text值中包含字符的个数。然后再写一个命令,统计其中字母的个数(使用sed、wc)

    ➜  ch05 cat names | wc -cw
           7      42
    
  1. 写出一个命令,吧文件names中各不相同的行存到变量namelist中

    ➜  ch05 sort names | uniq > namelist
    ➜  ch05 cat namelist                             
    ───────┬────────────────────────────────────────────────────────────────────────
           │ File: namelist
    ───────┼────────────────────────────────────────────────────────────────────────
       1   │ Charlie
       2   │ Emanuel
       3   │ Fred
       4   │ Lucy
       5   │ Ralph
       6   │ Tony
    ───────┴────────────────────────────────────────────────────────────────────────
    

ch06 传递参数

本章用于学习参数的处理

特殊的参数表示

  • $# 表示命令行中输入的参数个数

  • $* 表示所有的参数

  • $0 表示文件名
  • $? 表示运行结束后的状态码
# exampole for some specific args
echo "文件名: $0"
echo "所有参数:$*"
echo "输出的参数: $#"
for i in $(seq 1 5)
do
    echo "第i个参数:$i"
done
➜  ch06 ./args 1 2 3 4 5
文件名: ./args
所有参数:1 2 3 4 5
输出的参数: 5
第i个参数:1
第i个参数:2
第i个参数:3
第i个参数:4
第i个参数:5
➜  ch06 echo $?   
0

传递参数

电话簿中查找联系人

如果我们在phonebook中寻找用户的话,通过grep name phonebook即可,但是为了使得该功能更加灵活,可以将name抽出来作为参数,然后编写一个专门用于在phonebook中查找人名的脚本。

➜  ch06 vim lu 
# Look someone up in the phone book
grep $1 phonebook
➜  ch06 ./lu Alice
Alice Chebba    973-555-2015

但是有一个小问题,具体如下:

➜  ch06 ./lu "Susan T"
grep: T: No such file or directory
phonebook:Susan Goldberg  201-555-7776
phonebook:Susan Topple    212-555-4932

虽然我们只传递了一个参数,但实际执行时变成了两个参数。

# 代入到我们编写的脚本
# Look someone up in the phone book
grep Susan T phonebook
# 通过脚本就能明显的看到,我们是在T、phonebook两个文件中,匹配Susan这个名字,所以会爆 no such file问题

所以我们可以将脚本优化为:

➜  ch06 vim lu
# Look someone up in the phone book
grep "$1" phonebook
➜  ch06 ./lu "Susan T"
Susan Topple    212-555-4932

电话簿中添加联系人

➜  ch06 vim add      
# Add someone to the phone book
echo "$1    $2" >> phonebook
sort -o phonebook phonebook
➜  ch06 ./add 'Stromboli Pizza' 973-555-9478
➜  ch06 cat phonebook 
───────┬────────────────────────────────────────────────────────────────────────
       │ File: phonebook
───────┼────────────────────────────────────────────────────────────────────────
   1   │ Alice Chebba    973-555-2015
   2   │ Barbara Swingle 201-555-9257
   3   │ Liz Stachiw     212-555-2298
   4   │ Stromboli Pizza 973-555-9478
   5   │ Susan Goldberg  201-555-7776
   6   │ Susan Topple    212-555-4932
   7   │ Tony Iannino    973-555-1295
───────┴────────────────────────────────────────────

电话簿中删除联系人

➜  ch06 vim rem   
# remove someone fromm the phone book
grep -v "$1" phonebook > /tmp/phonebook
mv /tmp/phonebook phonebook
➜  ch06 cat phonebook              
───────┬────────────────────────────────────────────────────────────────────────
       │ File: phonebook
───────┼────────────────────────────────────────────────────────────────────────
   1   │ Alice Chebba    973-555-2015
   2   │ Barbara Swingle 201-555-9257
   3   │ Liz Stachiw     212-555-2298
   4   │ Susan Goldberg  201-555-7776
   5   │ Susan Topple    212-555-4932
   6   │ Tony Iannino    973-555-1295
───────┴────────────────────────────────────────────────────────────────────────

更多参数

如果参数的数量超过9个,那么我们无法使用$10进行表示第10个参数,其会覆盖第一个参数($1)的值,所以要想正确接受到10个以上的参数,从第10个开始,我们需要使用\${n}的形式

ch07条件语句

退出状态

在条件测试中,退出状态很重要。只要程序执行完成,都会向Shell返回一个退出状态码,这个状态码类似HTTP的响应码,是一个数字,表示程序是否运行成功。0表示运行成功,非0状态表示运行失败,状态码对应于不同的失败原因。其中$?可以作为程序运行结束后的返回结果。

test和[]命令

test的一般格式为

test expression

在命令行中输入man test,可以查看test命令的用法。

  1. test命令用于判断文件类型,以及对变量做比较。
  2. test命令用exit code返回结果,而不是使用stdout。0表示真,非0表示假。
test 2 -lt 3  # 为真,返回值为0
echo $?  # 输出上个命令的返回值,输出0

同时我们也可以使用另一种形式来表示:

[ expression ]
# 在 ] 之前和 [ 之后要保证有空格 

if [ "$name" = kwk ]
then
        // do something
done

文件类型判断

格式:

test -e filename  # 判断文件是否存在
测试参数 代表意义
-e 文件是否存在
-f 是否为文件
-d 是否为目录

文件权限判断

格式:

test -r filename  # 判断文件是否可读
测试参数 代表意义
-r 文件是否可读
-w 文件是否可写
-x 文件是否可执行
-s 是否为非空文件

整数间的比较

格式:

test $a -eq $b  # a是否等于b
测试参数 代表意义
-eq a是否等于b
-ne a是否不等于b
-gt a是否大于b
-lt a是否小于b
-ge a是否大于等于b
-le a是否小于等于b

字符串比较

测试参数 代表意义
test -z STRING 判断STRING是否为空,如果为空,则返回true
test -n STRING 判断STRING是否非空,如果非空,则返回true(-n可以省略)
test str1 == str2 判断str1是否等于str2
test str1 != str2 判断str1是否不等于str2

多重条件判定

格式:

test -r filename -a -x filename
测试参数 代表意义
-a 两条件是否同时成立(与)
-o 两条件是否至少一个成立(或)
-r 取反。如 test ! -x file,当file不可执行时,返回true(非)

根据以上的各种类型判断,可以编写一个更为完整的用户登录脚本

#! /bin/bash 
user="$1"

if who | grep "^$user" > /dev/null # 避免 grep的输出结果
then   
    echo "$user is logged on"
else
    echo "$user is not logged on"
fi

同时,为了成为一个长期可用的程序,我们应该添加对该程序的阐述,并且校验参数数量上的正确性。如果哪个参数不对,还需要进行指出

#! /bin/bash 
# determine if someone is logged on V1
# judge correct number of arguments were supplied
if [ "$#" -ne 1 ] 
then
    echo "Incorrect number of arguments"
    echo "Usage: ./on user"
else 
    user="$1"

    if who | grep "^$user" > /dev/null 
    then   
        echo "$user is logged on"
    else
        echo "$user is not logged on"
    fi
fi

输出:

➜  ch07 ./on hanhuanhuan
hanhuanhuan is not logged on
➜  ch07 ./on            
Incorrect number of arguments
Usage: ./on user
➜  ch07 ./on weikunkun  
weikunkun is logged on
➜  ch07 ./on aa bb cc 
Incorrect number of arguments
Usage: ./on user

exit命令

  • exit命令用来退出当前shell进程,并返回一个退出状态;使用$?可以接收这个退出状态。
  • exit命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。
  • exit退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。

8

编写删除phone book中的任意一个用户的脚本,同时为了保证系统安全,使用对参数进行校验,仅支持传入一个参数

# rem v2.0
# remove someone from phone book
#
if [ "$#" -ne 1 ] 
then 
    echo "Incorrect number of arguments."
    echo "Usage: ./rem name"
    exit 1 # 指定错误码
fi
grep -v "$1" phonebook > /tmp/phonebook
mv /tmp/phonebook phonebook

if…then形式

格式:

if condition
then
    语句1
    语句2
    ...
else
    语句1
    语句2
    ...
fi

示例:

a=3
b=4

if ! [ "$a" -lt "$b" ]
then
    echo ${a}不小于${b}
else
    echo ${a}小于${b}
fi

输出结果:

3小于4

多层if-elif-elif-else

格式:

if condition
then
    语句1
    语句2
    ...
elif condition
then
    语句1
    语句2
    ...
elif condition
then
    语句1
    语句2
else
    语句1
    语句2
    ...
fi

示例:

还是以删除phone book为例,之前的写法,虽然检验了参数的合法性,但还不能很好明白用户的意图,对于其他的行为并没有做过多的检测。有时候会误删非用户目标的内容。所以仍需要优化

➜  ch07 vim rem 
#! /bin/bash
# rem v3.0
# remove someone from phone book
#
if [ "$#" -ne 1 ] 
then 
    echo "Incorrect number of arguments."
    echo "Usage: ./rem name"
    exit 1 # 指定错误码
fi

# 获取入参
name=$1

# 先匹配出根据参数 删除的条目数量
matches=$(grep $name phonebook | wc -l)

if [ "$matches" -gt 1 ]
then
    echo "More than one match; please qualify further"
elif [ "$matches" -eq 1 ]
then 
    grep -v "$name" phonebook > /tmp/phonebook
    mv /tmp/phonebook phonebook
else 
    echo "I could not find $name in the phone book"
fi

➜  ch07 ./rem aaaa     
I could not find aaaa in the phone book

➜  ch07 cat phonebook                 
───────┬────────────────────────────────────────────────────────────────────────
       │ File: phonebook
───────┼────────────────────────────────────────────────────────────────────────
   1   │ Alice Chebba    973-555-2015
   2   │ Barbara Swingle 201-555-9257
   3   │ Liz Stachiw     212-555-2298
   4   │ Susan Goldberg  201-555-7776
   5   │ Susan Topple    212-555-4932
   6   │ Tony Iannino    973-555-1295
───────┴────────────────────────────────────────────────────────────────────────
➜  ch07 ./rem Susan
More than one match; please qualify further
➜  ch07 ./rem Tony 
➜  ch07 cat phonebook
───────┬────────────────────────────────────────────────────────────────────────
       │ File: phonebook
───────┼────────────────────────────────────────────────────────────────────────
   1   │ Alice Chebba    973-555-2015
   2   │ Barbara Swingle 201-555-9257
   3   │ Liz Stachiw     212-555-2298
   4   │ Susan Goldberg  201-555-7776
   5   │ Susan Topple    212-555-4932
───────┴────────────────────────────────────────────────────────────────────────

case…esac形式

格式:

case $变量名称 in
    值1)
        语句1
        语句2
        ...
        ;;  # 类似于C/C++中的break
    值2)
        语句1
        语句2
        ...
        ;;
    *)  # 类似于C/C++中的default
        语句1
        语句2
        ...
        ;;
esac

示例:

输入一个数字,然后转化为对应的英语单词

➜  ch07 vim number
#! /bin/bash 
#
# Translate a didgit to English
# 

if [ "$#" -ne 1 ]
then 
    echo "Usage: number digit"
    exit 1
fi

number=$1

case $number in
    0)
        echo zero
        ;;
    1) 
        echo one
        ;;
    2) 
        echo two
        ;;
    3)
        echo three
        ;;
    4)
        echo four
        ;;
    5)
        echo five
        ;;
    6)
        echo six
        ;;
    7)
        echo seven
        ;;
    8)
        echo eight
        ;;
    9)
        echo nine
        ;;
    *)
        echo "Bad argumnet; please specify a single digit"
        ;;
esac

输出结果:

➜  ch07 ./number 1
one
➜  ch07 ./number 100
Bad argumnet; please specify a single digit
➜  ch07 ./number   
Usage: number digit

调试

一个用于识别数字、大小写字符、特殊字符的程序

# !/bin/bash
# 
# Ensure that only one character was types

# verify arguments
if [ "$#" -ne 1 ]
then 
    echo "Please type a single character"
exit 1
fi


char="$1"
numberchar=$(echo "$char" | wc -c)

if [ "$numberchar" -ne 1 ]
then
    echo "Please type a single character"
exit 1
fi

# classify it
case "$char" in
    [0-9])
        echo "digit"
        ;;
    [a-z])
        echo "lowercase letter"
        ;;
    [A-Z])
        echo "uppercase letter"
        ;;
        *)
        echo "special character"
        ;;
esac

但是,当我们执行之后,却发现不符合我们的预期

➜  ch07 ./ctype a                  
Please type a single character
➜  ch07 ./ctype 1
Please type a single character
➜  ch07 ./ctype '*'
Please type a single character

我们可以通过-x进行调试

➜  ch07 sh -x ./ctype a
+ '[' 1 -ne 1 ']'
+ char=a
++ echo a
++ wc -c
+ numberchar='       2' # echo "$name" | wc -c 输出为2???
+ '[' '       2' -ne 1 ']'
+ echo 'Please type a single character'
Please type a single character
+ exit 1

通过输出调试,我们发现一个特性:

echo "$name" | wc -c

统计的结果出了我们需要的统计字符外,还有一个是echo在行尾自带的不可见字符换行符,所以需要重新修改判断逻辑,最后执行结果符合预期:

➜  ch07 ./ctype '*'
special character
➜  ch07 ./ctype a  
lowercase letter
➜  ch07 ./ctype 1
digit

ch08循环

for…in…do…done

格式:

for var in val1 val2 val3
do
    语句1
    语句2
    ...
done

for ((…;…;…)) do…done

格式:

for ((expression; condition; expression))
do
    语句1
    语句2
done

while…do…done循环

格式:

while condition
do
    语句1
    语句2
    ...
done

until…do…done循环

格式:

until condition
do
    语句1
    语句2
    ...
done

ch09 数据的读取和打印

主要介绍如何使用read命令从终端或文件中读取数据,以及如何使用printf将数据格式化并输出

read 命令

格式:

read variables

例子:

编写一个具有复制功能的脚本,同时要求根据用户的自己的意愿判断是否需要覆盖文件

# Copy a file
if [ "$#" -ne 2 ]
then
    echo "Usage: mycp from to"
    exit 1
fi

from="$1"
to="$2"

# see if the destination file already exits

if [ -e "$to" ]
then
    echo "$to already exit; overwrite(yes/no)?" 
    read anwser

    if [ "$anwser" != yes ]
    then
        echo "Copy not performed"
        exit 0
    fi
fi

# either desitination doesn't exist or "yes" was typed

cp $from $to

==小技巧==

# 我们可以使用 \c 将echo中最后自带的换行符替换
echo "$to already exit; overwrite (yes/no)? \c"
字符 输出
\b Backspace
\c 忽略输出最后的换行符
\f 换页
\n 回车换行
\r 回车
\t Tab
\\ 反斜线
\0nnn ASCII值为nnn的字符,其中nnn是1~3份的八进制数

输出结果:

➜  ch09 git:(master) ✗ mycp.sh names numbers
numbers already exit; overwrite(yes/no)? no
Copy not performed

mycp的系列优化

  1. 当前的复制,没有检测to是否为一个目录,可能会导致我们的目标文件被移动到to目录中,所以需要有效的通知用户,同时将移动的文件名保持为愿文件名

    ```shell

    Copy a file

    if [ “$#” -ne 2 ]
    then

    echo "Usage: mycp from to"
    exit 1
    

    fi

    from=”$1”
    to=”$2”

    check to is a directory

    if [ -d “$to” ]; then # if better expression

    to="$to/$(basename $from)"
    

    fi

see if the destination file already exits

if [ -e “$to” ]
then
echo “$to already exit; overwrite(yes/no)?\c “
read anwser

   if [ "$anwser" != yes ]
   then
       echo "Copy not performed"
       exit 0
   fi

fi

either desitination doesn’t exist or “yes” was typed

cp $from $to


2. mycp支持可变参数。使其和`cp`更加接近

   ```shell
   # Copy a file
   # mycp final version 5
   numargs=$#
   filelist=
   copylist=

   while [ "$#" -gt 1 ]; do
       filelist="$filelist $1"
       shift
   done

   to="$1"

   # if less than two args, or if more than two ags and last args
   # is not a directory then issue an error message
   if [ "$numargs" -lt 2 -o "$numargs" -gt 2 -a ! -d "$to" ]; then
       echo "Usage: mycp file1 file2"
       echo "       mycp file(s) dir"
       exit 1
   fi

   for form in $filelist; do
       # judege $to is a directory
       if [ -d "$to" ]; then
           toFile="$to/$(basename $from)"
       else
           toFile="$to"
       fi

       # add file
       if [  -e "$toFile" ]; then
           echo "$toFile already exits; overwrite (yes/no)? \c"
           read anwser

           if [ "$anwser" = yes ]; then
               copylist="$copylist $from"
           fi
       else
           copylist="$copylist $from"
       fi
   done

   if [ -n "$copylist" ]; then
       cp $copylist $to
   fi

一个具有交互功能的电话簿脚本

#########################################################################
# File Name:    rolo.sh
# Author:       weikunkun
# mail:         kongwiki@163.com
# Created Time: 日 11/21 21:21:35 2021
#########################################################################
#!/bin/bash
# role - rolodex program to look up, add, and remove people form the phone book


# display menu
echo "
     Would you like to: 

        1. Look someone up
        2. Add someone to the phone book
        3. Remove someone from the phone book
        Please select one of the above (1 - 3): \c "

# read input and process selection

read choice
echo ""
case "$choice" in
    1) 
        echo "Enter name to look up: \c"
        read name
        lu "$name"
        ;;
    2)
        echo "Enter name to added: \c"
        read name
        add "$name"
        ;;
    3)
        echo "Enter name to be removed: \c"
        read name
        rem "$name"
        ;;
    *)
        echo "Bad choice"
        ;;
esac

输出:

➜  ch09 git:(master) ✗ rolo.sh 

     Would you like to: 

        1. Look someone up
        2. Add someone to the phone book
        3. Remove someone from the phone book
        Please select one of the above (1 - 3): 1

Enter name to look up: wkk
➜  ch09 git:(master) ✗ cat lu         
───────┬────────────────────────────────────────────────────────────────────────
       │ File: lu
───────┼────────────────────────────────────────────────────────────────────────
   1   │ # Look someone up in the phone book
   2   │ grep "$1" phonebook
───────┴────────────────────────────────────────────────────────────────────────
➜  ch09 git:(master) ✗ lu Alice  
➜  ch09 git:(master) ✗ rolo.sh   

     Would you like to: 

        1. Look someone up
        2. Add someone to the phone book
        3. Remove someone from the phone book
        Please select one of the above (1 - 3): 2

Enter name to added: kwk
➜  ch09 git:(master) ✗ rolo.sh 

     Would you like to: 

        1. Look someone up
        2. Add someone to the phone book
        3. Remove someone from the phone book
        Please select one of the above (1 - 3): 1

Enter name to look up: kwk
kwk

优化:

rolo大多数功能还是查找,当误参数的时候,默认使用lu进行查找,然后退出。同时使用util命令,进行优化,保证用户肯定有操作,避免选错其他字符导致程序结束

#########################################################################
# File Name:    rolo2.sh
# Author:       weikunkun
# mail:         kongwiki@163.com
# Created Time: 日 11/21 21:38:40 2021
#########################################################################
#!/bin/bash
# role - rolodex program to look up, add, and remove people form the phone book

if [ "$#" -ne 0 ]; then
    lu "$@"
    exit
fi

validchoice=""

until [ -n "$validchoice" ]; do
    # display menu
    echo "
    Would you like to: 

    1. Look someone up
    2. Add someone to the phone book
    3. Remove someone from the phone book
    Please select one of the above (1 - 3): \c "

    # read input and process selection

    read choice
    echo ""
    case "$choice" in
        1) 
            echo "Enter name to look up: \c"
            read name
            lu "$name"
            validchoice=TRUE
            ;;
        2)
            echo "Enter name to added: \c"
            read name
            echo "Enter number: \c"
            read number
            add "$name" "$number"
            validchoice=TRUE
            ;;
        3)
            echo "Enter name to be removed: \c"
            read name
            rem "$name"
            validchoice=TRUE
            ;;
        *)
            echo "Bad choice"
            ;;
    esac
done

输出:

➜  ch09 git:(master) ✗ cat phonebook 
───────┬─────────────────────────────────────────────────────────────────────────────────
       │ File: phonebook
───────┼─────────────────────────────────────────────────────────────────────────────────
   1   │ Alice Chebba    973-555-2015
   2   │ Barbara Swingle 201-555-9257
   3   │ Liz Stachiw     212-555-2298
   4   │ Susan Goldberg  201-555-7776
   5   │ Susan Topple    212-555-4932
   6   │ Tony Iannino    973-555-1295
───────┴─────────────────────────────────────────────────────────────────────────────────
➜  ch09 git:(master) ✗ rolo2.sh  Tony
Tony Iannino    973-555-1295
➜  ch09 git:(master) ✗ rolo2.sh      

    Would you like to: 

    1. Look someone up
    2. Add someone to the phone book
    3. Remove someone from the phone book
    Please select one of the above (1 - 3): 2

Enter name to added: Tong Du
Enter number: 999=-^H^H2332-332
➜  ch09 git:(master) ✗ rolo2.sh Tony 
Tony Iannino    973-555-1295
➜  ch09 git:(master) ✗ cat phonebook 
───────┬─────────────────────────────────────────────────────────────────────────────────
       │ File: phonebook
───────┼─────────────────────────────────────────────────────────────────────────────────
   1   │ Alice Chebba    973-555-2015
   2   │ Barbara Swingle 201-555-9257
   3   │ Liz Stachiw     212-555-2298
   4   │ Susan Goldberg  201-555-7776
   5   │ Susan Topple    212-555-4932
   6   │ Tong Du    9992332-332
   7   │ Tony Iannino    973-555-1295
───────┴─────────────────────────────────────────────────────────────────────────────────

printf命令

格式化输出

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

编写一个格式化数组的程序:

#########################################################################
# File Name:    align.sh
# Author:       weikunkun
# mail:         kongwiki@163.com
# Created Time: 日 11/21 22:00:55 2021
#########################################################################
#!/bin/bash
cat $* | 
while read number1 number2
do
    printf "%20d %20d \n" $number1 $number2
done

输出:

➜  ch09 git:(master) ✗ cat data    
───────┬────────────────────────────────────────────────────────────────────────
       │ File: data
───────┼────────────────────────────────────────────────────────────────────────
   1   │ 95436928923 54398857123
   2   │ 96327974 978968345823
   3   │ 96324623375324 9745738523
   4   │ 962372735 96243642523
   5   │ 9764236246452352 952352234
   6   │ 91414124 545235
   7   │ 79861236 9461246213
   8   │ 9412376413264 23461275
   9   │ 52161273521 94612746123
  10   │ 9412341234674 941267451254162
───────┴────────────────────────────────────────────────────────────────────────
➜  ch09 git:(master) ✗ align.sh data     
         95436928923          54398857123 
            96327974         978968345823 
      96324623375324           9745738523 
           962372735          96243642523 
    9764236246452352            952352234 
            91414124               545235 
            79861236           9461246213 
       9412376413264             23461275 
         52161273521          94612746123 
       9412341234674      941267451254162

通过printf命令,其实我们可以优化下add程序,因为我们之前在phonebook中新增联系人,都要按照格式输入,这样才能保持格式的一致,现在可通过printf进行优化。

# Add someone to the phone book
# echo "$1    $2" >> phonebook
printf "%10s %20s" $1 $2 >> phonebook
sort -o phonebook phonebook

输出:

➜  ch09 git:(master) ✗ cat phonebook
➜  ch09 git:(master) ✗ add 孔维坤 999-999-9999
➜  ch09 git:(master) ✗ add 维坤坤 888-888-8888    
➜  ch09 git:(master) ✗ cat phonebook
───────┬────────────────────────────────────────────────────────────────────────
       │ File: phonebook
───────┼────────────────────────────────────────────────────────────────────────
   1   │  维坤坤         888-888-8888
   2   │  孔维坤         999-999-9999
   3   │ Alice Chebba    973-555-2015
   4   │ Barbara Swingle 201-555-9257
   5   │ Liz Stachiw     212-555-2298
   6   │ Susan Goldberg  201-555-7776
   7   │ Susan Topple    212-555-4932
   8   │ Tony Iannino    973-555-1295
───────┴────────────────────────────────────────────────────────────────────────

ch10环境

当我们登录到系统中,我们其实使用的都是一个全新的shell副本。登录之后,shell提供的就是属于我们自己的环境,该环境从登录一直持续到退出系统。

Shell中变量的作用域

变量的作用域顾名思义就是说变量的有效范围。在不同的范围中,同名的变量不会互相干涉,但是在相同的作用域中,同名的变量就会互相干扰,这也就是我们需要了解作用域的原因,避免变量之间的互相干扰。

shell中作用域共分为三种:

  • 变量仅在函数内部可以使用,称为局部变量
  • 变量可以在当前的Shell中使用, 称为全局变量
  • 变量除了在当前Shell进程中使用,还可以在其子进程中使用,称为环境变量

局部变量

正常编程中,函数内部的变量称之为局部变量。在Shell中略微不同的是,我们通过local声明的变量才能称之为局部变量。

例子:

➜  ch10 git:(master) ✗ cat func.sh            
───────┬─────────────────────────────────────────────────────────────────────────────────
       │ File: func.sh
───────┼─────────────────────────────────────────────────────────────────────────────────
   1   │ #########################################################################
   2   │ # File Name:    func.sh
   3   │ # Author:       weikunkun
   4   │ # mail:         kongwiki@163.com
   5   │ # Created Time: 一 11/22 19:15:39 2021
   6   │ #########################################################################
   7   │ #!/bin/bash
   8   │ 
   9   │ function func() {
  10   │     a=99
  11   │ }
  12   │ 
───────┴─────────────────────────────────────────────────────────────────────────────────
➜  ch10 git:(master) ✗ func.sh
➜  ch10 git:(master) ✗ echo $x
100
➜  ch10 git:(master) ✗ cat func.sh 
───────┬─────────────────────────────────────────────────────────────────────────────────
       │ File: func.sh
───────┼─────────────────────────────────────────────────────────────────────────────────
   1   │ #########################################################################
   2   │ # File Name:    func.sh
   3   │ # Author:       weikunkun
   4   │ # mail:         kongwiki@163.com
   5   │ # Created Time: 一 11/22 19:15:39 2021
   6   │ #########################################################################
   7   │ #!/bin/bash
   8   │ 
   9   │ function func() {
  10   │     local a=99
  11   │ }
  12   │ 
───────┴─────────────────────────────────────────────────────────────────────────────────
➜  ch10 git:(master) ✗ func.sh
➜  ch10 git:(master) ✗ echo $x

全局变量

所谓的全局变量,就是在当前的Shell进程中都有效,每个进程都有自己的作用域,彼此之间互不影响。在当前Shell中定义的变量,默认为全局变量。

需要注意的是,全局变量的作用域当前的Shell进程,不是当前的Shell脚本文件,这也就解释了,上述的例子中,最开始未加local关键字的示例中,func.sh中定义的变量x,在该Shell脚本外,仍能被正确调用,因为变量x是一个全局变量

当我们打开多个Shell窗口时,就创建了多个Shell进程。每个Shell进程都是独立的。同时我们通过source命令可以引入多个脚本,然后全局变量对这些脚本都有效。

b = 200
➜  ch10 git:(master) ✗ cat a.sh
───────┬─────────────────────────────────────────────────────────────────────────────────
       │ File: a.sh
───────┼─────────────────────────────────────────────────────────────────────────────────
   1   │ #########################################################################
   2   │ # File Name:    a.sh
   3   │ # Author:       weikunkun
   4   │ # mail:         kongwiki@163.com
   5   │ # Created Time: 一 11/22 19:54:12 2021
   6   │ #########################################################################
   7   │ #!/bin/bash
   8   │ echo "a = $a"
   9   │ b=200
───────┴─────────────────────────────────────────────────────────────────────────────────
➜  ch10 git:(master) ✗ cat b.sh
───────┬─────────────────────────────────────────────────────────────────────────────────
       │ File: b.sh
───────┼─────────────────────────────────────────────────────────────────────────────────
   1   │ #########################################################################
   2   │ # File Name:    b.sh
   3   │ # Author:       weikunkun
   4   │ # mail:         kongwiki@163.com
   5   │ # Created Time: 一 11/22 19:54:25 2021
   6   │ #########################################################################
   7   │ #!/bin/bash
   8   │ echo "b = $b"
───────┴───────────────────────────────────────────────────────────────────────────────
➜  ch10 git:(master) ✗ a=99   
➜  ch10 git:(master) ✗ source a.sh 
a = 99
➜  ch10 git:(master) ✗ source b.sh 
b = 200

这三条命令都是在一个进程中执行的,从输出结果可以发现,在 Shell 窗口中以命令行的形式定义的变量 a,在 a.sh 中有效;在 a.sh中定义的变量 b,在 b.sh 中也有效,变量 b 的作用范围已经超越了 a.sh

注意:这里是使用source的方式执行的脚本,效果是在当前进程下执行,而如果使用bash a.sh这种方式执行脚本就不会有上述结果,因为这种方式执行脚本是在子进程下执行的,具体执行方式的区别参考这篇文章

全局变量冲突:

虽然通过source可以打通所有脚本,共用多个全局变量,但是有些问题需要我们注意:

  • 写了一个函数脚本,名为qc.sh
  • 在另一个脚本run.sh中对qc.sh进行了source执行

qc.sh截取:

复制# func one
func_1(){
  for i in $(ls ${local_variable}/*_test.zip)
  do 
    mv ${i} something
  done
}

# func two
func_2(){
  for i in $(ls ${local_variable_2}/*_test.zip)
  do 
    mv ${i} something_2
  done
}

run.sh截取:

复制for i in "190915_data"
do
  func_1  ${results_folder}/${i}/${j}/qc
  func_2  ${results_folder}/${i}/${j}/trim ${results_folder}/${i}/${j}/trim
done

最后程序在执行func_2时报错,而func_1正常执行,检查日志发现func_2传递的参数有误,其中的${i}部分变成了func_1for循环最终停止的i,已经不是目标的190915_datasource方式执行脚本会打通全局变量,所以func_1执行之后由于其中的i变量不是局部变量,所以其作用域是整个脚本,并且会对之前的变量i的值进行覆盖,从而导致其值变为了func_1for循环最终停止的i,并将其传递给了第二个函数func_2,最终导致出错。

环境变量

全局变量只能在当前的Shell进程中有效,若是还想在子进程中仍然有效,就需要使用到环境变量,其中环境变量又称为临时环境变量永久环境变量

  • 如果是使用export修饰的变量,则改变量在所有的子进程也都有效了,称为临时环境变量
  • 如果是将变量写在配置文件中,譬如/etc/profile、~/.profile、/etc/zsh/zprofile中,在所有的Shell进程中都有效了,称为永久环境变量

临时环境变量:

创建Shell子进程最简单的方法就是zsh命令,退出Shell子进程的方法是exit命令(采用的是zsh):

➜  ch10 git:(master) ✗ echo $$
29625
➜  ch10 git:(master) ✗ echo $$
36389
➜  ch10 git:(master) ✗ exit   
➜  ch10 git:(master) ✗ echo $$
29625
➜  ch10 git:(master) ✗ a=22            
➜  ch10 git:(master) ✗ echo $a         
22
➜  ch10 git:(master) ✗ zsh   
➜  ch10 git:(master) ✗ echo $a

➜  ch10 git:(master) ✗ exit   
➜  ch10 git:(master) ✗ export a
➜  ch10 git:(master) ✗ zsh      
➜  ch10 git:(master) ✗ echo $a
22

永久环境变量:

export TERM=xterm-256color
ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=208'
clear
export PATH="/usr/local/opt/redis@3.2/bin:$PATH"
export PATH="/usr/local/opt/tcl-tk/bin:$PATH"
export JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk-11.0.12.jdk/Contents/Home"
export PATH=$JAVA_HOME/bin:$PATH

# Go PATH
export GOROOT=/usr/local/go
export PATH=$GOROOT/bin:$PATH

# 添加当前位置到环境变量
export PATH=.:$PATH

# Aliases
alias cat="/usr/local/bin/bat"
alias grep="grep --color"

对于永久环境变量, 其加载顺序如下(以bash为例):

加载顺序

PS1、PS2

PS1用于终端的提示符,PS2用于命令中输入的长度多于一行后的辅助提示符。

HOME、PATH

HOME用于表示当前用户登录所处的位置。

➜  ch10 git:(master) ✗ echo $HOME
/Users/weikunkun

PATH用于记录当前系统的环境变量。

➜  ch10 git:(master) ✗ echo $PATH 
.:/usr/local/go/bin:/Library/Java/JavaVirtualMachines/jdk-11.0.12.jdk/Contents/Home/bin:/usr/local/opt/tcl-tk/bin:/usr/local/opt/redis@3.2/bin:/Users/weikunkun/bin:/usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/opt/apache-maven-3.6.3/bin:/usr/local/mysql/bin

在命令中,输入的命令,Shell会搜索PATH列出的目录,知道找到可以匹配的可执行文件。所以想要最先搜索当前目录,可以将.放到当前$PATH的最开始(注意取舍安全性)

rolo再优化

通过上述的学习,我们会发现rolo还能继续优化

  1. 无法在任意位置执行rolo
  2. 所有用户都可以执行rolo之后,可能当前用户想操作的是自己的phonebook而不是其他人的,所以我们需要创建一个能够面向多用户的phonebook
#########################################################################
# File Name:    rolo.sh
# Author:       weikunkun
# mail:         kongwiki@163.com
# Created Time: 一 11/22 21:09:23 2021
#########################################################################
#!/bin/bash
PHONEBOOK=$HOME/phonebook 
export PHONEBOOK

if [ ! -f "$PHONEBOOK" ]; then
    echo "No phone book file in $HOME"
    exit 1
fi

# 如果提供了参数,则执行查询操作
if [ "$#" -ne 0 ]; then
    lu "$@"
    exit
fi

validchoice=""

# 循环 直到用户已经做出有效选择
until [ -n "$validchoice" ]; do
    # 显示菜单
    echo "
    Would you like to:

        1. Look someone up
        2. Add someone to the phone book
        3. Remove someone fomr the phone book
    Please select one of the above (1-3): \c
    "

    # 查找
    read choice
    echo

    case "$choice" in
        1) 
            echo "Enter name to look up: \c"
            read name
            lu "$name"
            validchoice=TRUE
            ;;
        2)
            echo "Enter name to be added: \c"
            read name
            echo "Enter number: \c"
            read number
            add $name $number
            validchoice=TRUE
            ;;
        3)
            echo "Enter name to be removed: \c"
            read name
            read number
            rem "$name"
            validchoice=TRUE
            ;;
        *)
            echo "Bad choic"
            ;;
    esac
done

执行结果:

➜  ch10 git:(master) ✗ rolo 孔维坤     
           孔维坤         111-111-1111
➜  ch10 git:(master) ✗ rolo       

    Would you like to:

        1. Look someone up
        2. Add someone to the phone book
        3. Remove someone fomr the phone book
    Please select one of the above (1-3): 1

Enter name to look up: 孔维坤
           孔维坤         111-111-1111
➜  ch10 git:(master) ✗ rolo       

    Would you like to:

        1. Look someone up
        2. Add someone to the phone book
        3. Remove someone fomr the phone book
    Please select one of the above (1-3): 2

Enter name to be added: 小坤坤
Enter number: 999-999-9999
➜  ch10 git:(master) ✗ cat $HOME/phonebook
───────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: /Users/weikunkun/phonebook
───────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │            孔维坤         111-111-1111
   2   │            维坤坤         222-222-2222
   3   │            韩欢欢         333-333-3333
   4   │            小坤坤         999-999-9999
───────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

ch11参数

该章节主要详细阐述了各种参数的操作

  1. 位置参数(行参)
  2. 特殊参数($#、$?)
  3. 关键字参数(普通变量)

${paramter}

这种形式的内容主要用于当前应用包含过多的参数(10个及以上)

${10}
${11}

进行替换:

mv ${file} ${file}x

${paramter:-value}

类似于getOrdeDefault的功能,如果当前paramter不为空,则使用它的值,否则就是用value。

示例:

➜  book git:(master) echo Using editor ${EDITOR:-$(which vim)}
Using editor /usr/local/bin/vim

其并不会对当前的paramter进行覆盖操作,如果不存在则会赋值我们指定的默认值,否则不进行任何操作,类似如下的if-else判断

#########################################################################
# File Name:    usevim.sh
# Author:       weikunkun
# mail:         kongwiki@163.com
# Created Time: 二 11/23 16:21:22 2021
#########################################################################
#!/bin/bash
if [ -n "$EDITOR" ]; then
    echo "Using editor $EDITOR"
else
    echo "Using editor $(which vim)"
fi

示例:

➜  book git:(master) ✗ EDITOR=$(which ed)
➜  book git:(master) ✗ echo ${EDITOR:-$(which vim)}             
/bin/ed
➜  book git:(master) ✗ EDITOR=           
➜  book git:(master) ✗ echo ${EDITOR:-$(which vim)}
/usr/local/bin/vim
➜  book git:(master) ✗

${paramter:=value}

该操作类似直接对变量进行赋值。当然我们无法使用这种形式,对位置参数进行赋值(paramter不能是数字)。如果想要将其作为一个命令,需要做如下操作

: ${paramter:=value}
➜  ch10 : ${PHONEBOOK:=$HOME/phonebook}
➜  ch10 echo $PHONEBOOK
/root/phonebook
➜  ch10 : ${PHONEBOOK:=aaa}
➜  ch10 echo $PHONEBOOK
/root/phonebook

这种形式的用法一般在条件语句或者echo语句中第一次使用变量的时候会使用

${paramter:?value}

如果paramter不为空,Shell会进行替换,否则写入到标准错误中。和三目运算类似

${paramter:+value}

和-value相反,如果paramter不为空,则替换成value,否则不进行任何操作

➜  ch10 traceopt=T
➜  ch10 echo options: ${traceopt:+"Trae mode"}
options: Trae mode
➜  ch10 traceopt=
➜  ch10 echo options: ${traceopt:+"Trae mode"}
options:

set

作用:

  1. 设置各种Shell选项
  2. 重新为$1、$2、$3等位置参数赋值

示例:

➜  ch11 git:(master) ✗ set one two three four          
➜  ch11 git:(master) ✗ echo $1 $2 $3 $4
one two three four
➜  ch11 git:(master) ✗ echo $#                         
4
➜  ch11 git:(master) ✗ echo $@                         
one two three fou

编写一个word程序,用于统计一行中的单词个数

➜  ch11 git:(master) ✗ cat word.sh          
───────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: word.sh
───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ #########################################################################
   2   │ # File Name:    word.sh
   3   │ # Author:       weikunkun
   4   │ # mail:         kongwiki@163.com
   5   │ # Created Time: 六 11/27 15:45:22 2021
   6   │ #########################################################################
   7   │ #!/bin/bash
   8   │ read line
   9   │ set $line
  10   │ echo $# # 输出参数个数
───────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────

执行结果:

➜  ch11 git:(master) ✗ word.sh
Hello world my name is kwk
6

一些以外结果:

  1. 当我们不做任何输入时

    ➜  ch11 git:(master) ✗ word.sh
    
    AUTOJUMP_ERROR_PATH=/Users/weikunkun/Library/autojump/errors.log
    AUTOJUMP_SOURCED=1
    BASH=/bin/sh
    BASH_ARGC=()
    BASH_ARGV=()
    BASH_LINENO=([0]="0")
    BASH_SOURCE=([0]="word.sh")
    BASH_VERSINFO=([0]="3" [1]="2" [2]="57" [3]="1" [4]="release" [5]="x86_64-apple-darwin21")
    BASH_VERSION='3.2.57(1)-release'
    DIRSTACK=()
    EUID=501
    GOROOT=/usr/local/go
    GROUPS=()
    HOME=/Users/weikunkun
    ...
    # 不做任何输入时,set就会执行无参数的执行,也就是输出所有的环境变量
    
  2. 输入以-开头的内容

    ➜  ch11 git:(master) ✗ word.sh
    -11111+423
    word.sh: line 9: set: -1: invalid option
    set: usage: set [--abefhkmnptuvxBCHP] [-o option] [arg ...]
    # 将-1视为了一个选项, 然后因为并未有任何设置,所以报错
    

解决方式:

  • 使用--解决无参数输入时,打印当前所有的参数
  • 使用while,实现正常统计单词数量
➜  ch11 git:(master) ✗ vim word.sh 
count=0
while read line
do
    set -- $line
    count=$((count + $#))
done
echo $count
➜  ch11 git:(master) ✗ word.sh < /etc/passwd 
329

IFS

作用:

指定分隔符,在Shell解析输入、命令替换、输出以及执行变量替换时,就会使用IFS指定的分隔符

示例:

➜  ch11 git:(master) ✗ IFS=:           
➜  ch11 git:(master) ✗ read x y z      
123:456:789
➜  ch11 git:(master) ✗ echo $x $y $z   
123 456 789
➜  ch11 git:(master) ✗ list="one:two:three"            
➜  ch11 git:(master) ✗ for x in $list; do echo $x; done
one
two
three

## set和IFS的搭配示例

ch12拓展内容

wait命令

和父进程调用waitpid类似,在Shell中通过wait,当前Shell等待子Shell完成后,后续操作

使用:

wait process=id

例子:

➜  ch11 git:(master) ✗ sort big-data > sorted_data &
[1]  29625
➜  ch11 git:(master) ✗ echo $$         
➜  ch11 git:(master) ✗ wait 29625

trap命令

我们在编写Shell程序时,当发现运行不符合预期时,会直接中断执行,一般来说没啥问题,但是期间运行的一些临时文件就会特别多,所以我们可以使用trap进行操作

信号 助记名 产生原因
0 EXIT 退出SHELL
1 HUP 挂起
2 INT 中断(Ctrl + c)
15 TERM 软终端信号(默认kill)

实例:

trap "rm $WORKDIR/work1$$ $WORKDIR/dataout$$; eixt" INT

I/O内容

我们已经知道

  1. < 输出重定向
  2. > 输出重定向
  3. >> 追加输出重定向

同时我们也可以使用2>来实现标准错误的重定向

command 2> file

有时我们也可以这样

command >&2

>&指名将输出重定向到和指定文件描述符相关联的文件中。

  1. 文件描述符0表示标准输入
  2. 文件描述符1对应标准输出
  3. 文件描述符2对应标准错误

示例:

echo "Invalid number of arguments" >&2
command > foo 2 >> foo #将标准输出和标准错误输出到同一个文件
command > foo 2>&1 # 和上述同理

ch13 rolo终极进化版

  1. 不再强行要求一开始用户的主目录下有phone book,同时会检测PHONEBOOK变量是否被设置,如果设置,则不再进行设置,如果没有设置,则将其设置为$HOME/phonebook
  2. 检测phonebook文件是否存在,不存在进行交互,是否创建
  3. 新增选项:display功能
  4. 做了撤销判断
  5. 抽出add脚本,做更好的交互
  6. 对于查找、删除、修改选项,做了判空判断

rolo

使用export设置临时环境环境变量,同时PHONEBOOK使用${paramter:=variable}的形式进行设置(变量已设置不做任何操作,未设置,通过交互判断是新建文件)

#########################################################################
# File Name:    rolo.sh
# Author:       weikunkun
# mail:         kongwiki@163.com
# Created Time: 一 11/22 21:09:23 2021
#########################################################################
#!/bin/bash
# 如果PHONEBOOK已经设置,不做任何变动
: ${PHONEBOOK:=$HOME/phonebook} 
export PHONEBOOK

if [ ! -e "$PHONEBOOK" ]; then
    echo "$PHONEBOOK does not exit"
    echo "Should I create it for you (y/n)? \c"
    read anwser
    if [ "$anwser" != y ]; then
        exit 1
    fi

    >> $PHONEBOOK || exit 1 # 创建失败 退出
fi

# 如果提供了参数,则执行查询操作
if [ "$#" -ne 0 ]; then
    lu "$@"
    exit
fi


# 设置中断信号
trap "continue" SIGINT

# 循环 直到用户已经做出有效选择
while true; do
    # 显示菜单
    echo "
    Would you like to:

        1. Look someone up
        2. Add someone to the phone book
        3. Remove someone fomr the phone book
        4. Change an entry in the phone book
        5. List all names and numbers in the phone
        6. Exit this program
    Please select one of the above (1-6): \c

    "

    # 查找
    read choice
    echo

    case "$choice" in
        1) 
            echo "Enter name to look up: \c"
            read name
            # 判断是否为空
            if [ -z "$name" ]; then
                echo "Lookup ingnored"
            else
                lu "$name"
            fi
            ;;
        2)
            add            
            ;;
        3)
            echo "Enter name to be removed: \c"
            read name
            if [ -z "$name" ]; then
                echo "Remove all ignored"
            else
                rem "$name"
            fi
            ;;
        4)
            echo "Enter name to change: \c"
            read name
            if [ -z "$name" ]; then
                echo "Change ignored"
            else
                change "$name"
            fi
            ;;
        5)
            listall
            ;;
        6)
            exit 0
            ;;
        *) 
            echo "Bad choice"
            ;;
    esac
done

add

#########################################################################
# File Name:    add.sh
# Author:       weikunkun
# mail:         kongwiki@163.com
# Created Time: 一 11/22 21:19:22 2021
#########################################################################
#!/bin/bash
echo "Type in you new entry"
echo "When you are done, type just a single Enter on the line"

first=
entriy=

while true; do
    echo ">> \c"
    read line

    if [ -n "$line" ]; then
        entry="$entry$line^"

        if [ -z "$first" ]; then
            first=$line
        fi
    else
        break
    fi
done

echo "$entry" >> $PHONEBOOK
sort -o $PHONEBOOK $PHONEBOOK
echo 
echo "$first has been added to the phone book"

rem

#########################################################################
# File Name:    rem.sh
# Author:       weikunkun
# mail:         kongwiki@163.com
# Created Time: 一 11/22 21:23:32 2021
#########################################################################
#!/bin/bash
# 删
name=$1

grep -i "$name" $PHONEBOOK > /tmp/matches$$

if [ ! -s /tmp/matches$$ ]; then
    echo "I can not find $name int the phone book"
    exit 1
fi

while read line
do
    display "$line"
    echo "Remove this entry (y/n) ? \c"
    read anwser < /dev/tty 

    if [ "$anwser" = y ]; then
        break
    fi

done < /tmp/matches$$

rm /tmp/matches$$

if [ "$anwser" = y ]; then
    if grep -i -v "^$line$" $PHONEBOOK > /tmp/phonebook$$
    then
        mv /tmp/phonebook$$ $PHONEBOOK
        echo "Selected entry has been removed"
    elif [ ! -s $PHONEBOOK ]; then
        echo "Note: You now have an empty phone book"
    else
        echo "Entry not removed"
    fi
fi

lu

#########################################################################
# File Name:    lu.sh
# Author:       weikunkun
# mail:         kongwiki@163.com
# Created Time: 一 11/22 21:21:17 2021
#########################################################################
#!/bin/bash
# 查

name="$1"
grep "$name" $PHONEBOOK > /tmp/matches$$

if [ ! -s /tmp/matches$$ ]; then
    echo "I can not fine $name in the phone book"
else
    while read line; do
        display "$line"
    done < /tmp/matches$$
fi

rm /tmp/matches$$

change

#########################################################################
# File Name:    change.sh
# Author:       weikunkun
# mail:         kongwiki@163.com
# Created Time: 二 11/23 22:00:55 2021
#########################################################################
#!/bin/bash

name=$1

grep -i "$name" $PHONEBOOK > /tmp/mathches$$
if [ ! -s /tmp/mathches$$ ]; then
    echo "I can not find $name in the phone book"
    exit 1
fi

# 显示匹配出联系人的信息并确认是否要修改
while read line
do
    display "$line"
    echo "Change this entry (y/n) ? \c"
    read anwser < /dev/tty 

    if [ "$anwser" = y ]; then
        break
    fi

done < /tmp/mathches$$

rm /tmp/mathches$$

if [ "$anwser" != y ]; then
    exit
fi


# 针对已经确定修改的联系人 启动编辑器
echo "$line\c" | tr '^' '\012' > /tmp/ed$$

echo "Enter changes whith ${EDITOR:=$(which vim)}"
trap "" 2 # 撤销也不终止
$EDITOR /tmp/ed$$

# 删除旧信息,插入新信息
grep -i -v "^$line$" $PHONEBOOK > /tmp/phonebook$$
{ tr '\012' '^' < /tmp/ed$$; echo; } >> /tmp/phonebook$$

# 最后的echo被回收到tr转换后的结尾换行符
sort /tmp/phonebook$$ -o $PHONEBOOK
rm /tmp/ed$$ /tmp/phonebook$$

listall

#########################################################################
# File Name:    listall.sh
# Author:       weikunkun
# mail:         kongwiki@163.com
# Created Time: 二 11/23 22:09:11 2021
#########################################################################
#!/bin/bash
IFS='^'
echo "-----------------------------------------------------"
while read line
do
    # 获取第一个和最后一个字段
    set $line

    # 显示第一个和最后一个字段(相反的顺序)
    eval printf "\"%-40.40s %s\\n\"" "\"$1\"" "\"\${$#}\""
done < $PHONEBOOK
echo "-----------------------------------------------------"

display

#########################################################################
# File Name:    display.sh
# Author:       weikunkun
# mail:         kongwiki@163.com
# Created Time: 二 11/23 21:50:53 2021
#########################################################################
#!/bin/bash
echo

echo "--------------------------------------"
entry=$1
IFS="^"
set $entry


for line in "$1" "$2" "$3" "$4" "$5" "$6"
do
    printf "| %-34.34s |\n" $line
done
echo "|    o                           o   |"
echo "--------------------------------------"
echo

实战小项目—-一键配置助手

有时候,我们在切换不同设备时,对于同类型的环境,经常需要我们进行重复的配置,由此,个人计划编写一个配置助手,用于一键配置自己所需要的环境

详见我的配置中心

#########################################################################
# File Name:    configs.sh
# Author:       weikunkun
# mail:         kongwiki@163.com
# Created Time: Tue 16 Nov 2021 04:20:09 PM CST
#########################################################################
#!/bin/bash

echo ""
echo " ========================================================= "
echo " |            Linux init.sh 环境部署脚本 V 1.1           | "
echo " ========================================================= "
echo "                      author:weikunkun                    "
echo "                 https://github.com/Winniekun              "
echo -e "\n"

# 由于学艺不精,经常把自己的服务器搞崩,并且定位不到root case
# 所以经常会重装系统,痛定思痛,决定写一个一劳永逸的初始化安装配置脚本
# LEVEL=base/dev/hacker/full
#       base: 基础配置(zsh、vim、git、wget、curl等)
#       dev: 开发环境配置(Java、Go、Kafka、MySQL、Redis等)
#       hacker: 常见的网络安全工具(sqlmap、nmap、httpx、xray等)
#       full: 一把梭哈

LEVEL='full'
CUR_PATH=$(pwd)

# zsh & oh-my-zsh安装
config_zsh() {
    if command -v zsh >/dev/null 2>&1
    then
        echo -e "检测到zsh 已安装"
    else 
        apt install -y zsh >/dev/null 2>&1
    fi    
    # install_ohmyzsh
    sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
    # sh -c "$(wget -O- https://pastebin.com/raw/FAFw08G6)"  
    echo -e "shell切换为zsh"
    chsh -s $(which zsh)
    # 设置主题,新增插件
    sed -i 's@ZSH_THEME="robbyrussell"@ZSH_THEME="awesomepanda"@g' ~/.zshrc 
    sed -i 's@plugins=(.*)@plugins=(git extract zsh-syntax-highlighting autojump zsh-autosuggestions)@g' ~/.zshrc
    {
        # 使用bat替代cat 
        echo 'alias cat="/usr/bin/batcat"'
        echo 'alias myip="curl ifconfig.io/ip"'
        echo 'alias c=clear'
    } >> ~/.zshrc

    if [ -d "$HOME/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting" ]
    then
        echo -e "zsh-highlighting 已经安装"
    else
        echo -e "下载安装zsh-highlighting"
        git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
    fi

    if [ -d "$HOME/.oh-my-zsh/custom/plugins/zsh-autosuggestions" ]
    then
        echo -e "zsh-autosuggestions 已经安装"
    else
        echo -e "下载安装zsh-autosuggestions"
        git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
    fi

    # 声明终端类型
    echo "export TERM=xterm-256color" >> ~/.zshrc    
    # 设置建议命令的颜色
    echo ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=10'" >> ~/.zshrc     
    # 重载配置
    source ~/.zshrc 
}
# oh-my-zsh安装
install_ohmyzsh() {
    echo -e "开始配置oh-my-zsh"
    if [ -d "$HOME/.oh-my-zsh/" ]
    then
        test
    fi
    if [ -e "install.sh" ] 
    then
        rm install.sh
    fi    

     wget -O install.sh https://pastebin.com/raw/FAFw08G6           
    sed -i 's@REPO=${REPO:-ohmyzsh/ohmyzsh}@REPO=${REPO:-mirrors/oh-my-zsh}@g' install.sh
    sed -i 's@REMOTE=${REMOTE:-https://github.com/${REPO}.git}@REMOTE=${REMOTE:-https://gitee.com/${REPO}.git}@g' install.sh

    sh install.sh
}
# Vim 安装 & 配置
install_vim() {
    if [ ! -d "$CUR_PATH/PowerVim" ] 
    then        
        git clone https://github.com/youngyangyang04/PowerVim.git
    else
        VIM_PATH=$CUR_PATH/PowerVim
        cd $VIM_PATH && sh install.sh
        # 一些问题修复 (语言问题、ctag等)
        fix_powervim
    fi
    cd $CUR_PATH
}
# PowerVim的一些小问题修复
fix_powervim() {
    # 系统中添加中文包
    echo -e "修复PowerVim一些小问题ing"
    LOCAL_FILE=/var/lib/locales/supported.d/local
    if [ ! -e "$LOCAL_FILE" ]
    then
        echo -e "创建 $LOCAL_FILE"
        touch $LOCAL_FILE    
    else 
        rm $LOCAL_FILE
    fi
    {
        echo "en_US.UTF-8 UTF-8"
        echo "zh_CN.UTF-8 UTF-8"
        echo "zh_CN.GBK GBK"
        echo "zh_CN GB2312"
    } >> $LOCAL_FILE
sudo locale-gen
{
    echo 'let Tlist_Show_One_File=1 "不同时显示多个文件的tag,只显示当前文件的'
    echo 'let Tlist_Exit_OnlyWindow=1 "如果taglist窗口是最后一个窗口,则退出vim'
    echo 'let Tlist_Ctags_Cmd="/usr/bin/ctags" "将taglist与ctags关联'
} >> ~/.vimrc
echo -e "修复成功"
}
# Git 安装 & 配置
intall_git() {
    if command -v git >/dev/null 2>&1
    then 
        # 配置
        echo -e "检测到已经安装Git,开始Git配置"
        config_git
    else
        # 安装 
        apt install -y git >/dev/null 2>&1
        # 配置
        echo -e "开始Git配置"
        config_git
    fi
}
# git相关的配置
config_git() {
    # 用户信息
    git config --global user.name "weikunkun"
    git config --global user.email "kongwiki@163.com"
    # core
    git config --global core.editor vim   
    # color
    git config --global color.ui true
    git config --global color.status "auto"
    git config --global color.branch "auto"
    # merge
    git config --global merge.tool "vimdiff"
    # alias
    git config --global alias.co "checkout"
    git config --global alias.br "branch"  
    git config --global alias.ci "commit"
    git config --global alias.st "status"
    git config --global alias.last "log -1 HEAD"
}
# python3 配置
install_python3() {
    if [ -d "$HOME/.pip" ]
    then
        echo -e "$HOME/.pip 已经创建"
    else
        mkdir ~/.pip/
    fi
    echo -e "[global]\n" >~/.pip/pip.conf
    # 替换PIP源 速度更快
    echo -e "index-url = https://pypi.tuna.tsinghua.edu.cn/simple" >>~/.pip/pip.conf
    echo -e "开始安装Python常见库"
    pip3 install lxml >/dev/null 2>&1
    pip3 install ipaddress >/dev/null 2>&1
    pip3 install python-dateutil >/dev/null 2>&1
    pip3 install apscheduler >/dev/null 2>&1
    pip3 install mycli >/dev/null 2>&1
    pip3 install aiohttp >/dev/null 2>&1
    pip3 install datetime >/dev/null 2>&1
    pip3 install timeit >/dev/null 2>&1
    pip3 install docker-compose >/dev/null 2>&1
    pip3 install chardet >/dev/null 2>&1
    pip3 install supervisor >/dev/null 2>&1
    pip3 install python-dateutil >/dev/null 2>&1
    pip3 install requests >/dev/null 2>&1
}
# Java 配置
install_java() {
    echo -e "开始配置Java环境"
    if command -v java >/dev/null 2>&1 
    then
        # 已经配置,不做操作
        echo -e "Java环境已经配置,即将跳过"
        test 
    else 
        echo -e "手动配置Java环境"
        if [ ! -e "jdk-11_linux-x64_bin.tar.gz" ]
        then
            wget https://repo.huaweicloud.com/java/jdk/11+28/jdk-11_linux-x64_bin.tar.gz
        fi
        tar -xzvf jdk-11_linux-x64_bin.tar.gz -C /opt >/dev/null 2>&1
        echo "export JAVA_HOME=/opt/jdk-11" >> /etc/zsh/zprofile 
        echo "export PATH=${JAVA_HOME}/bin:$PATH" >> /etc/zsh/zprofile 
        source /etc/zsh/zprofile
        echo -e "Java环境配置完成"
    fi
}
# 系统配置
base_config() {
    echo -e "apt install ag ..."
    apt install -y silversearcher-ag >/dev/null 2>&1
    echo -e "apt install zh-hans 语言库"
    apt install -y language-pack-zh-hans >/dev/null 2>&1     
    sudo apt-get install fonts-droid-fallback ttf-wqy-zenhei ttf-wqy-microhei fonts-arphic-ukai fonts-arphic-uming >/dev/null 2>&1
    ulimit -n 10240
    echo -e "开始配置随机SSH 端口"
    SSH_PORT=$(cat /etc/ssh/sshd_config | ag -o '(?<=Port )\d+')
    if [ $SSH_PORT -eq 22 ] 
    then
        SSH_NEW_PORT=$(shuf -i 10000-30000 -n1)
        echo -e "SSHD Port: ${SSH_NEW_PORT}" | tee -a ssh_port.txt
        sed -E -i "s/(Port|#\sPort|#Port)\s.{1,5}$/Port ${SSH_NEW_PORT}/g" /etc/ssh/sshd_config
    fi
    # apt更新
    echo -e "apt update ..."
    apt update >/dev/null 2>&1
    # 常用软件安装
    cmdline=(
        "which lsof"
        "which man"
        "which tmux"
        "which htop"
        "which autojump"
        "which iotop"
        "which ncdu"
        "which jq"
        "which telnet"
        "which p7zip"
        "which axel"
        "which rename"
        "which vim"
        "which sqlite3"
        "which lrzsz"
        "which git"
        "which curl"
        "which wget"
        "which bat"
        "which autojump"
    )
    for prog in "${cmdline[@]}"; do
        soft=$($prog)
        if [ "$soft" ] >/dev/null 2>&1; then
            echo -e "$soft 已安装!"
        else
            name=$(echo -e "$prog" | ag -o '[\w-]+$')
            apt install -y ${name} >/dev/null 2>&1
            echo -e "${name} 安装中......"
        fi
    done
    # git/vim/zsh/cur/wget配置
    echo -e "正在配置git"
    config_git
    echo -e "正在配置vim"
    install_vim
    echo -e "正在配置zsh"
    cd $CUR_PATH
    config_zsh
    echo -e "正在配置curl"
    #curl https://raw.githubusercontent.com/al0ne/vim-for-server/master/.curlrc >~/.curlrc >/dev/null 2>&1
    echo -e "正在配置wget"
    #curl https://raw.githubusercontent.com/al0ne/vim-for-server/master/.wgetrc >~/.wgetrc >/dev/null 2>&1
}
#  开发环境配置
dev_config() {
    # 常见库配置
    echo -e "apt-get install -y libgeoip1"
    apt-get install -y libgeoip1 >/dev/null 2>&1
    echo -e "apt-get install -y libgeoip-dev"
    apt-get install -y libgeoip-dev >/dev/null 2>&1
    echo -e "apt-get install -y openssl"
    apt-get install -y openssl >/dev/null 2>&1
    echo -e "apt-get install -y libcurl3-dev"
    apt-get install -y libcurl3-dev >/dev/null 2>&1
    echo -e "apt-get install -y libssl-dev"
    apt-get install -y libssl-dev >/dev/null 2>&1
    echo -e "apt-get install -y php"
    apt-get install -y php >/dev/null 2>&1
    echo -e "apt-get install -y net-tools"
    apt-get install -y net-tools >/dev/null 2>&1
    echo -e "apt-get install -y ifupdown"
    apt-get install -y ifupdown >/dev/null 2>&1
    echo -e "apt-get install -y tree"
    apt-get install -y tree >/dev/null 2>&1
    echo -e "apt-get install -y cloc"
    apt-get install -y cloc >/dev/null 2>&1
    echo -e "apt-get install -y python3-pip"
    apt-get install -y python3-pip >/dev/null 2>&1
    echo -e "apt-get install -y gcc"
    apt-get install -y gcc >/dev/null 2>&1
    echo -e "apt-get install -y gdb"
    apt-get install -y gdb >/dev/null 2>&1
    echo -e "apt-get install -y g++"
    apt-get install -y g++ >/dev/null 2>&1
    echo -e "apt-get install -y locate"
    apt-get install -y locate >/dev/null 2>&1
    echo -e "apt-get install -y shellcheck"
    apt-get install -y shellcheck >/dev/null 2>&1
    echo -e "apt-get install -y redis-cli"
    apt-get install -y redis-cli >/dev/null 2>&1
    echo -e "apt-get install -y redis-server"
    apt-get install -y redis-server >/dev/null 2>&1
    install_java
    install_python3
    if command -v docker >/dev/null 2>&1
    then
        echo "检测到已经安装docker 将跳过Docker的安装"
    else 
        echo -e "开始安装Docker"
        curl -fsSL https://get.docker.com -o get-docker.sh
    fi
    # Go 安装和配置
    if command -v go >/dev/null 2>&1
    then
        echo -e "检测到已经安装Go 将跳过Go的安装"
    else 
        echo -e "开始安装Go"
        sh -c "$(wget -O- https://raw.githubusercontent.com/canha/golang-tools-install-script/master/goinstall.sh)"
    fi
    # Nodejs 配置
    if command -v node >/dev/null 2>&1; then
        echo -e "检测到已安装Nodejs 将跳过!"
    else
        echo -e "开始安装Nodejs"
        curl -fsSL https://deb.nodesource.com/setup_10.x -o nodesource_setup.sh
        apt install -y nodejs >/dev/null 2>&1
    fi 
    # TODO Redis、MySQL
}
# 网安配置
hacker_config() {
    echo -e "正在安装 nmap ..."
    apt install -y nmap >/dev/null 2>&1
    echo -e "正在安装 netcat ..."
    apt install -y netcat >/dev/null 2>&1
    echo -e "正在安装 masscan ..."
    apt install -y masscan >/dev/null 2>&1
    echo -e "正在安装 zmap ..."
    apt install -y zmap >/dev/null 2>&1
    echo -e "正在安装 dnsutils ..."
    apt install -y dnsutils >/dev/null 2>&1
    echo -e "正在安装 wfuzz ..."
    pip install wfuzz >/dev/null 2>&1
    # 安装 weevely3
    if [ -d "/opt/weevely3" ]; then
        echo -e "检测到weevely3已安装将跳过"
    else
        echo -e "正在克隆 weevely3 ..."
        cd /opt && git clone https://github.com/epinna/weevely3.git >/dev/null 2>&1
        echo 'alias weevely=/opt/weevely3/weevely.py' >>~/.zshrc
    fi
    # 安装 whatweb
    if [ -d "/opt/whatweb" ]; then
        echo -e "检测到whatweb已安装将跳过"
    else
        echo -e "正在克隆 whatweb ..."
        cd /opt && git clone https://github.com/urbanadventurer/WhatWeb.git >/dev/null 2>&1
        echo 'alias whatweb=/opt/WhatWeb/whatweb' >>~/.zshrc
    fi
    # 安装 OneForAll
    if [ -d "/opt/OneForAll" ]; then
        echo -e "检测到OneForAll已安装将跳过"
    else
        echo -e "正在克隆 OneForAll ..."
        cd /opt && git clone https://github.com/shmilylty/OneForAll.git >/dev/null 2>&1
    fi
    # 安装 dirsearch
    if [ -d "/opt/dirsearch" ]; then
        echo -e "检测到dirsearch已安装将跳过"
    else
        echo -e "正在克隆 dirsearch ..."
        cd /opt && git clone https://github.com/shmilylty/OneForAll.git >/dev/null 2>&1
    fi
    # 安装 httpx
    if command -v httpx >/dev/null 2>&1; then
        echo -e "检测到已安装httpx 将跳过!"
    else
        echo -e "开始安装Httpx"
        GO111MODULE=on go get -u -v github.com/projectdiscovery/httpx/cmd/httpx >/dev/null 2>&1
    fi
    # 安装 subfinder
    if command -v subfinder >/dev/null 2>&1; then
        echo -e "检测到已安装subfinder 将跳过!"
    else
        echo -e "开始安装subfinder"
        GO111MODULE=on go get -u -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder >/dev/null 2>&1
    fi
    # 安装 nuclei
    if command -v nuclei >/dev/null 2>&1; then
        echo -e "检测到已安装nuclei 将跳过!"
    else
        echo -e "开始安装nuclei"
        GO111MODULE=on go get -u -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei >/dev/null 2>&1
    fi
    # 安装 naabu
    if command -v naabu >/dev/null 2>&1; then
        echo -e "检测到已安装 naabu 将跳过!"
    else
        echo -e "开始安装 naabu"
        GO111MODULE=on go get -u -v github.com/projectdiscovery/naabu/v2/cmd/naabu >/dev/null 2>&1
    fi
    # 安装 dnsx
    if command -v dnsx >/dev/null 2>&1; then
        echo -e "检测到已安装 dnsx 将跳过!"
    else
        echo -e "开始安装 dnsx"
        GO111MODULE=on go get -u -v github.com/projectdiscovery/dnsx/cmd/dnsx >/dev/null 2>&1
    fi
    # 安装 subjack
    if command -v subjack >/dev/null 2>&1; then
        echo -e "检测到已安装 subjack 将跳过!"
    else
        echo -e "开始安装 subjack"
        go get github.com/haccer/subjack >/dev/null 2>&1
    fi
    # 安装 ffuf
    if command -v ffuf >/dev/null 2>&1; then
        echo -e "检测到已安装 ffuf 将跳过!"
    else
        echo -e "开始安装 ffuf"
        go get -u github.com/ffuf/ffuf >/dev/null 2>&1
    fi
}
CUR_USER=$(whoami)
if [ $CUR_USER != 'root' ]
then
    echo "请切换到root用户再执行该脚本"
fi
if [ $LEVEL = 'base' ] 
then
    base_config
    chsh -s /bin/zsh 
fi
if [ $LEVEL = 'dev' ]
then
    base_config
    dev_config
    chsh -s /bin/zsh
fi
if [ $LEVEL = 'hacker' ] 
then
    hacker_config
fi
if [ $LEVEL = 'full'  ]
then
    base_config
    dev_config
    hacker_config
    chsh -s /bin/zsh
fi
echo -e "环境全部配置完成,开始玩儿吧"

TODO

  • [ ] zsh

    一直没有成功配置

  • [x] vim

  • [x] git

  • [x] Java

  • [ ] MySQL

  • [x] Redis

  • [ ] Kafka

开发中的遇到的问题

$@和$*在没有双引号括起来的时候行为是一样的,而当使用双引号括起来的时候是不一样的。

空值问题:

shell中可能经常能看到:>/dev/null 2>&1 

命令的结果可以通过%>的形式来定义输出

分解这个组合:“>/dev/null 2>&1” 为五部分。

1:> 代表重定向到哪里,例如:echo "123" > /home/123.txt
2:/dev/null 代表空设备文件
3:2> 表示stderr标准错误
4:& 表示等同于的意思,2>&1,表示2的输出重定向等同于1
5:1 表示stdout标准输出,系统默认值是1,所以">/dev/null"等同于 "1>/dev/null"

因此,>/dev/null 2>&1 也可以写成“1> /dev/null 2> &1”

那么本文标题的语句执行过程为:
1>/dev/null :首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,说白了就是不显示任何信息。
2>&1 :接着,标准错误输出重定向 到 标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。

tee

从标准输入读取数据并重定向到标准输出和文件。

 tee -a ssh_port.txt

shuf

产生随机的排列。

# 随机输出10000~30000内的一个数字
shuf -i 10000-30000 -n 1

判断文件是否存在

if [ -d "/filenam" ]
then 
    自己逻辑
if

references