Bashは文字列ベースのタイムアウトオプション仕様に組み込まれているエラーを読み込みますが、配列ベースのタイムアウトオプション仕様では読みません。なぜ?

Bashは文字列ベースのタイムアウトオプション仕様に組み込まれているエラーを読み込みますが、配列ベースのタイムアウトオプション仕様では読みません。なぜ?

ソースコードを読むときFFreadBashプログラミングの詳細については、配列に渡されたタイムアウトオプションを見ました。ここ:

read "${read_flags[@]}" -srn 1 && key "$REPLY"

値がread_flags設定されました。このように:

read_flags=(-t 0.05)

(だから最後のread呼び出しはread -t 0.05 -srn 1)です。

なぜ文字列が利用できないのか分かりません。例:

read_flags="-t 0.05"
read "$read_flags" -srn 1 && key "$REPLY"

この文字列ベースのアプローチは「間違ったタイムアウト仕様」をもたらします。

いくつかの調査の最後にテストスクリプトを思いつきましたparmtest

show() {
  for i in "$@"; do printf '[%s]' "$i"; done
  printf '\n'
}

opt_string="-t 1"
opt_array=(-t 1)

echo 'Using string-based option...'
show string "$opt_string" x y z
read "$opt_string"
echo
echo 'Using array-based option...'
show array "${opt_array[@]}" x y z
read "${opt_array[@]}"

bash parmtest$BASH_VERSIONis 5.1.4(1)-release)でこのコマンドを実行すると、次のようになります。

Using string-based option...
[string][-t 1][x][y][z]
parmtest: line 11: read:  1: invalid timeout specification

Using array-based option...
[array][-t][1][x][y][z]
(1 second delay...)

デバッグ出力を見ると、1配列ベースの方法の値が個別で空白がないことがわかります。エラーメッセージでもわかります1。前に余分なスペースがありますread: 1: invalid timeout specification。私の疑いはまさにその部分にある。

奇妙なことに、たとえば他のコマンドでこのアプローチを使用すると、date問題はありません。

show() {
  for i in "$@"; do printf '[%s]' "$i"; done
  printf '\n'
}

opt_string="-d 1"
opt_array=(-d 1)

echo 'Using string-based option...'
show string "$opt_string" x y z
date "$opt_string"
echo
echo 'Using array-based option...'
show array "${opt_array[@]}" x y z
date "${opt_array[@]}"

(唯一の違いopt_stringはnotをopt_array指定し、すべての場合にnotを呼び出すことです)。-d-tdateread

実行すると、bash parmtest次のものが生成されます。

Using string-based option...
[string][-d 1][x][y][z]
Wed Sep  1 01:00:00 UTC 2021

Using array-based option...
[array][-d][1][x][y][z]
Wed Sep  1 01:00:00 UTC 2021

エラーはありません。

私はこの質問に対する答えを探していましたが、無駄でした。さらに、著者はこの段落も書いた。ため息でアレイを直接使う、気になります。

よろしくお願いします。

9月3日更新:以下は私が今まで読んで学んだ内容を記録したブログ投稿です。fffまた、この質問とその中にある素晴らしい答えを引用しました。fff Part 1を見る - メイン

答え1

その理由は、read組み込み関数とコマンドがdateコマンドライン引数を異なる方法で解釈するためです。

しかし、最初にすべきことがあります。どちらの例も、配列"${read_flags[@]}"の場合でもスカラーの場合でも、"$read_flags"シェル変数の逆参照の周りに引用符を配置して推奨事項に従いました。シェル変数を常に引用することが推奨される主な理由は次のとおりです。不要噴射。以下を考慮してください

  • スペースを含むファイルがあり、My favorite songs.txtそれをディレクトリに移動しようとしていますplaylists/
  • ファイル名を変数に保存して$fname呼び出すと
    mv $fname playlists/
    
    このmvコマンドは4つパラメーター:、、Myおよびfavorite存在しない3つのファイルをディレクトリにsongs.txt移動しようとします。明らかにあなたが望むものではありません。playlists/Myfavoritesongs.txtplaylists/
  • 代わりに、$fname次のように参照を二重引用符で囲むと、
    mv "$fname" playlists/
    
    シェルがスペースを含む文字列全体を渡すことを保証します。一つmv(名前にスペースはありますが)移動する必要があるファイルであることを認識する単語を入力します。

今状況を保存したいと思います。オプションシェル変数のパラメータ。時には長く、時には短く、時には値が必要なので難しいです。パラメーターを使用してオプションを指定する方法はいくつかあります。一般的に解析方法は完全にプログラマに依存します。(望むよりこのQ&A)ディスカッションをしてください)。したがって、readBashの組み込み関数とコマンドが異なる反応をする理由は、date2つの関数がコマンドライン引数を解析する方法の内部動作によるものです。しかし私達は少し推測できます。

  • スカラーシェル変数に保存され、文字列-t 0.05として渡されると、"$opt_string"受信者はそれをスペースを含む文字列として扱います(上記を参照)。
  • 配列変数に-tおよびを保存し、それを受信者に渡すと、2つの別々の項目、つまりおよびとして扱われます。(1) (2)0.05"${opt_array[@]}"-t0.05
  • 多くのプログラムは、getopt()POSIXガイドで推奨されているように、GNU Cライブラリの関数を使用してコマンドライン引数を解析します。
  • たとえば、コマンドでは、getopt()「短い」オプション形式と「長い」オプション形式を区別します。モードオプションdate -udate --utcdate価値オプション(たとえば、-o/)は通常、--option短いオプションおよび/または長いオプションの場合はisと解釈されます。getopt-ovalue-o value--option=value--option value
  • -t 0.05次のように渡されたとき二つ使用されているツールの場合は、オプション名の後にgetopt()最初の文字を使用し、オプション値(構文)として次の単語を使用します。-したがって、オプション名とオプション値。-o valuereadt0.05
  • -t 0.05次のように渡されたとき一つ単語の後の最初の文字は(やはり)オプション名になり、残りの文字列はオプション値になるため、値は次のように解釈されます。-ovaluegetopt()-0.05 先行スペースがあります
  • このreadコマンドは、前にスペースがあるタイムアウト仕様を受け入れないようです。実際に電話すると
    read -t " 0.05" -srn 1
    
    ここで、値は明示的に先行スペースを含む文字列です。read 返品これについて文句を言いました。

結論としてdate、オプション値に関しては、コマンドは明らかにより快適な方法で書かれており、-d値文字列が空白で始まるかどうかは関係ありません。 (明らかに)数値でなければならないタイムアウト仕様の場合とは異なり、日付仕様が取ることができる値は非常に多様であるため、これは驚くべきことではありません。


(1) @(代わりに*)を使用していることに注意してください。大きな違いがあるここで配列参照を引用すると、すべての配列要素が個別に参照されているように見えるため、分割しなくてもスペース自体を含めることができます。

(2) 原則として、3番目のオプションがあります。つまり、-t 0.05スカラー変数に保存しますが、$opt_string次のように渡すことです。$opt_string いいえ引用符。この場合、スペースで単語分割を実行してからやり直してください。二つ項目-t0.05はそれぞれプログラムに渡されます。ただし、パラメータ値に保存する明示的なスペースがある場合があるため、これは推奨されるアプローチではありません。

答え2

read_flags="-t 0.05"
read "$read_flags" -srn 1

ここでは"$read_flags"二重引用符で囲まれているので、そうではありません。噴射。ご覧のとおり、結果は実行と同じです。

read "-t 0.05" -srn 1

これは、指定されたタイムアウトに先行スペースがあることを意味します。これで、数字を解析するときにBashが実行するすべての操作はこれが好きではありません。

余分なスペースの機能は完全にプログラムによって異なります。数字を解析するときは、先行スペースを無視するのが簡単でなければなりません。標準strtod()関数がこれを行います。の場合、date -dより複雑な文字列を解析する必要があるため、空白に厳密ではないことは驚くべきことではありません。 (単純な数字ではなく、おそらくそのようなことでしょう12:00 Jun 4 2019 UTC + 5 days。)ここでは、Bashがなぜそれほど難しいのかを言うのは難しいです。

これで、ファイル名を渡すと、先行スペースのある文字列は先行スペースのない文字列とは異なるファイル名になり、すべてのプログラムがそれを無視するかどうかを知ることは困難です。


これらの単純な値を使用してください(グローバル文字がなく、分割したい場所)。実行スペース、デフォルト値と仮定IFS)実際には配列の代わりに文字列を使用できます。いいえ2つの異なるパラメータに分割されるように引用してください。だから、read $read_flags ...。または単に設定timeoutflag=-t0.05してくださいread "$timeoutflag" ...。ただし、read "$timeoutflag"変数が空の場合は他の空のパラメータに渡され、エラーが発生するため、これは最良のオプションではありません。

通常、配列は任意のパラメータリストを問題なく保存して使用する正しい方法です。

やや関連:変数に保存されたコマンドをどのように実行できますか?

関連情報