循环遍历对象并使用Shell命令的输出来添加一个字段

huangapple go评论72阅读模式
英文:

jq loop over objects and use shell command output to add a field

问题

我正在尝试迭代遍历JSON并为JSON中的每个对象添加一个字段。我在Gitlab管道阶段中执行此操作,但我认为这不重要。

JSON如下:

{
 "service10": {
     "path_to_file" : "service10.txt"
     },
"service192": {
     "path_to_file" : "service192.txt"
     }
}
     

我正在使用path_to_file来检查Gitlab仓库中是否存在该文件,并且我需要保存HTTP响应,目前的代码如下:

check_files_existence:
  stage: update-external-repo
  script:
    - apt-get update --fix-missing
    - apt-get install jq -y
    - HTTP_RESPONSE_CODES=()

    # Check for file
    - |-
      for FILE_TO_CHECK in $(jq -r '.[].path_to_file ' $JSON_TO_READ); do
        
        last_status_code=($(curl [omitted]))
        
        HTTP_RESPONSE_CODES+=($last_status_code)      
        
       done

   # Write array of responses into artifact
    - echo "${HTTP_RESPONSE_CODES[@]}" >> ./http_response_code.txt

  artifacts:
    paths:
      - ./http_response_code.txt
    expire_in: 10 min

这将生成一个包含以空格分隔的HTTP响应的.txt文件。

我想要在循环遍历服务时将HTTP响应添加到每个服务中,以便JSON看起来像这样:

{
 "service10": {
     "path_to_file" : "service10.txt",
     "http_response": 200
     },
"service192": {
     "path_to_file" : "service192.txt",
     "http_response": 404
     }
}

然后,我将将其导出为构件,以在Gitlab管道的另一个阶段中使用。

使用上面的for循环,我可以获取文件路径,但然后看起来我无法更新JSON。如果我使用类似以下的内容:

for OBJECT in $(jq -r '.[] ' $JSON_TO_READ)

我无法读取文件路径,因为使用

jq '.path_to_file $OBJECT' 

会返回错误。

有办法做到这一点吗?

英文:

I'm trying to iterate over a JSON and add a field to each object in the JSON.
I'm doing this in a Gitlab pipeline stage but I don't think is important.

The JSON is this:

{
 "service10": {
     "path_to_file" : "service10.txt"
     },
"service192": {
     "path_to_file" : "service192.txt"
     }
}
     

What I'm doing is using the path_to_file to check in a Gitlab repo if that file exists and I need to save the http response, the code now looks as follows:

check_files_existence:
  stage: update-external-repo
  script:
    - apt-get update --fix-missing
    - apt-get install jq -y
    - HTTP_RESPONSE_CODES=()

    # Check for file
    - |-
      for FILE_TO_CHECK in $(jq -r '.[].path_to_file ' $JSON_TO_READ); do
        
        last_status_code=($(curl [omitted]))
        
        HTTP_RESPONSE_CODES+=($last_status_code)      
        
       done

   # Write array of responses into artifact
    - echo "${HTTP_RESPONSE_CODES[@]}" >> ./http_response_code.txt

  artifacts:
    paths:
      - ./http_response_code.txt
    expire_in: 10 min

This generates a .txt file with the http responses separated by a whitespace.

What I want to do is add a field to each service while I loop over them with the http_response, so the JSON will be like:

{
 "service10": {
     "path_to_file" : "service10.txt",
     "http_response": 200
     },
"service192": {
     "path_to_file" : "service192.txt",
     "http_response": 404
     }
}

And then I'm going to export this an artifact to use it in another stage in my Gitlab Pipeline.

Using the above for loop I can get the file path but then it looks like I cannot update the JSON, if I use something like

for OBJECT in $(jq -r '.[] ' $JSON_TO_READ)

I cannot read the file path because using

jq '.path_to_file $OBJECT' 

returns an error.

Is there a way to do this?

Thanks.

答案1

得分: 0

这可能需要两个独立的jq副本。假设您的输入文件已更改为有效的JSON:

jq -r 'to_entries[] | [.key, .value.path_to_file] | @tsv' <"$JSON_TO_READ" \
| while IFS=$'\t' read -r key value; do
    # 在您的真实系统中取消注释以下行
    (( ++http_status )) # 用于在不调用curl的情况下进行测试
    # 取消注释下一行并在您的真实系统中替换URL
    # http_status=$(curl -s -o /dev/null -w "%{http_code}" "http://example.com/$key") || continue
    printf '%s\t%s\n' "$key" "$http_status"
  done \
| jq -Rn --slurpfile input_file "$JSON_TO_READ" '
    reduce inputs as $inline
      ($input_file[0];
       (($inline | split("\t")) as $pieces |
        .[$pieces[0]].http_response = ($pieces[1])))'

分解此shell管道:

  • 首先,有一个生成带有键和值的制表符分隔流的jq副本。此处的条目将如下所示:

    service10<TAB>service10.txt
    
  • 接下来,我们有一个shell管道(运行BashFAQ #1循环),读取这两个变量,并运行curl(或在上面的示例中,仅将递增的数字作为状态代码)。以确定HTTP状态代码:

    service10<TAB>200
    
  • 最后,我们有另一个jq的副本,通过--slurpfile接受您的输入文件,并从stdin读取制表符分隔的服务键和HTTP状态代码流,使用一个减少器来逐渐更新状态文件以获取新值(因为--slurpfile包含在文件中找到的JSON文档的列表,而不仅仅是单个文档,所以我们引用$input_file[0]以引用文件中的第一个文档 - 唯一的文档)。

英文:

This probably calls for two separate copies of jq. Assuming your input file were changed to be valid JSON:

jq -r &#39;to_entries[] | [.key, .value.path_to_file] | @tsv&#39; &lt;&quot;$JSON_TO_READ&quot; \
| while IFS=$&#39;\t&#39; read -r key value; do
    # comment this next line in your real system
    (( ++http_status )) # stand-in so this can be tested without calling curl
    # uncomment this next line and replace the url in your real system
    #http_status=$(curl -s -o /dev/null -w &quot;%{http_code}&quot; &quot;http://example.com/$key&quot;) || continue
    printf &#39;%s\t%s\n&#39; &quot;$key&quot; &quot;$http_status&quot;
  done \
| jq -Rn --slurpfile input_file &quot;$JSON_TO_READ&quot; &#39;
    reduce inputs as $inline
      ($input_file[0];
       (($inline | split(&quot;\t&quot;)) as $pieces |
        .[$pieces[0]].http_response = ($pieces[1])))&#39;

Breaking down the shell pipeline:

  • First, we have a copy of jq that emits a tab-separated stream with your key and value. An entry here would look like:

    service10&lt;TAB&gt;service10.txt
    
  • Next, we have a shell pipeline (running a BashFAQ #1 loop) that reads these two variables, and runs curl (or in the example above, just adds an incrementing number as the status code) to determine the HTTP status code:

    service10&lt;TAB&gt;200
    
  • Finally, we have another copy of jq that accepts your input file via --slurpfile, and reads the stream of tab-separated service keys and HTTP status codes from stdin, using a reducer to incrementally update the status file as new values come in. (Because --slurpfile contains a list of JSON documents found in the file, not just a single document, we're referring to $input_file[0] to refer to the first -- and only -- document in the file).

答案2

得分: 0

如果您的文件不太大,并且不介意多次阅读相同的输入文件,那么这是另一种可能适用的解决方案:

  1. 计算对象中的键值对数量 链接
  2. 从对象中按索引提取单个条目
  3. 从条目中获取 path_to_file 的值
  4. 调用 curl
  5. http_response 添加到条目
  6. 使用 jq -s (--slurp) 从条目重建对象
input='input.json'
length="$(jq 'length' "$input")"
for i in $(seq "$length"); do
  entry="$(jq --argjson idx "$i" 'to_entries[$idx-1]' "$input")"
  path="$(printf '%s' "$entry" | jq -r '.value.path_to_file')"
  last_status_code="$(curl ... "$path")"
  printf '%s' "$entry" | jq --argjson status "$last_status_code" '.value.http_response = $status'
done | jq -s 'from_entries'
英文:

If your file isn't too big and you don't mind reading the same input file multiple times, then here is another solution that should work:

  1. Count the number of key-value-pairs in the object
  2. Extract single entry by index from object
  3. Get path_to_file value from entry
  4. Call curl
  5. Add http_response to entry
  6. Use jq -s (--slurp) to reconstruct the object from its entries
input=&#39;input.json&#39;
length=&quot;$(jq &#39;length&#39; &quot;$input&quot;)&quot;
for i in $(seq &quot;$length&quot;); do
  entry=&quot;$(jq --argjson idx &quot;$i&quot; &#39;to_entries[$idx-1]&#39; &quot;$input&quot;)&quot;
  path=&quot;$(printf &#39;%s&#39; &quot;$entry&quot; | jq -r &#39;.value.path_to_file&#39;)&quot;
  last_status_code=&quot;$(curl ... &quot;$path&quot;)&quot;
  printf &#39;%s&#39; &quot;$entry&quot; | jq --argjson status &quot;$last_status_code&quot; &#39;.value.http_response = $status&#39;
done | jq -s &#39;from_entries&#39;

huangapple
  • 本文由 发表于 2023年3月7日 01:20:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/75653903.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定