Shell 难学?那是因为你没找到场景(一)

1,731 阅读5分钟

导读

Shell 可以说是程序员的基本功,但在实际工作中,除了负责运维的同学常用一点,其他大多数同学是不怎么会用的,知道 ls、man、which 什么的几个命令就够了。其实 Shell 的语法最多花 2 小时都能看完,关键是你得找一个真正的场景去用它,去实战练习,光写 demo 是肯定不够的,这点相信身为程序员的大家心里都清楚。

笔者最近在研究 Gitflow 的自动化(详见 Gitflow 太繁琐?为什么不自动化呢),需要输出一份前后端都能使用的脚本,于是 Shell 就成了最佳选型。终于有合适的场景了,而且开发的过程中的确遇到了很多问题,也学到了很多东西,毕竟带着问题学习才是最高效的。现把一些小成果整理出来,希望能够有所帮助,相信你在日常开发中一定会有用到它们的时候。

第二篇在这: # Shell 难学?那是因为你没找到场景(二)

正文

笔者默认各位是有一定命令行基础的,起码知道管道、git 和一些基础命令。

基本命令

首先要先搞清楚两个基本而强大的命令:grepxargs

grep 熟悉的人比较多,用来查找、过滤。主要是它的参数比较多,笔者每次使用也都需要 --help 一下,大概知道它有什么能力就行,用的时候现查;

xargs 命令的作用,是将标准输入转为命令行参数。这么说肯定没几个人能明白,举个例子吧,以下几条命令效果是等价的。

echo "hello" | xargs git commit -m
# 等价于
echo "hello" | xargs sh -c 'git commit -m'
# 等价于
git commit -m "hello"

是不是容易理解多了。更有用的是它的 -I 参数,可以批量执行命令,这里就不展开了,可以看下 xargs 命令教程 这篇文档了解一下。

另外,要想让 xargs 接收多入参,必须把输入处理成多行,这就需要一点点正则和另外一个 sed 命令:

echo "1,2,3" | sed 's/,/\n/g'
# 1
# 2
# 3

正则和 sed 这里就不展开了,暂时用这个就够了,最多就是把逗号改成相应的分隔符。与 xargs 结合的效果就是:

echo "1 2 3" | sed 's/ /\n/g' | xargs -I n echo "1.n.0"
# 1.1.0
# 1.2.0
# 1.3.0

接下来,让我们来看看这些命令放在一起能起什么化学反应。

快速修改文件并提交 commit

echo "'feat: `date`'" >> test.txt | xargs git ci -am

主要用这条命令快速生成一个 commit 来进行测试,此命令的效果如下:

  • test.txt 新增一行:'feat: 2022年 01月 23日 星期日 23:28:01 CST'
  • git log 新增一条:feat: 2022年 01月 23日 星期日 23:28:01 CST 的 cimmit 记录 如果有必要,你完全可以搭配 xargs -I 实现多条提交,实际上笔者的确有这么用。

多个分支强制还原到某个 commit

echo "master/develop/release" | sed 's/\//\n/g' | xargs -I br sh -c "git checkout br && git reset --hard 1.0.0 && git push -f"

主要用这条命令来还原到初始状态。注意此处用了“/”作为分隔符,另外为了方便,笔者在需要还原的 commit 打了 1.0.0 的 tag,这样就不需要输入 commit id 了。

删除 1.0.0 之外的所有 tag

git tag | grep -v 1.0.0 | xargs -I tt sh -c "git tag -d tt;git push origin :refs/tags/tt"

这条一般配合上面那条一起使用。注意 grep-v 属性,就是过滤掉它后面的值。这个命令稍微改一改也可以删除分支。甚至自动删除远程不存在的本地分支:

删除远程已经不存在的本地分支

git fetch -p && git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -d`

这条命令很实用。注意到这里又出现了一个新命令 awk,其功能类似 grep,有很多处理多列文本的强大功能,具体用法可见 Linux awk 命令。为保证文章节奏,这里就暂不展开了。

判断远程是否存在某分支

git fetch -p && git branch -r | grep -w $branchName

注意,一定要加 -w 参数,必须完全匹配才可以。

再结合获取当前分知名的命令 git rev-parse --abbrev-ref HEAD 就可以做到检查当前分支是否存在了。写在 shell 文件里很简单,写在一行命令里还是有一点难度的,算是一个思考题吧。

获取当前 package.json 的版本号(nodejs 项目)

node -p -e "require('./package.json').version"

这个就不多解释了,你甚至可以在双引号之间写一大串 nodejs 逻辑。

获取两个 commit 之间的 git log 并格式化

git log --pretty=format:"- %cd %h - %s" $sourceBR...$targetBR --date=format:"%m/%d %H:%M"
# - 01/23 23:28 e660c5c - commit message 1
# - 01/22 23:27 758ba9a - commit message 2

这个命令挺实用的,可以用来自动生成 PR 的填充信息,并且可以自己格式化。举个例子:

  • 如果想获取 develop 分支与 master 分支之间的 log,把 $sourceBR 改成 develop,$targetBR 改成 master 即可;
  • 如果是当前分支与 master 之间的 log,把 $sourceBR 改成 HEAD 即可;
  • 两个变量也可以是 commit id log pretty 更多 format 参数见 pretty-formats

结语

最后,来一个组合拳的例子,是笔者实际使用的脚本中的一段(如果看不懂是在做什么,请移步 Gitflow 太繁琐?为什么不自动化呢 查看上下文)。

doInit() {
  echo "master/develop/release" | sed 's/\//\n/g' | xargs -I br sh -c "git checkout br && git reset --hard 1.0.0"
  git tag | grep -v 1.0.0 | xargs -I tt sh -c "git tag -d tt"
}

doFeature() {
  echo "feature/feat$1" | xargs -I ff sh -c 'git checkout develop && git checkout -b ff;echo "ff `date`" >> test.txt;git ci -am "feat: ff `date`";git checkout develop && git rebase ff;git br -D ff'
}

doBugfix() {
  echo "bugfix/bf$1" | xargs -I bf sh -c 'git checkout release && git checkout -b bf;echo "bf `date`" >> bugfix.txt;git ci -am "fix: bf `date`";git checkout release && git rebase bf;git br -D bf && git checkout develop && git rebase release'
}

doHotfix() {
  echo "hotfix/hf$1" | xargs -I hh sh -c 'git checkout master && git checkout -b hh;echo "hh `date`" >> hotfix.txt;git ci -am "fix: hh `date`" && npm version patch;git checkout master && git rebase hh;git br -D hh && git checkout release && git rebase master && git checkout develop && git rebase release'
}

doUAT() {
  git checkout develop && npx conventional-changelog -p angular -o CHANGELOG.md -u && git checkout release && git rebase develop
}

doDeploy() {
  git checkout release && npm version minor && git checkout master && git rebase release && git checkout develop && git rebase release
}

# doInit
# doFeature 1
# doHotfix 1
# doHotfix 2
# doFeature 2
# doUAT
# doBugfix 1
# doHotfix 3
# doBugfix 2
# doDeploy

笔者可以方便的通过对下面的注释内容进行,放开、关闭、新增、删除、调整顺序等操作,在几秒钟内在本地模拟一次完整的 gitflow 工作流。

当然,笔者还有一套模拟更复杂的模拟真实远程 gitflow 工作流的脚本,就不贴出来了,相信大家已经可以根据上面的例子自己进行应用了。尤其是自动生成 log 的那个命令,大家可以仔细研究一下,哪怕每次提交了 PR,手动执行一下再贴到 PR 中去,看着也很优雅,很解压不是吗。

“做难事必有所得。越是喧闹,越是孤独;越是寂寞,越是丰富。” —— 金一南