分享大纲:

  1. 文件系统相关,inode、权限、硬链接软链接及在 pnpm 中的应用
  2. Shell 脚本入门和注意事项
  3. 一些实用的命令分享

一、背景

工作生活中,少不了和 Unix/Linux 系统接触,比如 MacOS、Linux 服务器、Openwrt 软路由配置和部署等。偶尔也需要写点 Shell 脚本(尽管我们可以用 Shell 脚本当壳,核心内容用 node.js 来写)。希望这点实用为主的分享对大家有用。

列举几个公司内用到的 shell 脚本场景(需内网访问):

  1. pr-common 的构建部署:主要事项:版本号输出到 njk 、 执行 saas 脚本(因 pr-common 不支持直接引入微应用,构建时同步微应用的产物资源到 njk 中)、打包,上传 cos
  2. Serverless 部署相关

二、文件系统相关

什么是 inode

inode

在 Unix/Linux 系统中,表面上,用户通过文件名打开文件,实际上,系统内部这个过程分成三步:

$ ls -li link.txt
50260683 -rw-r--r--  1 anto  staff  0 Oct 12 12:00 link.txt

这么设计有什么优势?

举个例子,软件更新变得简单,可以在不关闭软件的情况下进行更新,不需要重启。(阮一峰:理解 inode

硬链接和软链接

# 新建硬链接
$ ln file link

# -s 新建软链接(符号链接), B 是被链接文件,A 是将创建的软链接
$ ln -s B A

硬链接和软链接

应用 - PNPM 减少 node_modules 的磁盘占用

$ ls -l node_modules/dohjs
lrwxr-xr-x  1 anto  staff  36 Aug 30 18:48 node_modules/dohjs -> .pnpm/dohjs@0.3.3/node_modules/dohjs
# ls -i 看下 .pnpm 下某个文件的 inode 号码,也看到硬链接次数是 2
$ ls -li /Users/anto/Projects/doh-benchmark/node_modules/.pnpm/dohjs@0.3.3/node_modules/dohjs/package.json
45349431 -rw-r--r--  2 anto  staff  1265 Aug 30 17:33 

# 根据这个 inode 号码,扫一下硬盘中,有几个文件关联了这个 inode,查出来确实是 2 个文件
find ~/ -inum 45349431
# 第一个
/Users/anto/Projects/doh-benchmark/node_modules/.pnpm/dohjs@0.3.3/node_modules/dohjs/package.json
# 第二个
/Users/anto/Library/pnpm/store/v3/files/62/3b004149c528a15325b39235b366c559ef0c1caf380e5ab17e5864d6a0d4b377ecdd721e4231ac2b55843347bb9fcab905a295af59ac290250bb1b05c7a7b1

文件权限

# 给所有用户赋予执行权限,等价于 a+x
$ chmod +x script.sh

# 给所有用户读权限和执行权限
$ chmod +rx script.sh
# 或者
$ chmod 755 script.sh

脚本的权限通常设为 755(拥有者有所有权限,其他人有读和执行权限)。755 到底代表啥呢? 文件权限

比如某个文件的权限是:-rwxr-xr-x,chmod 可以用 755 表示。

提问:如果想把某个文件权限变成最宽松,所有人都可读可写可执行,用 chmod 命令应该怎么操作?

Tips: 目录的执行权限。对于目录来说,如果无执行权限,则对应用户 cd 不进去。(为啥?不是有读权限了吗?)

输出重定向

# 执行 command1 然后将标准输出的内容,覆盖式存入file1
$ command1 > file1

# 不覆盖,追加到指定文件
$ command1 >> file1

# `2>`用来将 标准错误 重定向到指定文件。
# 比如我们想把执行中的错误,单独记录在 error.log里
$ ls -l /bin/usr 2> error.log

# 标准输出和标准错误,可以重定向到同一个文件:
$ ls -l /bin/usr > ls-output.txt 2>&1
# 或者
$ ls -l /bin/usr &> ls-output.txt

# 追加到同一个文件
$ ls -l /bin/usr &>> ls-output.txt
$ ping kujiale.com | tee output.txt

其他常用命令

$ du -sh node_modules
283M    node_modules
$ sed -i -e "s/g_prCmnCdnHost = \"\"/g_prCmnCdnHost = \"\/\/qhstaticssl.kujiale.com\/pub\/$deployVersion\"/g" $cdnInfoFtl
# 尾部 50 行
$ tail -n 50 output.log

# `-f`会实时追加显示新增的内容,常用于实时监控日志,按`Ctrl + C`停止。
$ tail -f /var/log/messages

三、系统和网络相关

$ lsof -i :7000
$ ps -ef |grep node
# 或
$ ps aux |grep node
$ ifconfig
# 查看磁盘挂载信息
$ df -h

# 挂载磁盘
$ mount /dev/sdb1 /mnt/sdb1

分享个假期前开发的网络小工具(nali),方便查询 ip 归属地: https://github.com/fantasyroot/nali-ip-cli

$ nali 1.145.1.4

1.145.1.4 [澳大利亚,新南威尔士州,悉尼,澳大利亚电信]

四、Shell 脚本

当我们需要多个任务编排,重复使用这些命令集合的时候,可以写个 shell 脚本执行它们。

特殊变量

调试排查

提问:以下脚本中的代码有什么问题?(不要实际尝试)

#! /bin/bash

dir_name=/path/not/exist

cd ~/Project
cd $dir_name
rm *

编写 Shell 脚本的时候,一定要考虑到命令失败的情况,因为默认出错后会继续向下执行。 如果目录 $dir_name 不存在,cd $dir_name 命令就会执行失败。这时,就不会改变当前目录,脚本会继续执行下去,导致 rm 命令删光当前目录的文件。

正确做法是,用 [ -d file ] 判断表达式,先判断目录是否存在:

[[ -d $dir_name ]] && cd $dir_name && rm *

同时可以用 set 命令设置 Shell 的行为参数,有利于脚本除错。比如:

#!/usr/bin/env bash
set -e

foo  # 发生错误,就会到此终止,不向下继续执行
echo bar

命令替换

将一个命令的输出,替换进入另一个命令。

$ ls -l $(which cp)

# 或者
$ ls -l `which cp`

多个命令连续执行

# 第一个命令执行完(不管成功或失败),执行第二个命令
$ command1; command2

# 只有第一个命令成功执行完(退出码0),才会执行第二个命令
$ command1 && command2

# 只有第一个命令执行失败(退出码非0),才会执行第二个命令
$ mkdir foo || mkdir bar

其他常用命令:

exit

# 退出值为 0(成功)
$ exit 0

# 退出值为 1(失败,走到异常逻辑)
$ exit 1

exitreturn 差别是,return 命令是函数的退出,脚本依然执行。exit 是整个脚本的退出。

alias 别名

# 防止误删文件
$ alias rm='trash -F'

# 不加参数时,显示所有有效的别名
$ alias

# 当我们用 alias 自定义命令覆盖了原始命令,但还想用原始命令,怎么做?
# 可以在命令前加上反斜杠("\")来绕过别名。例如:
$ alias ping=nali-ping
$ \ping

type

Shell 可执行命令分为四种类型,可以用 type 命令判断命令的来源:

# 平常会用 which 看某个命令的路径
$ which node
/Users/anto/.nvs/default/bin/node

# 也可以用 type
$ type node
node is /Users/anto/.nvs/default/bin/node

$ type type
type is a shell builtin

五、推荐读物