「转载」能偶尔用上的 awk

能偶尔用上的 awk

英雄总有用武之地。

原文:能偶尔用上的 awk
作者:@nixzhu


对于 app 内的图片,我们可用其名字获取:

1
let image = UIImage(named: "test_image")

但这样并不安全,我们可能拼错图片的名字,图片本身也可能被删除。而如果我们要在多处使用同一张图片,就更要时时小心。

我们可以用一个 UIImage 的扩展来消除我们的担忧:

1
2
3
4
5
extension UIImage {
static var xxx_testImage: UIImage {
return UIImage(named: "test_image")!
}
}

之后我们使用时,只需:

1
let image = UIImage.xxx_testImage

这样在输入时,除了编辑器会提供自动补全外,编译器也能保证我们不会输入不存在的图片名。

但我们还不能保证图片不会被从我们的 app 工程里删除,所以我们应该加一个测试,保证每次测试运行时,app 所需要的所有图片都能正常访问。

这些都不是问题,问题是,我们可能并没有一开始就考虑这个问题,因此,app 已经累计了很多图片,手动生成这个 extension 实在没有乐趣,自然,轮到脚本登场。(我知道有人会推荐 R.swift,不过它做了太多事,对本地化字符串的生成还不太好,也依赖 CocoaPod,所有并不太喜欢)

我们打开终端,先进入 app 的 Images.xcassets 目录里:ls -l,输出类似:

1
2
3
4
5
6
7
total 8
drwxr-xr-x 22 nix staff 748 12 16 2015 AppIcon.appiconset
-rw-r--r-- 1 nix staff 62 5 5 17:06 Contents.json
drwxr-xr-x 4 nix staff 136 4 3 2015 bubble_body.imageset
drwxr-xr-x 4 nix staff 136 4 3 2015 bubble_left_tail.imageset
drwxr-xr-x 4 nix staff 136 4 3 2015 bubble_right_tail.imageset
...

可见,只有imageset后缀的才是正常的图片,因此我们 grep 过滤一下:ls -l | grep imageset,输出类似:

1
2
3
4
drwxr-xr-x   4 nix  staff  136  4  3  2015 bubble_body.imageset
drwxr-xr-x 4 nix staff 136 4 3 2015 bubble_left_tail.imageset
drwxr-xr-x 4 nix staff 136 4 3 2015 bubble_right_tail.imageset
...

注意上面命令中的|为管道,它的作用是将其左边命令的输出作为右边命令的输入。

然后就轮到 awk 出场:ls -l | grep imageset | awk '{print $9}',将第 9 列切割出来:

1
2
3
4
bubble_body.imageset
bubble_left_tail.imageset
bubble_right_tail.imageset
...

注意 awk 默认使用空白符作为分隔符,$9 表示第 9 列。

我们离得到图片名字列表又近了一步,继续 awk:ls -l | grep imageset | awk '{print $9}' | awk -F"." '{print $1}'

这次我们增加了awk -F"." '{print $1}',awk 默认用空白符做分割符,但这一句里我们指定用.做分割符,然后输出第一列:

1
2
3
4
bubble_body
bubble_left_tail
bubble_right_tail
...

这样我们就得到图片名字列表了。接下来我们想把图片名字做一个变换,️以符合 Swift 命名规范,例如将bubble_body变成bubbleBody

因此,继续追加一个 awk 命令:ls -l | grep imageset | awk '{print $9}' | awk -F"." '{print $1}' | awk -F"_" '{out="";for(i=1;i<=NF;i++){if(i==1){out=$i}else{out=out""toupper(substr($i,1,1))substr($i,2)}};print out}'

这次比较复杂,先指定用_做分割符;然后定义了一个变量out="",看起来就是个空字符串;之后进入循环,以i为变量;其中NF是 awk 的内置变量,表示当前行的“列数”,这里我们用_做分割符,因此第一行有两列,NF 就为 2,第二行有三列,N F 就等于 3,awk 是一行一行地处理输入的,所以每一行的 NF 可以不同;在 for 循环的循环体里(注意大括号),我们先判断 i 是否为 1,是 1 的话就直接赋值给 out,否则就将其第一个字母变为大写再追加到 out 后面,这里的代码比较难看,toupper(substr($i,1,1))中的 substr 从第 i 列取出第一个字符然后用 toupper 变为大写,接着substr($i,2)表示剩下的字符串,前面的out=out""...表示给 out 追加一个空白字符和之后的大写字母和剩下的字符串,这样就实现了首字母大写的功能。

这很麻烦,但我不知道 awk 里是否有更方便的函数,但这里的代码能很工作,于是生成:

1
2
3
4
bubbleBody
bubbleLeftTail
bubbleRightTail
...

为了之后的代码生成,我们需要没有改变前的名字,因此再修改一下上面的命令:
ls -l | grep imageset | awk '{print $9}' | awk -F"." '{print $1}' | awk -F"_" '{out=$0" ";for(i=1;i<=NF;i++){if(i==1){out=$i}else{out=out""toupper(substr($i,1,1))substr($i,2)}};print out}'

只有一点改变,初始化 out 时用了 $0(当前行) 和一个空格,得到:

1
2
3
4
bubble_body bubbleBody
bubble_left_tail bubbleLeftTail
bubble_right_tail bubbleRightTail
...

最后终于轮到代码生成了,依然再加一段 awk:ls -l | grep imageset | awk '{print $9}' | awk -F"." '{print $1}' | awk -F"_" '{out=$0" ";for(i=1;i<=NF;i++){if(i==1){out=$i}else{out=out""toupper(substr($i,1,1))substr($i,2)}};print out}' | awk '{print "static var xxx_"$2": UIImage {\n\treturn UIImage(named: \""$1"\")!\n}\n"}'

可得到:

1
2
3
4
5
6
7
8
9
10
11
12
static var xxx_bubbleBody: UIImage {
return UIImage(named: "bubble_body")!
}

static var xxx_bubbleLeftTail: UIImage {
return UIImage(named: "bubble_left_tail")!
}

static var xxx_bubbleRightTail: UIImage {
return UIImage(named: "bubble_right_tail")!
}
...

我们的目的就基本达到。毫不夸张地说,我们只用“一句代码”就搞定了。

然后就是搜索项目代码中使用 UIImage 的代码,将它们替换为更安全的用法,并写一个测试,在测试里访问所有的图片,当然,测试代码也可用类似上面的脚本去生成。

我并非 awk 专家,不过我建议所有程序员都学习 grep、awk 以及本文并未提及的 sed 这三个文本处理工具。不用很熟悉,知道它们如何工作即可,用时再查手册。

最终的脚本大概如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/bin/bash

# Generate UIImage extension for images assets

if [ $# -eq 0 ]; then
echo "Usage: ./ios_static_images.sh path_to_images_assets"
exit
fi

if [ ! -d $1 ]; then
echo "Usage: ./ios_static_images.sh path_to_images_assets"
exit
fi

echo "extension UIImage {"
echo ""

ls -l $1 | \
grep imageset | \
awk '{ print $9; }' | \
awk -F"." '{ print $1; }' | \
awk -F"_" '{ \
out = $0" "; \
for (i = 1; i <= NF; i++) { \
if (i == 1) { \
out = out $i; \
} else { \
out = out toupper(substr($i,1,1)) substr($i,2); \
} \
}; \
print out \
}' | \
awk '{ \
print " static var xxx_" $2 ": UIImage {"; \
print " return UIImage(named: \"" $1 "\")!"; \
print " }\n"; \
}'

echo "}"

欢迎转载,但请一定注明出处! https://github.com/nixzhu/dev-blog

坚持原创技术分享,您的支持将鼓励我继续创作!