<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yuya Takeyama</title>
    <description>The latest articles on DEV Community by Yuya Takeyama (@yuyatakeyama).</description>
    <link>https://dev.to/yuyatakeyama</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F46605%2F76790ff0-2577-472e-940c-3ca781e98890.jpeg</url>
      <title>DEV Community: Yuya Takeyama</title>
      <link>https://dev.to/yuyatakeyama</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yuyatakeyama"/>
    <language>en</language>
    <item>
      <title>Mask multiple lines text in GitHub Actions Workflow</title>
      <dc:creator>Yuya Takeyama</dc:creator>
      <pubDate>Sun, 26 May 2024 13:36:10 +0000</pubDate>
      <link>https://dev.to/yuyatakeyama/mask-multiple-lines-text-in-github-actions-workflow-1a0</link>
      <guid>https://dev.to/yuyatakeyama/mask-multiple-lines-text-in-github-actions-workflow-1a0</guid>
      <description>&lt;p&gt;This article is a translation of an article I originally wrote in Japanese, translated into English using ChatGPT with some modifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.yuyat.jp/post/mask-multiple-lines-text-in-github-actions-workflow/"&gt;GitHub Actions の Workflow 内で複数行の文字列をマスクする&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  tl; dr
&lt;/h2&gt;

&lt;p&gt;Although it might seem like a bit of bad practice, the content of &lt;code&gt;$multiple_lines_text&lt;/code&gt; can be masked with the following one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;echo "::add-mask::$(echo "$multiple_lines_text" | sed ':a;N;$!ba;s/%/%25/g' | sed ':a;N;$!ba;s/\r/%0D/g' | sed ':a;N;$!ba;s/\n/%0A/g')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;add-mask&lt;/code&gt; Command in GitHub Actions
&lt;/h2&gt;

&lt;p&gt;GitHub Actions has a feature called workflow commands.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions"&gt;Workflow commands for GitHub Actions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;add-mask&lt;/code&gt; command is used to mask specific strings in subsequent outputs. For example, by executing the following command, the string "Hello, World!" will be masked as "***" in subsequent outputs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "::add-mask::Hello, World!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Values retrieved from secrets can be masked this way, but using the &lt;code&gt;add-mask&lt;/code&gt; command allows dynamically specifying strings to be masked.&lt;/p&gt;

&lt;p&gt;Additionally, this command is implemented in the NPM package &lt;a href="https://github.com/actions/toolkit/tree/main/packages/core"&gt;@actions/core&lt;/a&gt;. By using &lt;code&gt;core.setSecret('Hello, World!');&lt;/code&gt; in JavaScript code, the same masking can be achieved.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;setSecret&lt;/code&gt; function essentially builds and outputs the &lt;code&gt;::add-mask::~~~&lt;/code&gt; string to the standard output, so the mechanism is identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues When Masking Multiple Lines
&lt;/h2&gt;

&lt;p&gt;If you try to mask a string like &lt;code&gt;Foo\nBar\nBaz&lt;/code&gt; in a shell script without any considerations, it would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;::add-mask::Foo
Bar
Baz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command breaks due to the line breaks, and only "Foo" gets masked. This can lead to unwanted side effects like the string "Foo Fighters" being masked as "*** Fighters".&lt;/p&gt;

&lt;p&gt;The workflow commands documentation briefly mentions handling multiple strings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings"&gt;Multiline strings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, these techniques are meant for inputting multiline environment variables or output values into &lt;code&gt;$GITHUB_ENV&lt;/code&gt; or &lt;code&gt;$GITHUB_OUTPUT&lt;/code&gt;, and based on my tests, they don’t work with workflow commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examining the &lt;code&gt;core.setSecret&lt;/code&gt; Code
&lt;/h2&gt;

&lt;p&gt;By examining the &lt;code&gt;core.setSecret&lt;/code&gt; function in @actions/core, it appears that the values to be masked are escaped using a function called &lt;code&gt;escapeData&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/actions/toolkit/blob/d1df13e178816d69d96bdc5c753b36a66ad03728/packages/core/src/command.ts#L80-L85"&gt;https://github.com/actions/toolkit/blob/d1df13e178816d69d96bdc5c753b36a66ad03728/packages/core/src/command.ts#L80-L85&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;escapeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;toCommandValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/%/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%25&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%0D&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%0A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;toCommandValue&lt;/code&gt; function simply returns the string as-is if it’s already a string, so it can be ignored.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/actions/toolkit/blob/d1df13e178816d69d96bdc5c753b36a66ad03728/packages/core/src/utils.ts#L11-L18"&gt;https://github.com/actions/toolkit/blob/d1df13e178816d69d96bdc5c753b36a66ad03728/packages/core/src/utils.ts#L11-L18&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, the &lt;code&gt;replace&lt;/code&gt; method replaces &lt;code&gt;%&lt;/code&gt; with &lt;code&gt;%25&lt;/code&gt;, &lt;code&gt;\r&lt;/code&gt; with &lt;code&gt;%0D&lt;/code&gt;, and &lt;code&gt;\n&lt;/code&gt; with &lt;code&gt;%0A&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By executing a command like this, &lt;code&gt;Foo\nBar\nBaz&lt;/code&gt; gets masked correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"::add-mask::Foo%0ABar%0ABaz"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After asking ChatGPT about how to perform these replacements in a shell script, I arrived at the following conclusion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"::add-mask::&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$multiple_lines_text&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;':a;N;$!ba;s/%/%25/g'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;':a;N;$!ba;s/\r/%0D/g'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;':a;N;$!ba;s/\n/%0A/g'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the &lt;code&gt;s/%/%25/g&lt;/code&gt; part is clear, the preceding &lt;code&gt;:a;N;$!ba;&lt;/code&gt; part is obscure. I understand from ChatGPT that it’s necessary for handling line breaks, but I can’t explain it in detail here.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Is This Necessary?
&lt;/h2&gt;

&lt;p&gt;This article is written for those wondering how to mask multiline strings, regardless of the purpose. For me, there was a specific use case: safely storing and using a GitHub App's Private Key.&lt;/p&gt;

&lt;p&gt;To achieve this, I believe the following steps are necessary:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Encode the Private Key in Base64 and store it in AWS Secrets Manager.&lt;/li&gt;
&lt;li&gt;Use OIDC for authentication with AWS.&lt;/li&gt;
&lt;li&gt;Retrieve the secret in the workflow, decode it from Base64 to get the Private Key.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Assuming AWS usage, the steps can be adapted similarly for Google Cloud using Secret Manager, etc. (not verified).&lt;/p&gt;

&lt;p&gt;This can be implemented in a workflow as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-credentials&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:iam::012345678901:role/role-name&lt;/span&gt;
      &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ap-northeast-1&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Retrieve secret from AWS Secrets Manager&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-secrets&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;secrets=$(aws secretsmanager get-secret-value --secret-id secret-name --query SecretString --output text)&lt;/span&gt;
      &lt;span class="s"&gt;gh_app_private_key="$(echo "$secrets" | jq .GH_APP_PRIVATE_KEY_BASE64 -r | base64 -d)"&lt;/span&gt;
      &lt;span class="s"&gt;echo "::add-mask::$(echo "$gh_app_private_key" | sed ':a;N;$!ba;s/%/%25/g' | sed ':a;N;$!ba;s/\r/%0D/g' | sed ':a;N;$!ba;s/\n/%0A/g')"&lt;/span&gt;
      &lt;span class="s"&gt;echo "gh-app-private-key&amp;lt;&amp;lt;__EOF__"$'&lt;/span&gt;
&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;"$gh_app_private_key"$'&lt;/span&gt;
&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__EOF__&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"$GITHUB_OUTPUT"&lt;/span&gt;

  &lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;uses:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;actions/create-github-app-token@v1&lt;/span&gt;
    &lt;span class="s"&gt;id:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;app-token&lt;/span&gt;
    &lt;span class="s"&gt;with:&lt;/span&gt;
      &lt;span class="s"&gt;app-id:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;vars.GH_APP_ID&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&lt;/span&gt;
      &lt;span class="s"&gt;private-key:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;steps.aws-secrets.outputs.gh-app-private-key&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Further details are explained in the Q&amp;amp;A format below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not Store It in Organization or Repository Secrets?
&lt;/h3&gt;

&lt;p&gt;Because it is considered unsafe.&lt;/p&gt;

&lt;p&gt;With some specific conditions, the secret's value can be exposed if someone can create a Pull Request and place a GitHub Actions workflow file in the repository.&lt;/p&gt;

&lt;p&gt;For more details, refer to this presentation from a recent event. (company blog)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://tech.layerx.co.jp/entry/scalable-and-secure-infrastructure-as-code-pipeline-for-a-compound-startup"&gt;AWS知見共有会でTerraformのCI/CDパイプラインのセキュリティ等について発表してきました + GitHub新機能Push rulesについて&lt;/a&gt; (in Japanese)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Do You Need to Mask the Private Key?
&lt;/h3&gt;

&lt;p&gt;Without masking, the Private Key passed as input to &lt;code&gt;actions/create-github-app-token@v1&lt;/code&gt; can be viewed in the GitHub Actions UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Encode the Private Key in Base64 for AWS Secrets Manager?
&lt;/h3&gt;

&lt;p&gt;It’s not mandatory but simplifies storing it without line breaks.&lt;/p&gt;

&lt;p&gt;While AWS Secrets Manager can store secrets with line breaks, the key/value mode in the Management Console does not handle line breaks well. Plaintext mode allows entering line breaks, but it’s cumbersome. Therefore, encoding it in Base64 for storage without line breaks is more convenient.&lt;/p&gt;

</description>
      <category>githubactions</category>
    </item>
    <item>
      <title>Representation of a Data Structure Like an Array of Hash Tables in Bash Using jq</title>
      <dc:creator>Yuya Takeyama</dc:creator>
      <pubDate>Mon, 17 Sep 2018 20:41:55 +0000</pubDate>
      <link>https://dev.to/yuyatakeyama/representation-of-a-data-structure-like-an-array-of-hash-tables-in-bash-using-jq-4c3d</link>
      <guid>https://dev.to/yuyatakeyama/representation-of-a-data-structure-like-an-array-of-hash-tables-in-bash-using-jq-4c3d</guid>
      <description>&lt;p&gt;Have you ever needed a Hash Table in Bash?&lt;br&gt;
I needed it when I was writing a deploy script.&lt;/p&gt;

&lt;p&gt;I found that Bash 4 has a data structure called Dictionary.&lt;br&gt;
&lt;a href="https://stackoverflow.com/questions/1494178/how-to-define-hash-tables-in-bash"&gt;https://stackoverflow.com/questions/1494178/how-to-define-hash-tables-in-bash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the version of Bash in my laptop is 3.2.57 and I couldn't use it.&lt;br&gt;
Of course, it's easy to update the Bash.&lt;br&gt;
But we may need to work on environments with an older version of it.&lt;br&gt;
So I thought up about the other way.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash -eu&lt;/span&gt;

&lt;span class="c"&gt;# A data structure like an array of hash tables&lt;/span&gt;
&lt;span class="c"&gt;# Strictly, it's a stream of JSONs&lt;/span&gt;
&lt;span class="nv"&gt;PARAMETERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;_EOT_&lt;/span&gt;&lt;span class="sh"&gt;
{
  "foo":"FOO 1",
  "bar":"BAR 1"
}
{"foo":"FOO 2","bar":"BAR 2"}
{
  "foo": "フー",
  "bar": "&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="sh"&gt;30d0&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="sh"&gt;30fc"
}
&lt;/span&gt;&lt;span class="no"&gt;_EOT_
&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Transform into a line-delimited JSONs&lt;/span&gt;
&lt;span class="nv"&gt;PARAMETERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PARAMETERS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--monochrome-output&lt;/span&gt; &lt;span class="nt"&gt;--compact-output&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Enumerate JSONs and output foo and bar of each item&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; param &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;param&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;param&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq .foo &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;param&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq .bar &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"foo=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;foo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"bar=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"---"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt; &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PARAMETERS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bash json_stream.sh
foo=FOO 1
bar=BAR 1
---
foo=FOO 2
bar=BAR 2
---
foo=フー
bar=バー
---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Build a JSON as a string using heredoc&lt;/li&gt;
&lt;li&gt;Transform the JSON into a line-delimited JSONs&lt;/li&gt;
&lt;li&gt;Read a JSON from each line

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/a/10929511"&gt;https://stackoverflow.com/a/10929511&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Retrieve each property like &lt;code&gt;jq .foo&lt;/code&gt; in the loop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hope you enjoyed it!&lt;/p&gt;

</description>
      <category>bash</category>
      <category>jq</category>
    </item>
    <item>
      <title>How I measure Response Times of Web APIs using curl</title>
      <dc:creator>Yuya Takeyama</dc:creator>
      <pubDate>Wed, 06 Dec 2017 09:39:27 +0000</pubDate>
      <link>https://dev.to/yuyatakeyama/how-i-measure-response-times-of-web-apis-using-curl-6nh</link>
      <guid>https://dev.to/yuyatakeyama/how-i-measure-response-times-of-web-apis-using-curl-6nh</guid>
      <description>&lt;h2&gt;
  
  
  Why &lt;code&gt;curl&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;There is a bunch of specific tools for benchmarking HTTP requests.&lt;br&gt;
&lt;code&gt;ab&lt;/code&gt;, &lt;code&gt;JMeter&lt;/code&gt;, &lt;code&gt;wrk&lt;/code&gt;...&lt;br&gt;
Then why still use &lt;code&gt;curl&lt;/code&gt; for the purpose?&lt;/p&gt;

&lt;p&gt;It's because &lt;code&gt;curl&lt;/code&gt; is widely-used and it's a kind of common language for Web Developers.&lt;/p&gt;

&lt;p&gt;Also, some tools have a feature to retrieve an HTTP request as a &lt;code&gt;curl&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8kv548dxno1fiw43ozwc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8kv548dxno1fiw43ozwc.png" alt="copy as curl command" width="676" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's quite useful because it copies not only the URL and parameters but also request headers including &lt;code&gt;Authorization&lt;/code&gt; or &lt;code&gt;Cookie&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;In this article, I use these tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://curl.haxx.se/" rel="noopener noreferrer"&gt;curl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/yuya-takeyama/d396e2acef8a0472353ff15d545f9fd0" rel="noopener noreferrer"&gt;curlb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/yuya-takeyama/ntimes" rel="noopener noreferrer"&gt;ntimes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/yuya-takeyama/percentile" rel="noopener noreferrer"&gt;percentile&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Measure response time using &lt;code&gt;curl&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;At first, let's prepare a &lt;code&gt;curl&lt;/code&gt; command. In this time, I got the command of the request to my personal blog using Google Chrome. (&lt;code&gt;Cookie&lt;/code&gt; is removed)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl 'https://blog.yuyat.jp/' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en-US,en;q=0.8,ja;q=0.6' -H 'Upgrade-Insecure-Requests: 1' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.86 Safari/537.36' -H 'Connection: keep-alive' --compressed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It just outputs the response body from the server.&lt;/p&gt;

&lt;p&gt;Let's append these options.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-s -o /dev/null -w  "%{time_starttransfer}\n"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-s&lt;/code&gt; is to silence the progress, &lt;code&gt;-o&lt;/code&gt; is to dispose the response body to &lt;code&gt;/dev/null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And what is important is &lt;code&gt;-w&lt;/code&gt;.&lt;br&gt;
We can specify a variety of format and in this time I used &lt;code&gt;time_starttransfer&lt;/code&gt; to retrieve the response time (time to first byte).&lt;/p&gt;

&lt;p&gt;It shows like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl 'https://blog.yuyat.jp/' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en-US,en;q=0.8,ja;q=0.6' -H 'Upgrade-Insecure-Requests: 1' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.86 Safari/537.36' -H 'Connection: keep-alive' --compressed -s -o /dev/null -w  "%{time_starttransfer}\n"
0.188947
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response time is 0.188947 second (188 msec).&lt;/p&gt;

&lt;p&gt;To simplify, I also created a wrapper command &lt;code&gt;curlb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/sh
curl -s -o /dev/null -w '%{time_starttransfer}\n' "$@"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Measure the percentile of the response times
&lt;/h2&gt;

&lt;p&gt;It's not proper to benchmark from just a single request.&lt;/p&gt;

&lt;p&gt;Then let's measure the percentile of 100 requests.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ntimes&lt;/code&gt; is useful for such purposes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/yuya-takeyama/ntimes" rel="noopener noreferrer"&gt;https://github.com/yuya-takeyama/ntimes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can install with &lt;code&gt;go get github.com/yuya-takeyama/ntimes&lt;/code&gt; or the repository has pre-built binaries.&lt;/p&gt;

&lt;p&gt;Let's append &lt;code&gt;ntimes 100 --&lt;/code&gt; at the beginning of the &lt;code&gt;curl&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ntimes 100 -- curlb 'https://blog.yuyat.jp/' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en-US,en;q=0.8,ja;q=0.6' -H 'Upgrade-Insecure-Requests: 1' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.86 Safari/537.36' -H 'Connection: keep-alive' --compressed
0.331915
0.064085
0.059883
0.074047
0.059774
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to measure the percentile of the numbers, the command called &lt;code&gt;percentile&lt;/code&gt; may be the easiest option.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/yuya-takeyama/percentile" rel="noopener noreferrer"&gt;https://github.com/yuya-takeyama/percentile&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install it by &lt;code&gt;go get github.com/yuya-takeyama/percentile&lt;/code&gt; or download the pre-built binary from the repo.&lt;/p&gt;

&lt;p&gt;And append &lt;code&gt;| percentile&lt;/code&gt; to the end of the command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ntimes 100 -- curlb 'https://blog.yuyat.jp/' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en-US,en;q=0.8,ja;q=0.6' -H 'Upgrade-Insecure-Requests: 1' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.86 Safari/537.36' -H 'Connection: keep-alive' --compressed | percentile
50%:    0.061777
66%:    0.06412
75%:    0.06872300000000001
80%:    0.07029000000000001
90%:    0.07496700000000001
95%:    0.076153
98%:    0.077226
99%:    0.07957
100%:   0.109931
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it!&lt;/p&gt;

</description>
      <category>curl</category>
    </item>
  </channel>
</rss>
