我喜欢阅读Julia Evans 在 Tiny Personal Programs 上的帖子,所以我想分享一些我自己的。我希望它们对其他人复制有用,或者至少考虑他们是否可以窃取我工作流程中一些有用的部分。
警告:这些工具使用风险自负。它们对我来说效果很好,但还没有准备好“生产”,也没有以任何方式强化。
另请注意,所有的 shell 脚本都是用 ZSH 编写的。我还总结了其中的一些,因此它们可能需要稍作调整才能在您的机器上运行。
w(手表)
这个程序是我日常生活的基石。如此重要,以至于我给它起了一个单字母的名字来模仿经典的 UNIX 实用程序。
当前的来源很简单:
f = ${ XDG_RUNTIME_DIR :- /tmp } /save [ -e $f ] || touch $f exec entr -r $@ <<< $f
它只是entr的一个小包装,一个非常简单的程序,用于监视文件和运行程序。
他们的关键创新是我意识到我不需要费心告诉 entr 99% 的时间要观看哪些文件。我可以节省自己的脑力,并避免在添加或重命名文件时需要重新启动,只需告诉它观看特定文件,然后我只需在需要重新运行时触摸该文件即可。
几乎我所有的更新都来自我保存文件时。我在 Vim 中有一个简单的键绑定来保存和触摸这个文件。
let g:touchfile = expand ( '$XDG_RUNTIME_DIR/save' ) nnoremap < silent > < Leader > w : echo 'No changed files.' < CR >: wa < CR >: call writefile ([], g:touchfile )< CR >
而不是做:w
或:wa
我只是做<Leader>w
并保存所有内容,然后触摸魔术文件。
我的开发工作流程几乎总是由一些在屏幕左侧的w
下运行的程序组成,并且每次我将编辑器保存在屏幕右侧时它都会重新运行。
从我的 shell 历史中随机选择的命令:
w cargo test wd python3 ./test.py # Using a command rather than an alias works great with "nested" commands. nix-shell -A ecl --run 'w cargo run' # Build my site, then do a word-diff against a reference copy. w zsh -c 'nix-build -A www && git dw /nix/store/0nf7l88dpbjfzp64lfmiyl8c52xgip85-kevincox.ca result/ | cat' # Build and upload a docker image on save. w zsh -c 'nix-build -A docker && podman load <result && podman push nix-ci:latest registry.gitlab.com/kevincox/nix-ci:test'
你明白了。如果你能想到一个命令,你可以在保存时运行它。不必担心要监视哪些文件和目录,也不必担心在大型目录上设置监视的时间。只需调用w
并保存。
d
主要是w
上的一个伴侣,它为命令添加了许多有用的行为。
- 设置完成通知。因此,如果在我没有专注于终端的情况下完成命令,我会收到桌面通知。
- 打印开始和停止消息。有些命令有点太安静了,很难判断它们是在运行还是已经完成,而您什么也没有等待。此消息还包括扩展命令和一些资源利用率统计信息。
if [[ $VTE_VERSION -ge 3405 ]] ; then echo -n $' \e ]777;preexec \e\\ ' fi color = $' \e [2m' nocolor = $' \e [0m' formatted = " ${ (@q-)@ } " echo " ${ color } Starting $formatted$nocolor " 1>&2 TIMEFMT = " ${ color } Done. Total: %*E (%P), User: %*U, System: %*S - $formatted$nocolor " if [[ $VTE_VERSION -ge 3405 ]] ; then TIMEFMT = $' \e ]777;precmd \e\\ ' $TIMEFMT fi time ( exec " $@ " )
另一个“功能”是它可以作为一种简单的方式来启动 shell。运行d eval "foo && bar"
将在 shell 中运行参数,这比zsh -c "foo & bar"
的输入要少一些。
c(剪贴板)
另一个对于一个字母的名字来说足够重要。
xsel --clipboard
好的,此时这可能是别名。但多年来,它已经更新以支持不同的操作系统并使用不同的工具来执行命令。在你的$PATH
上有一个“真正的”可执行文件也很好。
它总是派上用场。特别是因为它是将数据从 Web 浏览器等程序中获取到命令或文件中的最简单方法,而无需担心转义。在 ZSH $( c )
中甚至是二进制安全的!
# base64 decode your clipboard. c | base64 -d # Generate a UUID and put it in the clipboard. uuidgen | c # Process the clipboard and put it back into the clipboard afterwards. c | tr AZ az | c
在依赖 shell 历史时,我也经常使用它。我通常会调用如下命令,其中从剪贴板中提取公共变量部分,以便我可以快速重用该命令而无需编辑。 (我可能有一天应该把这些变成脚本或别名,但 shell 历史的开销如此之低,而且随着时间的推移很容易调整。)
# Show an HTTP response with headers in my editor. (expects the URL in the clipboard) curl -i $( c ) | v # Test building a nixpkgs patch. (expects PR ID or URL in the clipboard) nixpkgs-review pr $( c ) --no-shell --post-result -c commit
监视日志
这实际上是一个完整的程序。将流式日志或其他缓慢进展的内容放在末尾很有用。它让您了解上次取得进展的时间,并在暂停期间向日志中注入一些时间戳。
地穴
有时我需要在某处插入密码哈希。 UNIX crypt
格式是可升级密码哈希的通用标准。我有一个可以生成或检查密码哈希的小包装器。
import crypt import getpass import sys p = getpass . getpass () argc = len ( sys . argv ) if argc == 1 : salt = None assert p == getpass . getpass (), "Passwords not the same" elif argc == 2 : salt = sys . argv [ 1 ] else : raise ArgumentError ( "Too many arguments. Expected 0 or 1" ) hash = crypt . crypt ( p , salt = salt ) print ( hash ) if salt : if hash == salt : print ( "Match" ); else : print ( "No Match" ) sys . exit ( 1 )
只需调用crypt
生成哈希或crypt $hash
来检查它。
u(UNIX 时间戳)
一个简单的工具来获取一个数字并猜测它代表什么时间。 (那个时间戳是以 ns、ms 还是 s 为单位的???)
它可以接受参数或从剪贴板中提取数字,但核心代码如下所示。
t = $1 t = " $( perl -e '$i=$ARGV[1];while($i>1e10){$i/=1000}print($i)' tos " ${ t //[^0-9.]/ } " ) " date "-d@ $t " --rfc-3339 = ns " $@ "
% u 1655150354 # UNIX seconds 2022-06-13 15:59:14.000000000-04:00 % u 1653150192354 # UNIX ms 2022-05-21 12:23:12.354000000-04:00
r(重试)
一个重试命令的简单工具。它将命令作为参数并重试,直到成功。它支持目标退出代码、延迟和最大尝试次数的选项。
示例:继续在 flakey 端点上重试下载。
r --delay = 10 wget --continue https://example.com/somefile.tar.gz
示例:即使 pod 重新启动,也要继续跟踪 kube 日志。 ( -u=-1
表示直到进程返回 -1,AKA 永远)
r -u = -1 kubectl logs -f deployment/something
Git 工具
我使用 git,这里是别名和命令的快速小节(当git x
太长 4 个字符时)。
对于上下文,这是我的 git checkouts 的约定。我将origin
设置为我有权写入的存储库。例如,我的个人叉子。我将u
设置为上游仓库。对于我的项目,这可能与origin
相同,但对于其他项目,这可能是我没有写入权限的存储库。
ga (git 修改)
在出版之前,历史重写很棒。我有一个快速别名,用于快速将修复合并到现有提交中。
git commit --amend --no-edit " $@ "
gf(混帐力)
如果您不了解git push --force-with-lease
,您应该认真阅读!它与普通的--force
类似,但提供了一个重要的安全功能。它不会盲目地覆盖目标中的任何内容,而是检查您认为存在的内容是否确实存在。例如,如果同事通过修复推送到您的分支,它可能会避免您覆盖同事的工作。它绝对不是完美的(至少没有明确指定您期望的提交),但至少抓住了一些常见的错误。
但这很难说清楚,我经常在开发分支中使用它。所以我喜欢这个简短的名字。这使我可以简单地运行gf
并且经常像ga && gf
那样配对以将本地更改合并到现有提交中并推送结果。
git push --force-with-lease " $@ "
git-clean-合并
这将删除作为u
遥控器上HEAD
ref 子集的任何分支。对于我的工作,这意味着它们已完全集成到上游项目中。
请注意,我不会努力避免一些错误,例如删除当前分支。相反,我只是让 git 在日志中发出一个很好的警告。
branch = ${ 1 -refs/remotes/u/HEAD } echo "Deleting branches merged into $branch ." git branch --merged $branch --format = '%(refname:lstrip=2)' \ | grep -vFx $branch \ | grep -v '\(detached at [0-9a-f]*\)' \ | xargs -r -- git branch -D --
gn (git 新)
一种快速启动新分支的方法。这基于u
遥控器的HEAD
ref 是什么。我将此设置为我想要开始新更改的分支。
branch = $1 git fetch u git checkout u [[ -n $branch ]] && git branch $branch
这类似于git checkout master && git pull
但有一个关键优势;这不会让您留在主分支上,因此您不会意外推送它,并且如果您的本地 master 很脏,您也不会创建意外合并。
我通常不传递分支名称参数。这让我处于“分离的 HEAD 状态”。缺点是更容易丢失你的工作,因为它没有一个容易找到的名字。但是, git reflog是我的朋友,所以这不是主要问题。我更喜欢推迟给分支命名,因为一旦我对代码进行了一些尝试,我经常会想到一些更有意义的东西。
gs (git 开关)
这是一个用于切换分支的简单命令。我使用这个命令的主要原因是创建或切换到分支是一样的。这意味着我可以键入gs <Up>
来搜索我的 shell 历史记录以查找最近的分支,然后再次运行该命令以切换到它。我发现这比git switch -c <Up>
方便得多,然后在找到它后将命令切换到git branch
。
它还有一个小便利功能,您可以在不带参数的情况下运行gs
以切换到“主”分支。
branch = ${ 1 - $( git symbolic-ref refs/remotes/origin/HEAD | sed 's#^refs/remotes/origin/##' ) } git switch $branch || git switch -c $branch
git b (git 分支)
在我的结帐中显示“活动”分支
[alias] b = branch -v --no-merged HEAD
提供一些整洁的输出,例如:
% git b renovate/tonic-0.x 670b383 Update Rust crate tonic to 0.7.2 secure-websub f8e3737 Don't use HTTP WebSub hubs for HTTPS feeds.