Version: Next
CLI Basic
- 输出 hello world
- 支持自定义 name
- 带上时间戳
- 昵称
Demo——hello world
输出 hello world
use echo
(base) ➜ ~ echo hello worldhello worlduse function
code ~/.bashrc
在里面写
function h1() {echo hello $NAME}使用
source ~/.bashrc
刷新使用
h1
可以调用这个函数use script
- 定义一个文件
#!/bin/bashecho hello world
- 调用,需要把当前脚本文件所在目录添加到环境变量
- 以下命令表示将当前路径添加到 PATH 环境变量中
export PATH="$PWD:$PATH"
使用文件名调用 或 使用相对路径进行调用
查询命令定义位置
which {命令}
whereis {命令}
- which 只能查找 path 路径下的命令,whereis 没有以上限制
查询命令类型
type {命令}
查询命令解释手册
man {命令}
help {命令}
tldr {命令}
—— too long don't read,太长懒得读
Demo——支持自定义 name
- use function
- use script
- 名字包含空格怎么办
- 其他常见环境变量
为了不断的切换名字,很自然的想到定义一个变量
NAME
,结合函数,可以这么写,直接在 zsh 里敲function h1() {NAME=$1echo hello $NAME}
$1
表示传入的第一个参数,NAME 表示定义一个变量,$NAME 表示调用变量的值s#
表示传入参数的总个数$*
zsh中表示所有参数组成的数组,bash 中表示把所有参数封装成一个大字符串,中间的空格不是知道是谁的,bash 中$@
可以区分保留多个参数内部的空格,对于 zsh 而言由于是数组,没有必要专门用$@
- zsh 开发指南——函数与脚本
使用脚本,定义一个脚本文件
temp
#!/bin/bashNAME=$1echo hello $NAME
- 调用
(base) ➜ ./temp 123123123hello 123123123
Bug——参数带空格,会被当做两个参数处理
- 使用
$*
其他环境变量
(base) ➜ echo $HOME
/Users/banshaoxiong
(base) ➜ echo $SHELL
/bin/zsh
(base) ➜ echo $PWD
/Library/bsx/gtb
携带时间戳
(base) ➜ date "+%Y-%m-%d %H:%M:%S"
2022-04-05 23:24:40
- 结合脚本
- 不能在脚本里写个
date "+%Y-%m-%d %H:%M:%S"
,必须用$()
来取值,echo -n
表示不换行打印,这样时间戳和后续那个echo hello xxx
就会在同一行
- 不能在脚本里写个
#!/bin/bash
echo -n $(date "+%Y-%m-%d %H:%M:%S")
NAME=$*;
echo hello $NAME
(base) ➜ ./temp
2022-04-05 23:31:42hello
- 小 bug:发现时间戳和后续内容之间没有空格
#!/bin/bash
echo -n "$(date "+%Y-%m-%d %H:%M:%S") "
NAME=$*;
echo hello $NAME
(base) ➜ ./temp
2022-04-05 23:32:26 hello
最佳实践
#!/bin/bash
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
NAME=$*;
echo "[$TIMESTAMP]: hello $NAME"
(base) ➜ ./temp
[2022-04-05 23:34:30]: hello
自适应昵称
配置一个文件
name.list
,里面存储着账户
到昵称
的映射,供后续查询使用ban 班班jiayu.wang 小甜甜
- 从
name.list
文件中查询,使用grep
命令,进行过滤
(base) ➜ grep ban name.listban 班班
- 使用
cut
命令取文件的第二列
-d
相当于字符串的 split 操作,默认以\t
制表符切割,-d
之后可以跟一个参数表示以这个参数进行分割,此处用' '
表示用空格分割-f
用来配合-d
切割使用,表示filed
域,每个被切出来的小部分就是一个域,此处-f2
就是指每一行切出来的第二块东西,也就是第二列(base) ➜ cut -d ' ' -f2 name.list班班小甜甜
- 结合,用 grep 过滤查询 name.list 中 ban 的昵称,并把这个查询结果交给
cut
命令取第二列,中间使用的是|
管道符
- 管道符
|
用来将一个指令的输出传递到另一个指令(base) ➜ grep ban name.list | cut -d ' ' -f2班班
脚本化
- 目标:写一个脚本,带时间戳,传入参数是用户的账户,显示 hello + 用户昵称,昵称是用账户名查出来的
#!/bin/bashTIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")ACOUNT=$1NAME=$(grep $ACOUNT name.list | cut -d ' ' -f2)echo "[$TIMESTAMP]: hello $NAME"
- 输出效果
(base) ➜ ./temp ban[2022-04-06 10:50:39]: hello 班班
默认昵称——if 语句
对于新加入的用户,还没有昵称,那么显示一个
默认昵称
#!/bin/bashTIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")ACOUNT=$1NAME=$(grep $ACOUNT name.list | cut -d ' ' -f2)if [[ "" == $NAME ]]thenNAME="unknown user"fiecho "[$TIMESTAMP]: hello $NAME"(base) ➜ ./temp 1231[2022-04-06 10:57:55]: hello unknown user
- 注意:bash 脚本有严格的书写要求,否则解析会出错
- 此处,if 后有空格
[[]]
中间两侧有空格,简单粗暴推荐 if 都用两个方括号,==
两侧要有空格,=
两侧不能有空格
错误处理
假设上述例子中,查询
name.list
文件的命令,错误的写成了names.list
之类的,那么命令执行会出错,并打出错误信息,如果我们不希望显示错误信息,总是希望强制执行命令,可以这样做
- 对于一个指令,其实是有返回值的,成功执行返回 1,错误执行返回 2
- 对于
2
返回值,可以使用>
重定向符,将输出结果输出到别的地方去#!/bin/bashTIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")ACOUNT=$1NAME=$(grep $ACOUNT name.list 2> /dev/null | cut -d ' ' -f2)if [[ "" == $NAME ]]thenNAME="unknown user"fiecho "[$TIMESTAMP]: hello $NAME"
- 注意,2后面不能有空格,必须是 2> 连起来
/dev/null
俗称字节黑洞,被输出到这个路径的东西会被直接舍弃
重定向——redirection
date > date.log
,将 date 执行结果写入date.log
文件,多次执行会覆盖先前的输出(base) ➜ date > date.log(base) ➜ cat date.log2022年 4月 6日 星期三 11时11分26秒 CST
>
等价于1>
,因为 1 是成功执行的返回码
date >> date.log
,是 append 追加模式,不会覆盖(base) ➜ date >> date.log(base) ➜ cat date.log2022年 4月 6日 星期三 11时11分26秒 CST2022年 4月 6日 星期三 11时13分48秒 CST
输入一个不存在的命令,并用
>
输出(base) ➜ abc > abc.logzsh: command not found: abc
- 会在终端直接看到指令不存在提示
abc.log
文件会被创建abc.log
文件内容是空的,因为只有标准输出内容会被输入进来,而此时输出内容是错误信息,不是标准输出
输入一个不存在的命令,并用
2>
输出(base) ➜ abc 2> abc.log(base) ➜ abc(base) ➜ cat abc.logzsh: command not found: abc
- 控制台没有输出
abc.log
被创建,里面写着错误信息,因为2>
是错误状态时重定向
执行一个脚本,将其错误信息、正确信息,统一输出到一个文件中
#!/bin/bashabcTIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")ACOUNT=$1NAME=$(grep $ACOUNT name.list 2> /dev/null | cut -d ' ' -f2)if [[ "" == $NAME ]]thenNAME="unknown user"fiecho "[$TIMESTAMP]: hello $NAME"
- 先直接执行看看
(base) ➜ ./temp ban./temp: line 3: abc: command not found[2022-04-06 11:22:50]: hello 班班
- 实现
(base) ➜ ./temp ban > log.log 2>&1(base) ➜ cat log.log./temp: line 3: abc: command not found[2022-04-06 11:24:32]: hello 班班
>
正常标准输出,输出到log.log
文件中2>
错误输出,输出到1
正常输出信息里面去,由于直接写2>1
,1
会被认定为一个名为1
的文件,因而此处要写成2>&1
,表示写到标准输出里面去,而不是写到1
文件去
管道符——piping
查询包含 root 关键字的进程
(base) ➜ ps aux | grep root
查询以 root 开头的进程
- 用
正则表达式
(base) ➜ ps aux | grep ^root
查询以 root 开头的进程,单独开一个界面显示
(base) ➜ ps aux | grep ^root | less
- 按
q
退出
查询以 root 开头的进程,单独开一个界面显示并带上行号
(base) ➜ ps aux | grep ^root | less -N
查询,直接输出,带行号
(base) ➜ ps aux | grep ^root | cat -n
- 还可以
(base) ➜ gtb ps aux | grep ^root | wc
158 1934 21896
- 158是行数、1934是单词数目、21896是字母数目
(base) ➜ gtb ps aux | grep ^root | wc -l
158
*小需求
- 使用
history
显示执行过的命令- 分别统计各种指令执行的次数
- 按照执行次数排序显示
history | head输出前10行
history | tail输出最后10行
history | head -3输出前3行
(base) ➜ history | head1 sudo spctl --master-disable2 pwd3 ll4 ls -al5 d\pwd6 pwd7 cd Downloads8 ll9 ls10 ls(base) ➜ gtb history | tail2282 ls | grep ^a2283 ps aux | grep ^root2284 ps aux | grep ^root | less2285 ps aux | grep ^root | less -n2286 ps aux | grep ^root | less -N2287 ps aux | grep ^root | cat -n2288 ps aux | grep ^root | wc2289 ps aux | grep ^root | wc -l2290 history2291 history | head
- 分析:第一列编号部分,自动做了对齐,所以编号较小时前面补了空格,用空格分割时就会出错
(base) ➜ history | sed -E 's/^ +//' | head -31 sudo spctl --master-disable2 pwd3 ll
(base) ➜ history | sed -E 's/^ +//' | cut -d ' ' -f3
- 上述 history 输出的编号与命令之间,有
2个空格
,当用-d ' '
分隔时,-f2
会取到两个空格中间的东西,正好是 null,所以此处要用-f3
,表示第二个空格之后的那个字段
排序
(base) ➜ history | sed -E 's/^ +//' | cut -d ' ' -f3 | sort
去重+统计个数
(base) ➜ history | sed -E 's/^ +//' | cut -d ' ' -f3 | sort | uniq -c
最终
(base) ➜ history | sed -E 's/^ +//' | cut -d ' ' -f3 | sort | uniq -c | sort
经过测试,第一个
sort
起到了类似 group by 的效果,如果不写的话,重复项不会合并起来更严谨,加
-n
表示对数字进行排序,默认是按文本进行排序(在我的 zsh 上似乎输出效果一样)(base) ➜ history | sed -E 's/^ +//' | cut -d ' ' -f3 | sort | uniq -c | sort -n
别名
把常用的命令配置别名,方便使用
alias g='git'
建议掌握的命令
常用简单命令
- cat
- chmod
- chown
- cut
- echo
- find
- history
- ifconfig
- less
- ping
- ps
常用高级命令
- awk
- expect
- sed
- xargs
- vim
作业
rm
删除名为
-delete-me
的文件
first-commit
可以在命令行中执行命令
first-commit
,即可在当前路径创建一个 git repo,并将当前文件提交,提交 log 为 "first commit",也可以支持参数first-commit abc
,提交 log 则为abc