• 能偶尔用上的 awk

    能偶尔用上的 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