能偶尔用上的 awk

英雄总有用武之地。

作者:@nixzhu


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

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

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

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

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

之后我们使用时,只需:

  1. let image = UIImage.xxx_testImage

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

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

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

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

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

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

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

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

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

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

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

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

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

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

这样我们就得到图片名字列表了。接下来我们想把图片名字做一个变换,️以符合 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. bubbleBody
  2. bubbleLeftTail
  3. bubbleRightTail
  4. ...

为了之后的代码生成,我们需要没有改变前的名字,因此再修改一下上面的命令:
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. bubble_body bubbleBody
  2. bubble_left_tail bubbleLeftTail
  3. bubble_right_tail bubbleRightTail
  4. ...

最后终于轮到代码生成了,依然再加一段 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. static var xxx_bubbleBody: UIImage {
  2. return UIImage(named: "bubble_body")!
  3. }
  4. static var xxx_bubbleLeftTail: UIImage {
  5. return UIImage(named: "bubble_left_tail")!
  6. }
  7. static var xxx_bubbleRightTail: UIImage {
  8. return UIImage(named: "bubble_right_tail")!
  9. }
  10. ...

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

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

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

最终的脚本大概如下:

  1. #!/bin/bash
  2. # Generate UIImage extension for images assets
  3. if [ $# -eq 0 ]; then
  4. echo "Usage: ./ios_static_images.sh path_to_images_assets"
  5. exit
  6. fi
  7. if [ ! -d $1 ]; then
  8. echo "Usage: ./ios_static_images.sh path_to_images_assets"
  9. exit
  10. fi
  11. echo "extension UIImage {"
  12. echo ""
  13. ls -l $1 | \
  14. grep imageset | \
  15. awk '{ print $9; }' | \
  16. awk -F"." '{ print $1; }' | \
  17. awk -F"_" '{ \
  18. out = $0" "; \
  19. for (i = 1; i <= NF; i++) { \
  20. if (i == 1) { \
  21. out = out $i; \
  22. } else { \
  23. out = out toupper(substr($i,1,1)) substr($i,2); \
  24. } \
  25. }; \
  26. print out \
  27. }' | \
  28. awk '{ \
  29. print " static var xxx_" $2 ": UIImage {"; \
  30. print " return UIImage(named: \"" $1 "\")!"; \
  31. print " }\n"; \
  32. }'
  33. echo "}"

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