能偶尔用上的 awk
英雄总有用武之地。
作者:@nixzhu
对于 app 内的图片,我们可用其名字获取:
let image = UIImage(named: "test_image")
但这样并不安全,我们可能拼错图片的名字,图片本身也可能被删除。而如果我们要在多处使用同一张图片,就更要时时小心。
我们可以用一个 UIImage 的扩展来消除我们的担忧:
extension UIImage {
static var xxx_testImage: UIImage {
return UIImage(named: "test_image")!
}
}
之后我们使用时,只需:
let image = UIImage.xxx_testImage
这样在输入时,除了编辑器会提供自动补全外,编译器也能保证我们不会输入不存在的图片名。
但我们还不能保证图片不会被从我们的 app 工程里删除,所以我们应该加一个测试,保证每次测试运行时,app 所需要的所有图片都能正常访问。
这些都不是问题,问题是,我们可能并没有一开始就考虑这个问题,因此,app 已经累计了很多图片,手动生成这个 extension 实在没有乐趣,自然,轮到脚本登场。(我知道有人会推荐 R.swift,不过它做了太多事,对本地化字符串的生成还不太好,也依赖 CocoaPod,所有并不太喜欢)
我们打开终端,先进入 app 的 Images.xcassets 目录里:ls -l
,输出类似:
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
,输出类似:
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 列切割出来:
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 默认用空白符做分割符,但这一句里我们指定用.
做分割符,然后输出第一列:
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 里是否有更方便的函数,但这里的代码能很工作,于是生成:
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(当前行) 和一个空格,得到:
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"}'
可得到:
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 这三个文本处理工具。不用很熟悉,知道它们如何工作即可,用时再查手册。
最终的脚本大概如下:
#!/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