Version: Next

CLI Basic

  • 输出 hello world
  • 支持自定义 name
  • 带上时间戳
  • 昵称

Demo——hello world

输出 hello world

use echo

(base) ➜ ~ echo hello world
hello world

use function

  • code ~/.bashrc

  • 在里面写

    function h1() {
    echo hello $NAME
    }
  • 使用 source ~/.bashrc 刷新

  • 使用 h1 可以调用这个函数

use script

  • 定义一个文件
#!/bin/bash
echo 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=$1
echo hello $NAME
}
  • $1 表示传入的第一个参数,NAME 表示定义一个变量,$NAME 表示调用变量的值
  • s# 表示传入参数的总个数
  • $* zsh中表示所有参数组成的数组,bash 中表示把所有参数封装成一个大字符串,中间的空格不是知道是谁的,bash 中 $@ 可以区分保留多个参数内部的空格,对于 zsh 而言由于是数组,没有必要专门用 $@
  • zsh 开发指南——函数与脚本

使用脚本,定义一个脚本文件 temp

#!/bin/bash
NAME=$1
echo hello $NAME
  • 调用
(base) ➜ ./temp 123123123
hello 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.list
ban 班班
  • 使用 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/bash
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
ACOUNT=$1
NAME=$(grep $ACOUNT name.list | cut -d ' ' -f2)
echo "[$TIMESTAMP]: hello $NAME"
  • 输出效果
(base) ➜ ./temp ban
[2022-04-06 10:50:39]: hello 班班

默认昵称——if 语句

对于新加入的用户,还没有昵称,那么显示一个 默认昵称

#!/bin/bash
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
ACOUNT=$1
NAME=$(grep $ACOUNT name.list | cut -d ' ' -f2)
if [[ "" == $NAME ]]
then
NAME="unknown user"
fi
echo "[$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/bash
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
ACOUNT=$1
NAME=$(grep $ACOUNT name.list 2> /dev/null | cut -d ' ' -f2)
if [[ "" == $NAME ]]
then
NAME="unknown user"
fi
echo "[$TIMESTAMP]: hello $NAME"
  • 注意,2后面不能有空格,必须是 2> 连起来
  • /dev/null 俗称字节黑洞,被输出到这个路径的东西会被直接舍弃

重定向——redirection

  • date > date.log,将 date 执行结果写入 date.log 文件,多次执行会覆盖先前的输出
(base)date > date.log
(base)cat date.log
202246日 星期三 11时11分26秒 CST
  • > 等价于 1>,因为 1 是成功执行的返回码
  • date >> date.log,是 append 追加模式,不会覆盖
(base)date >> date.log
(base)cat date.log
202246日 星期三 11时11分26秒 CST
202246日 星期三 11时13分48秒 CST

输入一个不存在的命令,并用 > 输出

(base) ➜ abc > abc.log
zsh: command not found: abc
  1. 会在终端直接看到指令不存在提示
  2. abc.log 文件会被创建
  3. abc.log 文件内容是空的,因为只有标准输出内容会被输入进来,而此时输出内容是错误信息,不是标准输出

输入一个不存在的命令,并用 2> 输出

(base) ➜ abc 2> abc.log
(base) ➜ abc
(base)cat abc.log
zsh: command not found: abc
  1. 控制台没有输出
  2. abc.log 被创建,里面写着错误信息,因为 2> 是错误状态时重定向

执行一个脚本,将其错误信息、正确信息,统一输出到一个文件中

#!/bin/bash
abc
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
ACOUNT=$1
NAME=$(grep $ACOUNT name.list 2> /dev/null | cut -d ' ' -f2)
if [[ "" == $NAME ]]
then
NAME="unknown user"
fi
echo "[$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>11 会被认定为一个名为 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

*小需求

  1. 使用 history 显示执行过的命令
  2. 分别统计各种指令执行的次数
  3. 按照执行次数排序显示
history | head

输出前10行

history | tail

输出最后10行

history | head -3

输出前3行

(base)history | head
1 sudo spctl --master-disable
2 pwd
3 ll
4 ls -al
5 d\pwd
6 pwd
7 cd Downloads
8 ll
9 ls
10 ls
(base) ➜ gtb history | tail
2282 ls | grep ^a
2283 ps aux | grep ^root
2284 ps aux | grep ^root | less
2285 ps aux | grep ^root | less -n
2286 ps aux | grep ^root | less -N
2287 ps aux | grep ^root | cat -n
2288 ps aux | grep ^root | wc
2289 ps aux | grep ^root | wc -l
2290 history
2291 history | head
  • 分析:第一列编号部分,自动做了对齐,所以编号较小时前面补了空格,用空格分割时就会出错
(base)history | sed -E 's/^ +//' | head -3
1 sudo spctl --master-disable
2 pwd
3 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'

建议掌握的命令

常用简单命令

常用高级命令

  • awk
  • expect
  • sed
  • xargs
  • vim

作业

rm

删除名为 -delete-me 的文件

first-commit

可以在命令行中执行命令 first-commit,即可在当前路径创建一个 git repo,并将当前文件提交,提交 log 为 "first commit",也可以支持参数 first-commit abc,提交 log 则为 abc