0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

目的

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!

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?