在一个编程项目中,我想轻松获得一个可打印的ASCII字符的列表,这样我就可以轻松地循环浏览它们,并对每个ASCII字符做一些工作。
这似乎很简单!但我并没有找到任何能满足我要求的东西。
我发现有一个非常酷的ascii 程序,可以通过自制软件使用,但尽管它非常棒,却没有一个简单的可打印ASCII列表的输出。
当然,我在网上找到了可打印的ASCII列表,但我不想简单地剪切和粘贴到一个文本文件中。
所以我转而用脚本编写东西,AWK是一个很好的简单脚本:
$ awk 'BEGIN { for(i=32;i<=126;i++) printf "%c\n", i; }'
!
"
#
$
%
&
'
(
)
*
+
,
-
.
/
0
1
-- etc --
这很好,但我不想一直让自己去用ctrl-r来使用这些数据输出。
所以把它扔到$HOME/bin/printable-ascii ,就完成了?
嗯,不是。当我把它写进一个脚本时,我想,既然我把它写成了一个真正的脚本,我不妨利用这几天难得的机会来写一些Ruby!
$ ruby -e "32.upto(126) { |n| puts n.chr }"
!
"
#
$
%
&
'
(
)
*
+
,
-
.
/
0
1
-- etc --
就这样,像我们所期望的那样,Ruby的表现力很强。
一个真正的Ruby脚本
但是,等一下。既然我费尽心思要通过Ruby把它写成一个脚本,我也可以输出其他有趣的数据。比如说,如果能运行一个脚本,不仅能看到ASCII码,还能看到字符的十进制、八进制、十六进制和二进制的表示,那就太酷了。
我已经很久没有用Ruby写过一个漂亮的命令行工具了,OptionParser是Ruby标准库中一个非常棒的库。与我的日常编程工作相比,如果能够只用一种好的语言和一个好的标准库来写一些小而有用的东西,那将是一种真正令人耳目一新的编程节奏的改变。
稍后的脚本工作,tada! printable-ascii 0.0.1.这个版本甚至没有进入repo,因为我还在我的dotfiles ~/bin目录中写它。
但它已经开始变得非常酷了。ASCII是如此整齐的数据片断,而我从来没有真正地直接解析过它。
当我添加了JSON输出时,我不得不把这个很酷的小脚本放到它自己的 repo 中去了Fiatsdball/printable-asciiandprintable-ascii v1.0.0
我到此为止了吗?我没有。
自制软件
我想看看我是否能把这个脚本加入到自制软件中,这将是一件很有趣的事情。一个Ruby脚本能被添加到自制软件中吗?结果是可以的,而且非常容易,这要感谢GitHub提供和托管tar.gz文件。 ❤️GitHub!
class PrintableAscii < Formula
desc "Output all printable ASCII characters in various representations and formats"
homepage "https://github.com/sdball/printable-ascii"
url "https://github.com/sdball/printable-ascii/archive/refs/tags/v2.1.0.tar.gz"
sha256 "cf0b2dfa7c1e0eb851be312c3e53d4f67ad68d46c58d8983f61afeb56588b061"
license "MIT"
def install
bin.install "bin/printable-ascii"
end
test do
ascii_json = [
{ "character" => " " },
{ "character" => "!" },
{ "character" => "\"" },
{ "character" => "#" },
{ "character" => "$" },
{ "character" => "%" },
{ "character" => "&" },
{ "character" => "'" },
{ "character" => "(" },
{ "character" => ")" },
{ "character" => "*" },
{ "character" => "+" },
{ "character" => "," },
{ "character" => "-" },
{ "character" => "." },
{ "character" => "/" },
{ "character" => "0" },
{ "character" => "1" },
{ "character" => "2" },
{ "character" => "3" },
{ "character" => "4" },
{ "character" => "5" },
{ "character" => "6" },
{ "character" => "7" },
{ "character" => "8" },
{ "character" => "9" },
{ "character" => ":" },
{ "character" => ";" },
{ "character" => "<" },
{ "character" => "=" },
{ "character" => ">" },
{ "character" => "?" },
{ "character" => "@" },
{ "character" => "A" },
{ "character" => "B" },
{ "character" => "C" },
{ "character" => "D" },
{ "character" => "E" },
{ "character" => "F" },
{ "character" => "G" },
{ "character" => "H" },
{ "character" => "I" },
{ "character" => "J" },
{ "character" => "K" },
{ "character" => "L" },
{ "character" => "M" },
{ "character" => "N" },
{ "character" => "O" },
{ "character" => "P" },
{ "character" => "Q" },
{ "character" => "R" },
{ "character" => "S" },
{ "character" => "T" },
{ "character" => "U" },
{ "character" => "V" },
{ "character" => "W" },
{ "character" => "X" },
{ "character" => "Y" },
{ "character" => "Z" },
{ "character" => "[" },
{ "character" => "\\" },
{ "character" => "]" },
{ "character" => "^" },
{ "character" => "_" },
{ "character" => "`" },
{ "character" => "a" },
{ "character" => "b" },
{ "character" => "c" },
{ "character" => "d" },
{ "character" => "e" },
{ "character" => "f" },
{ "character" => "g" },
{ "character" => "h" },
{ "character" => "i" },
{ "character" => "j" },
{ "character" => "k" },
{ "character" => "l" },
{ "character" => "m" },
{ "character" => "n" },
{ "character" => "o" },
{ "character" => "p" },
{ "character" => "q" },
{ "character" => "r" },
{ "character" => "s" },
{ "character" => "t" },
{ "character" => "u" },
{ "character" => "v" },
{ "character" => "w" },
{ "character" => "x" },
{ "character" => "y" },
{ "character" => "z" },
{ "character" => "{" },
{ "character" => "|" },
{ "character" => "}" },
{ "character" => "~" },
]
assert_equal ascii_json, JSON.parse(shell_output("#{bin}/printable-ascii --json"))
end
end
真正神奇的核心是bin.install "bin/printable-ascii" 。它将printable-ascii 脚本作为可执行文件安装到homebrew的bin中。
$ brew install --formula ./Formula/printable-ascii.rb
==> Downloading https://github.com/sdball/printable-ascii/archive/refs/tags/v2.1.0.tar.gz
Already downloaded: /Users/sdball/Library/Caches/Homebrew/downloads/1f5ded4652929fb1c8ca5ffdb1a733cdfa3e65e6bf447893ef59803f7f6919b9--printable-ascii-2.1.0.tar.gz
🍺 /opt/homebrew/Cellar/printable-ascii/2.1.0: 5 files, 22KB, built in 1 second
Removing: /Users/sdball/Library/Caches/Homebrew/printable-ascii--2.0.0.tar.gz... (6.3KB)
对了!也许有一天它真的会出现在Homebrew中,但现在直接用公式安装已经很容易了。
Docker
由于Homebrew需要克隆repo,然后从公式文件中手动安装,我应该可以很容易地将这个脚本包装成一个Docker镜像!然后任何人都可以轻松地运行这个愚蠢的脚本。然后任何人都可以轻松地运行这个愚蠢的脚本,只要他们安装了Docker。谁不喜欢通过Docker从网上运行任意的脚本呢?
因为我最近一直在帮助工作中的DevOps团队,所以我有一些热装的Docker知识准备好了。我只需要一个带有Ruby的图像,把脚本复制到某个地方,然后把脚本设置为ENTRYPOINT。
然后通过docker run ,传递给docker run命令的任何参数都会传递给脚本本身。✨Docker!
我很快就找到了Ruby Docker的官方镜像,并直接抓取了我在他们的README中所提到的第一个镜像
FROM ruby:2.5
# throw errors if Gemfile has been modified since Gemfile.lock
RUN bundle config --global frozen 1
WORKDIR /usr/src/app
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY . .
CMD ["./your-daemon-or-script.rb"]
因为我不需要bundler或Gemfiles,所以我把它精简了一下。以下是为printable-ascii制作的1.0.0 Docker镜像
FROM ruby:2.5
WORKDIR /usr/src/app
COPY bin/printable-ascii ./
ENTRYPOINT ["./printable-ascii"]
很简单,效果很好
GitHub行动
从我自己的笔记本上建立一个Docker镜像,然后发布到Docker Hub?这真是太古老了!如果我让自己生活在这样的发布故事中,我算什么DevOps新手?
GitHub Actions拯救了我!我设计了一个GitHub Actions工作流,每当我发布新版本的脚本时,就会同时发布到Docker Hub和GitHub自己的容器注册表。它甚至会检查脚本的声明版本是否与将要发布的版本一致。
这有点奇怪,因为它意味着脚本的Docker镜像与脚本本身的版本相同。如果我修改了脚本本身,那么一切都说得通,因为新的Docker镜像将包含新的脚本。但如果我只更新Docker镜像,那么我就没有一个合理的方法来只改变其版本。
幸好这个脚本和Dockerfile非常简单,所以我可以简单地让它们保持同步,只在有新版本的脚本发布时才更新Docker镜像。
但在实践中,对于Docker镜像提供的实用程序和Docker镜像本身之间更复杂的关系,似乎有机会获得更多的元数据。比如镜像的双重版本,以及在Docker Hub上以良好的、一致的方式表示其内容。
这个Docker镜像太庞大了
在我设置了GitHub Action之后,我回头看了看我的Docker Hub统计,发现这个脚本的镜像超过了300MB!这根本不是什么好事!
我想一定有一个更薄的Ruby镜像可以使用。我所使用的默认镜像可能有各种多余的(对我来说)实用程序和库来支持各种项目。
在阅读了Ruby的官方docker镜像后,我发现有一个-slim 和一个-alpine 版本的Ruby镜像可以使用。由于我的脚本确实真的是Ruby和它的标准库,我确信-alpine 镜像会很好用。
Alpine是一个特殊的Linux发行版,旨在创建小型Docker镜像。
成功了!使用Alpine版本使镜像减少到~30MB!大约是原来大小的10%!
FROM ruby:3.0-alpine
WORKDIR /usr/src/app
COPY bin/printable-ascii ./
ENTRYPOINT ["./printable-ascii"]
它的效果非常好!任何拥有Docker的人都可以随时获得可打印的ASCII码
$ docker run sdball/printable-ascii --json --decimal --binary --hexadecimal --octal --range A-C | jq '.'
[ { "character": "A", "decimal": "65", "binary": "1000001", "hexadecimal": "41", "octal": "101" }, { "character": "B", "decimal": "66", "binary": "1000010", "hexadecimal": "42", "octal": "102" }, { "character": "C", "decimal": "67", "binary": "1000011", "hexadecimal": "43", "octal": "103" }]
功能上的特点
在这个简单的玩具脚本上工作绝对是一种乐趣,所以我继续增加更多的功能,但没有人需要。甚至我也不需要。我只是觉得它们很整洁!
--range 选项允许提供一个或多个可打印的ASCII字符范围。
一个--random NUMBER 选项,可以选择NUMBER 的随机可打印ASCII字符。
我还计划了更多的愚蠢的选项,哈哈!所有可打印的ASCII码!
也许有一天我真的会回到我需要一个可打印ASCII列表的项目中去。