V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
mylovesaber
V2EX  ›  Linux

echo 输出的内容中包括调用的变量时为何会自动乱序?内详

  •  
  •   mylovesaber · 2023-02-28 01:52:16 +08:00 · 2435 次点击
    这是一个创建于 625 天前的主题,其中的信息可能已经有所发展或是发生改变。

    本意是希望下载 github 文件的时候先检查下有没有达到拉取频率上限,并打印出提示信息,但输出的提示信息是乱序的,具体命令如下一行一条命令:

    githubGetRateInfo=$(curl -s -I -X POST https://api.github.com/users/octocat)
    postLimit=$(echo "${githubGetRateInfo}"|awk /^X-RateLimit-Limit/'{print $2}')
    postRemaining=$(echo "${githubGetRateInfo}"|awk /^X-RateLimit-Remaining/'{print $2}')
    echo "GitHub 调用速率为 ${postLimit} 次 /小时"
    

    echo 输出结果预期效果:

    GitHub 调用速率为 60 次 /小时
    

    实际输出:

     次 /小时 用速率为 60
    

    以下是 curl 那条命令获取的信息,目的是筛选出X-RateLimit-LimitX-RateLimit-Remaining的值

    HTTP/1.1 404 Not Found
    Server: GitHub.com
    Date: Mon, 27 Feb 2023 17:37:26 GMT
    Content-Type: application/json; charset=utf-8
    Content-Length: 84
    X-GitHub-Media-Type: github.v3; format=json
    x-github-api-version-selected: 2022-11-28
    X-RateLimit-Limit: 60
    X-RateLimit-Remaining: 57
    X-RateLimit-Reset: 1677522152
    X-RateLimit-Used: 3
    X-RateLimit-Resource: core
    Access-Control-Expose-Headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
    Access-Control-Allow-Origin: *
    Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
    X-Frame-Options: deny
    X-Content-Type-Options: nosniff
    X-XSS-Protection: 0
    Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
    Content-Security-Policy: default-src 'none'
    Vary: Accept-Encoding, Accept, X-Requested-With
    X-GitHub-Request-Id: xxxxxxxxxxx
    

    有没有大佬知道这是什么原因导致的顺序错乱?是我疏忽了什么地方吗?

    36 条回复    2023-03-02 12:05:20 +08:00
    mylovesaber
        1
    mylovesaber  
    OP
       2023-02-28 03:34:57 +08:00
    解决了,估计 github 写 api 那老哥电脑是 windows 的,导致返回的 header 信息每一行都带上了 win 特有的结尾符号,在变量 githubGetRateInfo 后面加上: `|tr -d '\r'` 即可
    geelaw
        2
    geelaw  
       2023-02-28 04:36:53 +08:00   ❤️ 6
    @mylovesaber #1 不太确定您的结论是如何来的,但是据我所知使用 CR LF 标记大多数行结尾是 HTTP 规定的,而且是自古以来。
    julyclyde
        3
    julyclyde  
       2023-02-28 08:52:58 +08:00
    @geelaw HTTP 里用 crlf ,和 curl 输出 crlf 是两码事
    AoEiuV020CN
        4
    AoEiuV020CN  
       2023-02-28 09:10:31 +08:00 via Android
    这么调岂不是又多浪费了一次,header 还不好解析,
    github 有提供专用的接口获取 api 限制以及剩下的次数,json 直接 jq 还方便解析,

    https://api.github.com/rate_limit
    aloxaf
        5
    aloxaf  
       2023-02-28 09:15:13 +08:00
    无法复现,你的环境及 curl 版本是多少?
    HappyStraw
        6
    HappyStraw  
       2023-02-28 10:03:41 +08:00
    https://www.rfc-editor.org/rfc/rfc2616#section-5

    ```text
    Request = Request-Line ; Section 5.1
    *(( general-header ; Section 4.5
    | request-header ; Section 5.3
    | entity-header ) CRLF) ; Section 7.1
    CRLF
    [ message-body ]
    ```

    1. HTTP 是 CRLF 换行, 不是用不用 windows 的问题.

    2. 在 Archlinux curl 7.88.1 下, HEAD https://api.github.com/users/octocat, 返回为 http/2, header 为小写

    ```text
    HTTP/2 404
    server: GitHub.com
    date: Tue, 28 Feb 2023 01:45:47 GMT
    content-type: application/json; charset=utf-8
    content-length: 84
    x-github-media-type: github.v3; format=json
    x-github-api-version-selected: 2022-11-28
    x-ratelimit-limit: 60
    x-ratelimit-remaining: 52
    x-ratelimit-reset: 1677550667
    x-ratelimit-used: 8
    x-ratelimit-resource: core
    ...省略
    ```

    3. awk 设置 IGNORECASE=1 大小写不敏感, 如:

    ```bash
    echo "${githubGetRateInfo}"|awk 'BEGIN{IGNORECASE=1}/^X-RateLimit-Limit:/{print $2}'|tr -d '\r'
    ```
    hahahahahahahah
        7
    hahahahahahahah  
       2023-02-28 11:43:36 +08:00 via iPhone
    你是直接在命令行执行的还是写到 sh 里面执行的
    geelaw
        8
    geelaw  
       2023-02-28 11:47:57 +08:00
    @julyclyde #3 curl 不参与编码解析,自然是 HTTP 给什么就返回什么,否则(若是 curl 参与编码解析的话)你把 curl 传入管道的时候会出现非常糟糕的状况。
    julyclyde
        9
    julyclyde  
       2023-02-28 13:11:20 +08:00
    @geelaw 并不是的。http 一直都 crlf ,但是 linux 版本的 curl 的输出是\n 而不是\r\n
    ysc3839
        10
    ysc3839  
       2023-02-28 13:26:09 +08:00 via Android
    @julyclyde 可以自己试试,我这里输出的就是 CRLF
    $ curl -s -I -X POST https://api.github.com/users/octocat | hexdump -C
    00000000 48 54 54 50 2f 32 20 34 30 34 20 0d 0a 73 65 72 |HTTP/2 404 ..ser|
    julyclyde
        11
    julyclyde  
       2023-02-28 14:23:39 +08:00
    @ysc3839 居然真的是 0d0a ;换了 github 之外的网址也是 0d0a
    我服了

    这样的话,OP 的假设就被推翻了呢,并不是“因为 github 的人用 windows”而导致 crlf ,而是 shell 没能正确处理 crlf ?
    geelaw
        12
    geelaw  
       2023-02-28 14:30:24 +08:00
    @julyclyde #11 是你对 shell 的期待有误,Unix 风格 shell 的管道完全是二进制的,任何对数据的解读都是程序完成。

    Re: “OP 的假设就被推翻了呢”
    为什么你会觉得 OP 的假设是自然的?那明明是《我比 GitHub 做 Web API 的员工更懂 HTTP 之口嗨 Windows 用户真时髦》最新一期。
    aloxaf
        13
    aloxaf  
       2023-02-28 14:32:38 +08:00
    @aloxaf #5 啊,我搞错了,确实可以复现

    原先都没注意过这个问题,也用 shell 处理过几次 curl 输出,竟然一直没踩坑
    julyclyde
        14
    julyclyde  
       2023-02-28 15:12:24 +08:00
    @geelaw curl 既然“按文本”输出,按说应该遵照 unix 风格的习惯用\n 啊。这是我的想法。
    看起来它并没有把每一行要输出的内容“按文本”输出,而是把从 HTTP 读到的直接发给 stdout 了
    geelaw
        15
    geelaw  
       2023-02-28 15:32:09 +08:00
    @julyclyde #14 然而你的这个想法和 #3 体现的想法是矛盾的,如果你认为 curl 应该“按文本”输出,那也和 GitHub Web API 输出的是 CRLF 还是 LF 没有关系,因为此时你认为的 curl 的行为会让用户无法感知 HTTP 传输的到底是 CRLF 还是 LF 。
    julyclyde
        16
    julyclyde  
       2023-02-28 15:44:37 +08:00
    @geelaw 我确实认为和 github 输出的是“哪一种换行”没什么关系啊。
    http 用 crlf 是遵循标准
    curl 输出 crlf 在我看来简直是无法想象的事情。虽然 OP 自己说是 crlf 了,但我当时是不信的。我并没有按你推测的那么认为
    flyqie
        17
    flyqie  
       2023-02-28 15:54:15 +08:00 via Android
    @julyclyde #16

    curl 要是输出的不标准你信不信会被一堆人围着骂。。

    curl 可以跨平台,它必须尽力保证在各平台之间的统一性。

    况且,curl 从来就不是"按文本"输出,它只是原样返回数据,你觉得它按文本输出只是因为这玩意它就是文本。。

    我倒是认为 curl 就是应该返回 crlf ,这是协议指定的,curl 做的就应该是把请求原样返回到 stdout ,这没有任何疑问。
    mylovesaber
        18
    mylovesaber  
    OP
       2023-02-28 17:40:06 +08:00
    @geelaw 那肯定是我理解问题了,我不太了解这东西有说错的还请见谅
    mylovesaber
        19
    mylovesaber  
    OP
       2023-02-28 17:43:11 +08:00
    @AoEiuV020CN 我是从这看到的:
    https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#rate-limiting

    json 的话我为了解析还得再拉一个 jq ,而现在环境写这个解析判定就是为了拉一个 yq ,反而有种鸡和蛋谁先出现的辩论感觉了,不过还是感谢,我之前不知道这方式
    mylovesaber
        20
    mylovesaber  
    OP
       2023-02-28 17:45:23 +08:00
    @AoEiuV020CN 我刚刚反复网页刷新你发的这个链接,能看到剩余次数也在随着刷新的变化而变化,但几个小时不登录的话,居然可用只剩余 20 次,有点意外。。。另外对于下载 release 中文件的操作,具体指的是接口中的哪个 key 呢?下载地址: https://api.github.com/repos/mikefarah/yq/releases/latest
    mylovesaber
        21
    mylovesaber  
    OP
       2023-02-28 17:47:21 +08:00
    @aloxaf 7.81.0 和 7.29.0 都存在,前者服务器 ubuntu minimal22.04 ,后者应该是 centos7 ,
    mylovesaber
        22
    mylovesaber  
    OP
       2023-02-28 17:48:08 +08:00
    @hahahahahahahah 两个方式都尝试了,最开始脚本中出现意外输出,手动 echo 也出现了
    mylovesaber
        23
    mylovesaber  
    OP
       2023-02-28 17:55:50 +08:00
    @geelaw 是我理解的太无脑让大家见笑了,之前的确没遇到过这问题,第一次遇到给整懵了,网上查也没头绪,没想到是编码的问题
    mylovesaber
        24
    mylovesaber  
    OP
       2023-02-28 17:57:58 +08:00
    @julyclyde 哈哈,其实你也理解错我意思了,我是有点调侃甩锅的意思,但我基于假设 curl 远端显示什么就应该原封不动得打印给我的默认想法的,而不是让 curl 对输出的结果做解析后再反馈给我
    mylovesaber
        25
    mylovesaber  
    OP
       2023-02-28 18:00:09 +08:00
    关于本贴问题我昨天加班到凌晨刚准备走,最后一次测试居然发现有这个问题就两头都发了请教,那边给到了我提示,然后我把我的处理方法也补充进去了,v2 没法在回复中用 md ,也没法编辑超时的帖子,所以直接贴隔壁站的链接吧。

    https://stackoverflow.com/questions/75584213/if-the-text-output-by-linux-echo-contains-variables-the-text-will-be-out-of-ord
    AoEiuV020CN
        26
    AoEiuV020CN  
       2023-02-28 18:06:58 +08:00
    @mylovesaber #20 这个接口不会占用次数的,变了说明有其他人在用你一样的 ip ,你没有唯一的公网 ip 吧,
    建议带上 token 再调用 api ,就和 ip 无关了,

    下载地址的话就是 browser_download_url ,

    诶你既然要解析下载地址,那不还是要提前拉一个 jq 或者其他什么解析 json 的,rate limit 不就可以一样的办法解析处理, 还是没必要读 header 呀,

    官方 header 用途是你实际调用后判断是否还有剩次数,而不是在实际使用之前特地去拿 header 的,
    mylovesaber
        27
    mylovesaber  
    OP
       2023-02-28 18:42:44 +08:00
    @AoEiuV020CN 我是为了应对不同数据库的备份要求,写了个 shell 的工具,然后读取配置文件来实现获取信息,读的是 yml ,所以解析工具就需要拉一个 yq ,还对应加了一个可以检测 yq 是否有升级的模块,然后测试下载、校验等操作的代码是否工作正常的时候意外遇到了 github 有解析上限的问题,所以不得以才加了个 25 楼的模块用于检查上限的问题,再为了不占用次数的话引入个 jq 感觉是不是有点多此一举哈哈,我想了解的 key 不是那个,是你发的那个链接里面有五段,每段都有各自的 rate limit 和 remaining 信息,我不知道如果是获取 release 文件链接的话应该会占用哪个 rate limit 和 remaining 信息

    ---

    获取下载链接的话这两行就够了:
    ```shell
    remoteYQLatestHTML="$(curl -s --max-time 15 v 站不能用外链 repos/mikefarah/yq/releases/latest)"
    yqDownloadLink=$(echo "${remoteYQLatestHTML}" | grep "browser_download_url.*.yq_linux_amd64\"" | awk -F '[" ]' '{print $(NF-1)}')
    ```
    AoEiuV020CN
        28
    AoEiuV020CN  
       2023-02-28 18:58:46 +08:00 via Android
    @mylovesaber #27
    .rate.remaining 就是对应你 header 里的值,

    另外你也可以直接把链接替换成我那个,然后照样读取 header ,就不消耗次数也不用改代码了,
    AoEiuV020CN
        29
    AoEiuV020CN  
       2023-02-28 19:03:13 +08:00 via Android
    @mylovesaber #27 顺便我也遇到过同样问题,
    我最后是选择打印 http code ,api 失败就报错不继续,
    顺便加上了 token ,有 token 一小时免费一千次,没有 token 一个 ip 一小时免费只有 60 次,太小气了,
    AoEiuV020CN
        30
    AoEiuV020CN  
       2023-02-28 19:06:39 +08:00 via Android
    @mylovesaber #27 你这样就算提前判断了也还是有可能靠不住,
    可能判断时剩下一次,到真正调用时却又没了,
    毕竟天知道这个 ip 有几个人在用,
    AoEiuV020CN
        31
    AoEiuV020CN  
       2023-02-28 19:07:16 +08:00 via Android
    mylovesaber
        32
    mylovesaber  
    OP
       2023-02-28 21:28:03 +08:00
    @AoEiuV020CN 这样 ok 了,还是不减次数的舒服啊,直接筛选出来就是数字,根本不用 string 转 int

    githubGetRateInfo=$(curl -s https://api.github.com/rate_limit|xargs|grep -o "rate: {.*.}"|sed 's/,/\n/g; s/{/\n/g; s/}/\n/g; s/ \+//g')
    postLimit=$(echo "${githubGetRateInfo}" | awk -F ':' /^limit/'{print $2}')
    postRemaining=$(echo "${githubGetRateInfo}" | awk -F ':' /^remaining/'{print $2}')
    mylovesaber
        33
    mylovesaber  
    OP
       2023-02-28 21:29:50 +08:00
    @AoEiuV020CN 公司都是搭建的 gitlab ,不用 github ,所以我自己调用问题不大,不过还是感谢分享,否则之前用的那个调一次少一次
    julyclyde
        34
    julyclyde  
       2023-03-01 11:26:25 +08:00
    echo -n ${postLimit}|hexdump -C
    00000000 36 30 0d |60.|
    可以看出来 postLimit 变量里面最后含有一个 0d
    所以,为什么
    echo "GitHub 调用速率为 ${postLimit} 次 /小时"
    的结果是
    次 /小时用速率为 60
    呢?


    echo "GitHub 调用速率为 ${postLimit} 次 /小时" |hexdump -C
    00000000 47 69 74 48 75 62 20 e8 b0 83 e7 94 a8 e9 80 9f |GitHub .........|
    00000010 e7 8e 87 e4 b8 ba 20 36 30 0d 20 e6 ac a1 20 2f |...... 60. ... /|
    00000020 e5 b0 8f e6 97 b6 0a |.......|
    00000027

    看起来输出内容是对的,但是显示效果有问题
    geelaw
        35
    geelaw  
       2023-03-02 11:32:29 +08:00 via iPhone
    @julyclyde #34 CR 的作用是“回车”,也就是把光标移动到行开头,在 CR 之后继续打印,自然会踩踏先前的内容。理同 ab BKSP cd 看起来是 acd 一样。
    julyclyde
        36
    julyclyde  
       2023-03-02 12:05:20 +08:00
    @geelaw 所以问题是出在“只回车 没换行”?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5635 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 06:32 · PVG 14:32 · LAX 22:32 · JFK 01:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.