質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
bash

bash(Bourne-again-Shell)は sh(Bourne Shell)のインプリメンテーションに様々な機能が追加されたシェルです。LinuxやMac OS XではBashはデフォルトで導入されています。

Q&A

解決済

2回答

1334閲覧

bashで未入力時の共通メソッドを作りたい

earnest_gay

総合スコア615

bash

bash(Bourne-again-Shell)は sh(Bourne Shell)のインプリメンテーションに様々な機能が追加されたシェルです。LinuxやMac OS XではBashはデフォルトで導入されています。

0グッド

0クリップ

投稿2018/01/27 22:57

編集2018/01/29 13:30

多分初めてbashに触ってみたのですが戻り値がないのが違和感あってなかなかロジック組み立てられずにいます。
やりたいことは未入力があれば入力があるまでreadさせ続けるというものなのですが、、、

function check_blank () { if [ "$1" = "" ]; then echo "It is not yet input." # 入力させる ## 問題は入力させる変数名。メソッドを個別に分けるのはいやだ。 read Clone_url # 入力されていなかったらcheck_blankを呼び出す。 if [ "$Clone_url" = "" ]; then check_blank fi fi } ## Input setting echo -e "\e[5;36m* \e[0m\e[1;36mPlease enter the URL of the remote repository of the clone to be created.\e[0;39m" read Clone_url check_blank $Clone_url return echo -e "\e[5;36m* \e[0m\e[1;36mPlease enter an alias for managing the clone to be created.\e[0;39m" read Project_name check_blank $Project_name echo -e "\e[5;36m* \e[0m\e[1;36mPlease enter the domain name to reflect the setting.\e[0;39m" read Domain_name check_blank $Domain_name

check_blank1
check_blank2
check_blank3
と作れば解説しますが、それは個人的に嫌で、引数を利用して共通メソッドを作れればと思っているのですがロジックが思い浮かばず、、、

## 問題は入力させる変数名。メソッドを個別に分けるのはいやだ。 read Clone_url

の部分なのですが共通メソッドを作りたいので変数名が可変しなくてはならないはずです。

function check_blank () { if [ "$2" = "" ]; then echo "It is not yet input." # 入力させる read $1 # 入力されていなかったらcheck_blankを呼び出す。 if [ "${$1}" = "" ]; then check_blank "$1" fi fi } check_blank "Clone_url" $Clone_url

みたいなことをやってもうまくいかず、、、
知恵をお貸しいただきたく、、、

*execでの変数に代入されていないと思われる件の追記。
イメージ説明

本題から離れていってる気がするので、、、教えていただいた記述を例にメソッドを切り分けたいのですが、入力時も未入力時も動作がおかしいのです、、、

function read_input () { read input rtn_check_blank=$(check_blank) $input # 関数の返却値を標準出力に出力 echo $rtn_check_blank } function check_blank () { input=$1 while [ "$input" = "" ]; do echo "It is not yet input." read input done echo $input } ## Input setting echo -e "\e[5;36m* \e[0m\e[1;36mPlease enter the URL of the remote repository of the clone to be created.\e[0;39m" Clone_url=$(read_input) echo -e "\e[5;36m* \e[0m\e[1;36mPlease enter an alias for managing the clone to be created.\e[0;39m" Project_name=$(read_input) echo -e "\e[5;36m* \e[0m\e[1;36mPlease enter the domain name to reflect the setting.\e[0;39m" Domain_name=$(read_input) echo $Clone_url,$Project_name,$Domain_name

イメージ説明

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

通常、シェルスクリプトの中で関数から返却値を得たい場合は、return ではなく、標準出力に文字列で出力し呼び側でこれを変数に代入します。単純な例ですと

shell

1function test() 2{ 3 echo "ans" 4} 5 6result=$(test) 7 8# 以下で "ans" と表示される 9echo $result

のような感じです。$(command)で__command__が標準出力に出力したものを文字列として受け取ります。
それで質問者様の例では関数内で標準出力をコンソールへの出力として使っているので、返却値の受取とファイルディスクリプタが競合してしまい、簡単にはこの方法が使えません。そこで、シェルの開始時に標準出力(=コンソール)のファイルディスクリプタを別のファイルディスクリプタに複製しておいて、関数の出力と使い分ける必要があります。
具体的には以下のような感じです。

shell

1# 標準出力のファイルディスクリプタを複製してその番号をconsole という変数に保持 2exec {console}>&1 3function read_ans () 4{ 5 # 入力させる 6 read ans 7 while [ "$ans" = "" ]; do 8 # 入力されていなかったらもう一度入力してもらう 9 # 次の入力を促すメッセージは標準出力ではなくコンソールに出力 10 echo "It is not yet input." >& $console 11 read ans 12 done 13 # 関数の返却値を標準出力に出力 14 echo $ans 15} 16 17## Input setting 18echo -e "\e[5;36m* \e[0m\e[1;36mPlease enter the URL of the remote repository of the clone to be created.\e[0;39m" 19Clone_url=$(read_ans) 20 21echo -e "\e[5;36m* \e[0m\e[1;36mPlease enter an alias for managing the clone to be created.\e[0;39m" 22Project_name=$(read_ans) 23 24echo -e "\e[5;36m* \e[0m\e[1;36mPlease enter the domain name to reflect the setting.\e[0;39m" 25Domain_name=$(read_ans) 26 27echo $Clone_url,$Project_name,$Domain_name

1行目の exec {console}>&1 はシェルスクリプトでも凝った書き方(あまり見ませんし、周りで多少シェルスクリプトに詳しい人でも知らないことが多い)です。ちょっと長くなりますが、解説してみます。
通常、exec は別のコマンドをプロセスをfork せずに実行する場合に使用します。例えば、exec lsと書けば ls コマンドが実行されます。しかし、コマンドを指定しなかった場合、シェルは何もしません。例えば、

shell

1exec

と一行だけ書いて bash で実行しても何もしません。それで、この何もしないことを利用して、自分自身のプロセスのファイルディスクリプタをオープンしたり、クローズしたりするときなどに使います。例えば、flock というコマンドはファイルディスクリプタの番号を引数に取ってファイルをロックしますが、このファイルディスクリプタの取得にも使えます。以下は /var/hoge.lock というファイルをロックする例です。

shell

1# ファイルを追加書き込みモードでオープンしてそのファイルディスクリプタ番号を $lockfd に保持 2exec {lockfd}>>/var/hoge.lock 3# ロックを取得 4flock $lockfd 5# 独占した処理 6... 7# ロック開放 8flock -u $lockfd 9# ファイルをクローズ 10exec {lockfd}>&-

最後の exec もファイルをクローズするときにこのように書きます。回答のシェルスクリプトではクローズしてませんが、丁寧に書くのであれば、exec {console}>&-を書くと良いでしょう。
次に{console}についてですが、これはbash に空いているファイルディスクリプタ番号を選んでもらって変数に入れてもらうときの書き方です。実際にファイルディスクリプタ番号に何が入るかというと10以上で最初に開いている番号が割り当てられるので、回答に示したシェルスクリプトでは 10 が入ります。試しに

shell

1exec {console}>&1 2echo $console

というのを実行すると 10 が印字されると思います。短いシェルスクリプトであれば、変数を使わずにほぼ開いてることが確定している9というような番号を使うこともあります。回答例のシェルスクリプトでも

shell

1# 標準出力のファイルディスクリプタを9に複製 2exec 9>&1 3# 中略 4 echo "It is not yet input." >& 9 5# 後略

と書いても動作します。

投稿2018/01/28 00:32

編集2018/01/29 05:18
mit0223

総合スコア3401

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

earnest_gay

2018/01/29 02:30

回答ありがとうございます。 処理の流れで何をしているのか、ひとつだけわからないところがあります。 >ディスクリプタを複製してその番号をconsole という変数に保持 exec {console}>&1 exec で調べてはみたのですが、これがこの処理においてなぜ必要なのか? ないとどうなるのかというところが分かりません。 試しにexec {console}>&1を抜いて実行してみたりしましたが、差異が分かりませんでした。
mit0223

2018/01/29 05:20

exec の解説を追加しました。 exec がなければ空入力のときに echo "It is not yet input." >& $console を実行しようとして、エラーになります。試してみましたが、「test.sh: 行 10: $console: 曖昧なリダイレクトです」というエラーになりました。動きが変わらなかったのは空入力をテストしていないからではないでしょうか。
earnest_gay

2018/01/29 13:08

回答ありがとうございます。 やはり、こちらとそちらでの動作に差異があるように思えます。 実行結果の画像を追加しましたのでお手隙の際に見ていただけるとありがたいです。
mit0223

2018/01/30 00:44

いろいろとミスっておられます。まずは私の回答のスクリプトを動作させてみて、空入力をテストしてみてください。それから exec をコメントアウトしてどうなるかやってみてください。ミスの内容としては rtn_check_blank=$(check_blank) $input の行は $input が check_blank の引数になっていません。カッコの中に引数を書かないと。それから、 echo で $console の内容を確認されてますが、$console が #console になってます。
earnest_gay

2018/01/30 02:16

ありがとうございます! 解決できました。
guest

0

mit0223さんのお書きのように、

通常、シェルスクリプトの中で関数から返却値を得たい場合は、return ではなく、標準出力に文字列で出力し呼び側でこれを変数に代入します。

ですが、read命令の代替のように使いたい場合は、evalで代入することで、渡した変数名に代入することも出来ます。

Bash

1function my_read() 2{ 3 while read input 4 [ "$input" = "" -o "${input//[^A-Za-z0-9]/}" != "$input" ] 5 do 6 echo "It is not yet input, or invalid char." 7 done 8 eval $1="$input" 9} 10echo -e "~~~~" >&2 11my_read foo 12declare -p foo

ただし、evalは気をつけないと危険(1;dateのように入力したコマンドを実行できてしまう)なので、「誰に」「どのユーザー権限で」実行させるのかによっては十分なテストが必要です。
上記では、英数字以外があれば再入力にすることでこれを防いでいますが、;なども入力が必要なら面倒ですね。

一般的には、小さい整数を返すときはreturnで、そうで無いときは「標準出力」で返すのがいいと思います。

Bash

1foo(){ 2 echo 123 456 3} 4ans=($(foo)) 5declare -p ans

のように、複数の文字列を返して、配列で受けることも出来ます。

投稿2018/01/28 02:07

otn

総合スコア84555

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問