目的
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!
参考