给产品同学解决一个小问题

短、平、快。

背景

产品同学给了一个店铺 shopId 列表,想要知道这些店铺的最近一段时间的同城送订单的数量。

shop.txt

111,222,333

有个现成的命令 curl_search 可以拿到单个店铺的同城送订单的数量:

curl -s -H "Content-Type: application/json" -X POST -d '{"source":"xxx", "requestId":"111", "searchParam": {"shopId": 111, "endTime":1583424000,"startTime":1580486400, "deliveryType":"local_delivery"}}' http://127.0.0.1:7001/app/order/search

curl_search 的结果如下。 total 即是同城送订单的数量。

{"result":true,"code":0,"message":null,"data":{"success":true,"code":200,"message":"successful","data":{"orderNos":["202003051700001"],"total":22}}}

想拿到的结果是:每行一个 shopId total

111 22
222 256
333 1024

方案

使用Shell

先编写一个 脚本 curl_orders.sh:

#!/bin/sh

shopId=$1

curl -s -H "Content-Type: application/json" -X POST -d  '{"source":"xxx", "requestId":"'"${shopId}"'", "searchParam": {"shopId":"'"${shopId}"'", "endTime":1583424000,"startTime":1580486400,"deliveryType":"local_delivery"}}' http://127.0.0.1:7001/app/order/search | echo $shopId $(sed -r 's/^.*total.*:([0-9]+)\}.*$/\1/')

然后使用 :

cat /tmp/shop.txt | tr "," "\n" | xargs -I {} sh curl_orders.sh {}  > result.txt

使用Python

编写一个 Python 脚本 fetch_totals.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import commands
import json

def fetchTotal(curl_cmd, success, fail):

    (status, result) = commands.getstatusoutput(curl_cmd) 
    if status == 0:
        return success(result)
    return fail(result)

def success(result):
    obj = json.loads(result)
    if obj['result'] and obj['data']["success"]:
        return obj['data']['data']['total']
    return "Error"

def fail(result):
    return "Error"

if __name__ == '__main__':
  
   f = open('shop.txt')
   for line in f:
       shopIds = line.strip().split(',')
       for shopId in shopIds:
           cmd = """curl -s -H "Content-Type: application/json" -X POST -d  '{"source":"xxx", "requestId":"""+ str(shopId) + """, "searchParam": {"shopId":""" + str(shopId) + """, "endTime":"1583424000","startTime":"1580486400", "deliveryType":"local_delivery", "exportedFieldNames":[]}}' http://127.0.0.1:7001/app/order/search"""
           print shopId, " " , fetchTotal(cmd, success, fail)


使用Python+Shell

实际上,Shell 更适合构建任务处理的命令流,而 Python 更适合处理具体逻辑。两者结合食用更佳。先编写处理单个店铺的 Python 脚本 fetch_total.py :

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import commands
import json
import sys

def fetchTotal(curl_cmd, success, fail):

    (status, result) = commands.getstatusoutput(curl_cmd) 
    if status == 0:
        return success(result)
    return fail(result)

def success(result):
    obj = json.loads(result)
    if obj['result'] and obj['data']["success"]:
        return obj['data']['data']['total']
    return "Error"

def fail(result):
    return "Error"

def buildCmd(args):
    shopId = args[1]
    return """curl -s -H "Content-Type: application/json" -X POST -d  '{"source":"xxx","requestId":"""+ str(shopId) + """, "searchParam": {"kdtId":""" + str(shopId) + """, "endTime":"1583424000","startTime":"1580486400", "deliveryType":"local_delivery"}}' http://127.0.0.1:7001/app/order/search"""

if __name__ == '__main__':
    args = sys.argv
    shopId = args[1]
    print str(shopId), " " , fetchTotal(buildCmd(args), success, fail)

然后使用:cat shop.txt | xargs -d "," -I {} python fetch_total.py {} 即可。

求解

方案选择

最先想到用 Python 来解决。但是 python 解决要拼参数,写调用 curl 的脚本。有点麻烦。 接着想去数据平台用 hive 捞,但是没有同城送订单的标识。想到既然有 curl 命令可以拿到单个店铺的结果,只要解决批量的问题即可。

这里有两个问题要解决:1. 从 curl_search 命令的结果中解析出 total ; 2. 批量调用 curl 命令,必须把 shopId 作为参数传进去。

解析total

解析 total , 使用 sed + 正则捕获能力。

要领是:将 需要的东西 用 ( ) 括起来,然后用引用替换。比如,我只要 total 冒号后面的 数字, 首先将需要的部分 ([0-9]+) 括起来 ,然后使用 左边 "total".*: 和 右边 \} 进行识别, 最后通过引用符号 $1 拿到。数字标识第几个(被捕获的分组)括号。

正则表达式基础见: "正则表达式基础知识"

参数化调用curl

需要把 shopId 传到 curl 命令中。 但是 curl 命令的选项 -d 的参数 '{"shopId":"$shopId", "field2":"yyy"}',既有 双引号也有单引号,这些都会使得变量引用 $shopId 变成普通的字符串,而不是被替换成指定的值。 可以使用 "'${shopId}'" 来传入 shopId。

xargs

xargs 可以读取文件并将每一行数据作为参数传给指定的命令执行。 基本形式是: cat file.txt | xargs -I {} Command {} 。 {} 就是 file.txt 里的每一行数据。

这里之所以将调用 curl 并解析出 total 写成一个 curl_orders.sh 脚本文件,也是为了 xargs 命令更加简洁。 xargs -d [sep] 是使用分隔符 sep 将输入分割成多条数据,然后将多条数据分别作为一个入参依次传递给指定命令处理。

命令替换

$(command) : 用命令 command 的计算值来替换 command 本身,从而能将命令的执行结果作为参数直接传给其它命令。

相关示例:

cat shop.txt | xargs -d "," -I {} echo $(echo \{\"source\":\"xxx\",\"requestId\":\"98765432\",\"searchParam\":\{\"shopId\":{},\"endTime\":1583424000,\"startTime\":1580486400,\"deliveryType\":\"local_delivery\"\}\})

curl -s http://127.0.0.1:7001/app/order/search -H "Content-Type: application/json" -X POST -d $(echo \{\"source\":\"xxx\",\"requestId\":\"98765432\",\"searchParam\":\{\"kdtId\":37,\"endTime\":1583424000,\"startTime\":1580486400,\"deliveryType\":\"local_delivery\"\}\})

管道

Shell 最强大的一点是其”胶合“能力,可以将任意多个小程序组合起来完成一件事。 管道 | 将上一个程序的输出定向到下一个程序的输入,从而将两个相邻的程序连接起来。管道是实现胶合能力的强大机制。


重定向

> 是重定向符号,可以将命令执行结果重定向到文件里。当命令的结果非常大时适用。


完整命令

一般做法是,先将简单的命令调试好,再整合到一起。

不过,依然太难了。需要把 “参数传入 curl 命令”,“命令替换”,“字符转义” 等全部整合。 尤其是 “参数传入 curl 命令” 这一步。

完整的命令如下:

sed 's/,/\n/g' shop.txt | xargs -I {} sh -c 'echo {}" "$(curl -s -H "Content-Type: application/json" -X POST -d "{\"source\":\"xxx\", \"requestId\":\"123456879\", \"searchParam\": {\"shopId\":{},\"endTime\":1583424000,\"startTime\":1580486400, \"deliveryType\":\"local_delivery\"}}" http://127.0.0.1:7001/app/order/search | sed -r "s/^.*total.*:([0-9]+)\}.*$/\1/")'

sed 's/,/\n/g' shop.txt | xargs -I {} sh -c 'total=$(curl -s -H "Content-Type: application/json" -X POST -d "{\"source\":\"xxx\",\"requestId\":\"123456879\", \"searchParam\": {\"shopId\":{},\"endTime\":1583424000,\"startTime\":1580486400, \"deliveryTypeDesc\":\"local_delivery\"}}" http://127.0.0.1:7001/app/order/search | sed -r "s/^.*total.*:([0-9]+)\}.*$/\1/");echo {} $total'

你可能好奇为啥 -d 里面弄了那么多转义。主要是Java 服务端要整型,shell 默认给传字符串。搞这么多转义就是为了拼那个 json 参数串同时把店铺ID当做整数传过去。

本来想省事写到一行,但写到一行太容易出错而且难以排查,而且有多个参数要传入时,可扩展性不佳。关键 curl 命令的选项参数既有单引号又有双引号,命令也很长,放在 shell 一行上,完成正确的字符串拼接都是很头疼的事情。 看来就是不适合整在一行的。不做这种不必要的折腾了。带有单双引号的较复杂的长命令行的拼接,写在脚本文件里更合适。

引申

使用循环如下。 有命名参数果然容易传参。

for shopId in $(sed 's/,/\n/g' shop.txt); do echo $shopId $(curl -s -H "Content-Type: application/json" -X POST -d '{"source":"xxx", "requestId":"$shopId", "searchParam": {"shopId":"'$shopId'", "endTime":"1583424000","startTime":"1580486400", "deliveryType":"local_delivery"}}' http://127.0.0.1:7001/app/order/search | sed -r 's/^.*total.*:([0-9]+)\}.*$/\1/'); done

sed 's/,/\n/g' shop.txt | while read line; do echo $line $(curl -s -H "Content-Type: application/json" -X POST -d '{"source":"xxx","requestId":"$line", "searchParam": {"shopId":"'$line'", "endTime":"1583424000","startTime":"1580486400", "deliveryType":"local_delivery"}}' http://127.0.0.1:7001/app/order/search | sed -r 's/^.*total.*:([0-9]+)\}.*$/\1/'); done

小结

此例展示 Shell 命令用来解决临时小任务所具备的”短、平、快“的特征。 不过,要构造复杂的命令,要对 Shell 非常熟悉才行,因为其中需要整合多种东西,尤其涉及到字符串转义和命令传参的时候;否则反而失去了“快”的优势。 此外,最好能将复杂命令拆解成单个命令或脚本,这样更容易写出简洁的整合命令。

要构造较为复杂的任务脚本,还是采用自己较为熟悉的语言,比如 Python 。使用 Python 实现,拿到 curl 结果后,使用 json 模块解析即可得到 total ; 然后再写个 for 循环即可解决批量调用问题。

结论是:用 python 写个脚本处理单条记录,然后用 xargs -I {} python xxx.py {} 就可以了。 这样,需要有一个正交互补的 python 工具包。 Shell 更适合构建任务处理的命令流,而 Python 更适合处理具体逻辑。两者结合食用更佳。


posted @ 2020-03-06 21:34  琴水玉  阅读(441)  评论(0编辑  收藏  举报