shell の改行, ダブルクォーテーション, -n, jq, if などを理解する。

# 目的
GitHub ワークフローや、runn の command の部分でシェル芸をやっていると、余計な改行がついたり、意図せずダブルクォーテーションが消えていたりすることがあるので、整理する。

# 検証
## (A) jq, null, -n, ダブルクォーテーション周り

``` aaa_1.sh
#!/bin/bash

set -euo pipefail

echo " ==================================================================================================== [A]"
response=$(curl -s "https://pokeapi.co/api/v2/evolution-chain/7/")
echo "Response: $response"
# シェルには型として文字列しかなく、response は文字列である。
#
# response:
# {"baby_trigger_item":null,"chain":{"evolution_details":[],"evolves_to":[{"evolution_details":[{"gender":null,"held_item":null,"item":null,"known_move":null,"known_move_type":null,"location":null,"min_affection":null,"min_beauty":null,"min_happiness":null,"min_level":20,"needs_overworld_rain":false,"party_species":null,"party_type":null,"relative_physical_stats":null,"time_of_day":"","trade_species":null,"trigger":{"name":"level-up","url":"https://pokeapi.co/api/v2/evolution-trigger/1/"},"turn_upside_down":false},{"gender":null,"held_item":null,"item":null,"known_move":null,"known_move_type":null,"location":null,"min_affection":null,"min_beauty":null,"min_happiness":null,"min_level":20,"needs_overworld_rain":false,"party_species":null,"party_type":null,"relative_physical_stats":null,"time_of_day":"night","trade_species":null,"trigger":{"name":"level-up","url":"https://pokeapi.co/api/v2/evolution-trigger/1/"},"turn_upside_down":false}],"evolves_to":[],"is_baby":false,"species":{"name":"raticate","url":"https://pokeapi.co/api/v2/pokemon-species/20/"}}],"is_baby":false,"species":{"name":"rattata","url":"https://pokeapi.co/api/v2/pokemon-species/19/"}},"id":7}

echo " ================================================== (1-1) JSON形式の時に数値にはダブルクォーテーションがないので、-r オプションはつけてもつけなくても同じ"
evolving_1_min_level=$(echo $response | jq '.chain.evolves_to[0].evolution_details[0].min_level')
echo "evolving_1_min_level: $evolving_1_min_level"

echo " ================================================== (1-2) 同上"
evolving_1_min_level_with_r=$(echo $response | jq '.chain.evolves_to[0].evolution_details[0].min_level')
echo "evolving_1_min_level_with_r: $evolving_1_min_level_with_r"

echo " ================================================== (2-1) -r オプションがないとダブルクォーテーションを含む文字列となる"
evolving_1_time_of_day=$(echo $response | jq '.chain.evolves_to[0].evolution_details[1].time_of_day')
echo "evolving_1_time_of_day: $evolving_1_time_of_day"

echo " ================================================== (2-2) -r オプションをつけるとダブルクォーテーションを含まない文字列となる"
evolving_1_time_of_day_with_r=$(echo $response | jq -r '.chain.evolves_to[0].evolution_details[1].time_of_day')
echo "evolving_1_time_of_day_with_r: $evolving_1_time_of_day_with_r"

echo " ================================================== (3-1) JSON形式の時に null にはダブルクォーテーションがないので、-r オプションはつけてもつけなくても同じ"
baby_trigger_item=$(echo $response | jq '.baby_trigger_item')
echo "baby_trigger_item: $baby_trigger_item"

echo " ================================================== (3-2) 同上"
baby_trigger_item_with_r=$(echo $response | jq -r '.baby_trigger_item')
echo "baby_trigger_item_with_r: $baby_trigger_item_with_r"

echo " ================================================== (4-1)"
if [ -n $baby_trigger_item ]; then
  echo "APIから取得したレスポンスは文字列であり、jq によって null という文字列が抽出されているので、-n で true となる。"
else
  echo "こっちには分岐しない。"
fi

echo " ================================================== (4-2)"
if [ -n "" ]; then
  echo "こっちには分岐しない。"
else
  echo "空文字は -n で false となる。"
fi

echo " ================================================== (4-3)"
if [ -n ]; then
  echo "-n による判定は機能せずに、-n という文字列が存在するということになるので true になる。"
else
  echo "こっちには分岐しない"
fi

echo " ================================================== (4-4)"
set +u # 未定義変数への参照をエラーにしない
if [ -n "$not_existing_var_1" ]; then
  echo "こっちには分岐しない"
else
  echo "実質的に空文字なので、-n で false になる。"
fi
set -u

echo " ================================================== (4-5)"
someText=""
if [ -n $someText ]; then
  echo "実質的に、(4-3) と同じなるので、true になる。"
else
  echo "こっちには分岐しない"
fi

echo " ================================================== (4-6)"
set +u # 未定義変数への参照をエラーにしない
if [ -n $not_existing_var_2 ]; then
  echo "実質的に、(4-3) と同じなるので、true になる。"
else
  echo "こっちには分岐しない"
fi
set -u

echo " ================================================== (4-7)"
if [ -n "$not_existing_var_3" ]; then
  echo "そもそも、set -u によって、未定義変数への参照はエラーになるので、ここにはこない。"
else
  echo "そもそも、set -u によって、未定義変数への参照はエラーになるので、ここにはこない。"
fi
# 「not_existing_var_3: unbound variable」というエラーが出た。

```
下記はエラー終了している。
```
~/prashell$ bash aaa_1.sh
 ==================================================================================================== [A]
Response: {"baby_trigger_item":null,"chain":{"evolution_details":[],"evolves_to":[{"evolution_details":[{"gender":null,"held_item":null,"item":null,"known_move":null,"known_move_type":null,"location":null,"min_affection":null,"min_beauty":null,"min_happiness":null,"min_level":20,"needs_overworld_rain":false,"party_species":null,"party_type":null,"relative_physical_stats":null,"time_of_day":"","trade_species":null,"trigger":{"name":"level-up","url":"https://pokeapi.co/api/v2/evolution-trigger/1/"},"turn_upside_down":false},{"gender":null,"held_item":null,"item":null,"known_move":null,"known_move_type":null,"location":null,"min_affection":null,"min_beauty":null,"min_happiness":null,"min_level":20,"needs_overworld_rain":false,"party_species":null,"party_type":null,"relative_physical_stats":null,"time_of_day":"night","trade_species":null,"trigger":{"name":"level-up","url":"https://pokeapi.co/api/v2/evolution-trigger/1/"},"turn_upside_down":false}],"evolves_to":[],"is_baby":false,"species":{"name":"raticate","url":"https://pokeapi.co/api/v2/pokemon-species/20/"}}],"is_baby":false,"species":{"name":"rattata","url":"https://pokeapi.co/api/v2/pokemon-species/19/"}},"id":7}
 ================================================== (1-1) JSON形式の時に数値にはダブルクォーテーションがないので、-r オプションはつけてもつけなくても同じ
evolving_1_min_level: 20
 ================================================== (1-2) 同上
evolving_1_min_level_with_r: 20
 ================================================== (2-1) -r オプションがないとダブルクォーテーションを含む文字列となる
evolving_1_time_of_day: "night"
 ================================================== (2-2) -r オプションをつけるとダブルクォーテーションを含まない文字列となる
evolving_1_time_of_day_with_r: night
 ================================================== (3-1) JSON形式の時に null にはダブルクォーテーションがないので、-r オプションはつけてもつけなくても同じ
baby_trigger_item: null
 ================================================== (3-2) 同上
baby_trigger_item_with_r: null
 ================================================== (4-1)
APIから取得したレスポンスは文字列であり、jq によって null という文字列が抽出されているので、-n で true となる。
 ================================================== (4-2)
空文字は -n で false となる。
 ================================================== (4-3)
-n による判定は機能せずに、-n という文字列が存在するということになるので true になる。
 ================================================== (4-4)
実質的に空文字なので、-n で false になる。
 ================================================== (4-5)
実質的に、(4-3) と同じなるので、true になる。
 ================================================== (4-6)
実質的に、(4-3) と同じなるので、true になる。
 ================================================== (4-7)
aaa_1.sh: line 85: not_existing_var_3: unbound variable
~/prashell$
```

`man bash` で `-n` の意味を知ることができる。
```
$ man bash
...
       -z string
              True if the length of string is zero.
       string
       -n string
              True if the length of string is non-zero.
...
```
下記は上記のシェルスクリプトで curl で取得したデータを分かりやすく整形したものである。参考までに。
```
{
	"baby_trigger_item": null,
	"chain": {
		"evolution_details": [],
		"evolves_to": [
			{
				"evolution_details": [
					{
						"gender": null,
						"held_item": null,
						"item": null,
						"known_move": null,
						"known_move_type": null,
						"location": null,
						"min_affection": null,
						"min_beauty": null,
						"min_happiness": null,
						"min_level": 20,
						"needs_overworld_rain": false,
						"party_species": null,
						"party_type": null,
						"relative_physical_stats": null,
						"time_of_day": "",
						"trade_species": null,
						"trigger": {
							"name": "level-up",
							"url": "https://pokeapi.co/api/v2/evolution-trigger/1/"
						},
						"turn_upside_down": false
					},
					{
						"gender": null,
						"held_item": null,
						"item": null,
						"known_move": null,
						"known_move_type": null,
						"location": null,
						"min_affection": null,
						"min_beauty": null,
						"min_happiness": null,
						"min_level": 20,
						"needs_overworld_rain": false,
						"party_species": null,
						"party_type": null,
						"relative_physical_stats": null,
						"time_of_day": "night",
						"trade_species": null,
						"trigger": {
							"name": "level-up",
							"url": "https://pokeapi.co/api/v2/evolution-trigger/1/"
						},
						"turn_upside_down": false
					}
				],
				"evolves_to": [],
				"is_baby": false,
				"species": {
					"name": "raticate",
					"url": "https://pokeapi.co/api/v2/pokemon-species/20/"
				}
			}
		],
		"is_baby": false,
		"species": {
			"name": "rattata",
			"url": "https://pokeapi.co/api/v2/pokemon-species/19/"
		}
	},
	"id": 7
}
```

## (B) ダブルクォーテーション, if 周り

``` aaa_2.sh
#!/bin/bash

set -euo pipefail

echo " ==================================================================================================== [A]"
echo " ================================================== (1-1)"
text_with_white_space_1="Hello, world!"
if [ -n "$text_with_white_space_1" ]; then
  echo "空文字ではない文字列なので、-n で true になる。"
else
  echo "こっちには分岐しない"
fi

echo " ================================================== (1-2)"
text_with_white_space_2="Hello, world!"
if [ -n $text_with_white_space_2 ]; then
  echo "こっちには分岐しない"
else
  echo "bash の if, until, while などの制御構文は、exit 0 以外の終了ステータスは false として扱うので、エラーによって即時終了はしない。"
fi
# 「if [ -n Hello, world! ]; then」のように書かれていることになり、args が多すぎてエラーになる。
# [: Hello,: binary operator expected というエラーが出る。

echo " ================================================== (2-1)"
if grep "world" <<< "Hello, world!!"; then
  echo "grep の検索に引っかかるので、こっちに分岐する。"
else
  echo "こっちには分岐しない"
fi

echo " ================================================== (2-2)"
if grep "Good Morning!!" <<< "Hello, world!!"; then
  echo "こっちには分岐しない"
else
  echo "bash の if, until, while などの制御構文は、exit 0 以外の終了ステータスは false として扱うので、エラーによって即時終了はしない。"
fi

echo " ================================================== (2-3)"
grep "Hello" <<< "Hello, world!!"
echo "ここまで到達するよ！！終了ステータス: $?"

echo " ================================================== (2-4)"
grep "Good Morning!!" <<< "Hello, world!!"
echo "ここまで到達しないよ！！"

```
下記は、最後の grep でエラー終了しています。
```
~/prashell$ bash aaa_2.sh
 ==================================================================================================== [A]
 ================================================== (1-1)
空文字ではない文字列なので、-n で true になる。
 ================================================== (1-2)
aaa_2.sh: line 16: [: Hello,: binary operator expected
bash の if, until, while などの制御構文は、exit 0 以外の終了ステータスは false として扱うので、エラーによって即時終了はしない。
 ================================================== (2-1)
Hello, world!!
grep の検索に引っかかるので、こっちに分岐する。
 ================================================== (2-2)
bash の if, until, while などの制御構文は、exit 0 以外の終了ステータスは false として扱うので、エラーによって即時終了はしない。
 ================================================== (2-3)
Hello, world!!
ここまで到達するよ！！終了ステータス: 0
 ================================================== (2-4)
 ~/prashell$
```

## (C) 改行周り

``` bbb_1.sh
#!/bin/bash

set -euo pipefail

echo " ==================================================================================================== [A]"
echo " ================================================== (1-1) -n オプションがない場合、最後に余計な改行をつけて標準出力する。"
echo "Hello!"

echo " ================================================== (1-2)"
echo -n "Good!"

echo " ================================================== (2-1) すぐ後のコードと実質的に同じ。標準出力と違って、コマンド置換は最後の改行を除去する。"
some_text_1=$(echo "Hello, world!")
echo -n "some_text_1: $some_text_1"

echo " ================================================== (2-2)"
some_text_2=$(echo -n "Good, world!")
echo -n "some_text_2: $some_text_2"

echo " ================================================== (3-1) cat は最後に改行をつけない。また、プロセス置換は、一時ファイルに出力して渡すようなもので、標準出力がそのまま渡されるので、-n オプションがない時に最後に余計な改行がつく。 ゆえに、すぐ後のコードとは、改行の有無の観点で違う結果になる。"
cat < <(echo "Hello, everyone!")

echo " ================================================== (3-2)"
cat < <(echo -n "Good, everyone!")

echo " ================================================== (4-1) ヒアストリングは文字列を標準入力として渡すが、その際、最後に改行をつける。"
cat <<< "Hello, book!"

echo " ================================================== (5-1) すぐ後のコードと実質的に同じ。ファイルの中身をそのまま標準入力として渡すので、最後に余計な改行をつけることはない。"
cat < myfile_with_last_newline.txt

echo " ================================================== (5-2)"
cat myfile_with_last_newline.txt

echo " ================================================== (6-1) すぐ後のコードと実質的に同じ。ファイルの中身をそのまま標準入力として渡すので、最後に余計な改行をつけることはない。"
cat < myfile_without_last_newline.txt

echo " ================================================== (6-2)"
cat myfile_without_last_newline.txt

echo " ================================================== (7-1) パイプは標準出力をそのまま渡すので、-n オプションがない時で、最後に余計な改行が入る。"
echo "Hello, paper!" | cat

echo " ================================================== (7-2)"
echo -n "Good, paper!" | cat

echo " ================================================== (8-1) パイプは標準出力をそのまま渡すので、-n オプションがない時で、最後に余計な改行が入る。今回は、改行が V に置き換わる。"
echo "Hello, pen!" | tr '\n' 'V'

echo " ================================================== (8-2)"
echo -n "Good, pen!" | tr '\n' 'V'

echo " ================================================== (9-1) 標準出力のリダイレクトであり(上書きモード)、-n オプションがない時で、最後に余計な改行が入る。"
echo "Hello, glasses!" > myoutput_without_n.txt
# ちなみに、「>」 は 「1>」 の省略形です。

echo " ================================================== (9-2)"
echo -n "Good, glasses!" > myoutput_with_n.txt

echo " ================================================== (10-1) 標準出力のリダイレクトであり(追記モード)、-n オプションがない時で、最後に余計な改行が入る。"
echo "Hello, hat!" >> myoverwrite_without_n.txt

echo " ================================================== (10-2)"
echo -n "Good, hat!" >> myoverwrite_with_n.txt

```
``` myfile_with_last_newline.txt
このファイルには、改行があるよ。

```
``` myfile_without_last_newline.txt
このファイルには、改行がないよ。
```

下記は正常終了している。
```
~/prashell$ bash bbb_1.sh
 ==================================================================================================== [A]
 ================================================== (1-1) -n オプションがない場合、最後に余計な改行をつけて標準出力する。
Hello!
 ================================================== (1-2)
Good! ================================================== (2-1) すぐ後のコードと実質的に同じ。標準出力と違って、コマンド置換は最後の改行を除去する。
some_text_1: Hello, world! ================================================== (2-2)
some_text_2: Good, world! ================================================== (3-1) cat は最後に改行をつけない。また、プロセス置換は、一時ファイルに出力して渡すようなもので、標準出力がそのまま渡されるので、-n オプションがない時に最後に余計な改行がつく。 ゆえに、すぐ後のコードとは、改行の有無の観点で違う結果になる。
Hello, everyone!
 ================================================== (3-2)
Good, everyone! ================================================== (4-1) ヒアストリングは文字列を標準入力として渡すが、その際、最後に改行をつける。
Hello, book!
 ================================================== (5-1) すぐ後のコードと実質的に同じ。ファイルの中身をそのまま標準入力として渡すので、最後に余計な改行をつけることはない。
このファイルには、改行があるよ。
 ================================================== (5-2)
このファイルには、改行があるよ。
 ================================================== (6-1) すぐ後のコードと実質的に同じ。ファイルの中身をそのまま標準入力として渡すので、最後に余計な改行をつけることはない。
このファイルには、改行がないよ。 ================================================== (6-2)
このファイルには、改行がないよ。 ================================================== (7-1) パイプは標準出力をそのまま渡すので、-n オプションがない時で、最後に余計な改行が入る。
Hello, paper!
 ================================================== (7-2)
Good, paper! ================================================== (8-1) パイプは標準出力をそのまま渡すので、-n オプションがない時で、最後に余計な改行が入る。今回は、改行が V に置き換わる。
Hello, pen!V ================================================== (8-2)
Good, pen! ================================================== (9-1) 標準出力のリダイレクトであり(上書きモード)、-n オプションがない時で、最後に余計な改行が入る。
 ================================================== (9-2)
 ================================================== (10-1) 標準出力のリダイレクトであり(追記モード)、-n オプションがない時で、最後に余計な改行が入る。
 ================================================== (10-2)
~/prashell$ 
```
``` myoutput_without_n.txt
Hello, glasses!

```
``` myoutput_with_n.txt
Good, glasses!
```
``` myoverwrite_without_n.txt (四回実行した)
Hello, hat!
Hello, hat!
Hello, hat!
Hello, hat!

```
``` myoverwrite_with_n.txt (四回実行した)
Good, hat!Good, hat!Good, hat!Good, hat!
```




# 参考

https://jsonplaceholder.typicode.com/

https://dummyjson.com/

https://qiita.com/TakahiRoyte/items/949f4e88caecb02119aa

https://reqres.in/

https://pokeapi.co/