/ dev / stdoutのターゲットの場所をbashスクリプトに保存する方法は?


12

最初の/dev/stdoutファイル記述子を他の場所に置き換える前に元の場所を保持したい特定のbashスクリプトがあります。

だから、当然、私は次のようなものを書きました

old_stdout=$(readlink -f /dev/stdout)

そして、それはうまくいきませんでした。問題が何であるかをすぐに理解します。

test@ubuntu:~$ echo $(readlink -f /dev/stdout)
/proc/5175/fd/pipe:[31764]
test@ubuntu:~$ readlink -f /dev/stdout
/dev/pts/18

当然、$()サブシェルで実行され、親シェルにパイプされます。

質問は次のとおりです。/dev/stdout場所をbashスクリプトの文字列として保存するための信頼できる(Linuxディストリビューション間の移植性を考慮した)方法はありますか?


これはXY問題のように聞こえます。根本的な問題は何ですか?
クサラナナンダ

根本的な問題は、2つのモードで実行される特定のインストールスクリプトです。サイレントモードでは、すべての出力をファイルに記録します。ただし、両方のモードで、スクリプトはユーザーと対話したい、つまり端末に出力してユーザーの応答を読みたいと考えています。その/dev/stdoutため、保存すると、サイレントモードでメッセージを印刷する際の問題が解決されると考えました。代替手段は、出力を生成する他のすべてのアクションをリダイレクトすることであり、それらは非常に多くあります。ユーザーインタラクションメッセージの約100倍。
alexey.e.egorov

ユーザーと対話する標準的な方法は、に印刷することstderrです。これは、たとえば、プロンプトがstderrデフォルトで表示される理由です。
クサラナナンダ

残念ながら、stderrスクリプトは多数の外部プログラムを呼び出し、すべての可能なエラーメッセージが収集されてログに記録されるため、スクリプトも再保存して保存する必要があります。
alexey.e.egorov

回答:


14

ファイル記述子を保存するには、別のfdに複製します。対応するファイルへのパスを保存するだけでは不十分です。オープニングモード、オープニングフラグ、ファイル内の現在の位置などを保存する必要があります。そしてもちろん、匿名のパイプまたはソケットの場合、パスがないため機能しません。保存したいのは、fdが参照するオープンファイルの説明です。fdを複製すると、実際には同じオープンファイルの説明に新しいfdが返されます

Bourneのようなシェルを使用してファイル記述子を別の記述子に複製するには、構文は次のとおりです。

exec 3>&1

上記では、fd 1がfd 3に複製されます。

以前にfd 3が既に開いていたものはすべて閉じられますが、fds 3から9(通常はさらに最大99 yash)がその目的のために予約されていることに注意してください(0、1、または2に反する特別な意味はありません)シェルは、独自の内部ビジネスにそれらを使用しないことを知っています。fd 3が前もって開いていた唯一の理由は、スクリプト1でそれをしたか、呼び出し元によってリークされたためです。

次に、stdoutを別のものに変更できます。

exec > /dev/null

その後、stdoutを復元します。

exec >&3 3>&-

3>&-不要になったファイル記述子を閉じることです)。

ここでの問題は、kshを除き、その後実行するすべてのコマンドがexec 3>&1そのfd 3を継承することです。これはfdリークです。一般的には大したことではありませんが、それは問題を引き起こす可能性があります。

kshそれらのfdsにclose-on-execフラグを設定します(2を超えるfdsの場合)が、他のシェルや他のシェルにはそのフラグを手動で設定する方法がありません。

他のシェルの回避策は、次のように、コマンドごとにfd 3を閉じることです。

exec 3>&-

exec > file.log

ls 3>&-
uname 3>&-

exec >&3 3>&-

面倒。ここで、最善の方法は、まったく使用せずexec、コマンドグループをリダイレクトすることです。

{
  ls
  uname
} > file.log

そこでは、stdoutを保存して後で復元することに注意を払うのはシェルです(そして、fd(9を超える、99を超えるyash)にclose-on-execフラグを設定して複製することで内部的に行います)。

1

現在、これらのfds 3〜9の管理は、特にスクリプトでこれらのfdsを使用する可能性のあるサードパーティのコードを使用している場合、それらを広範囲にまたは関数で使用すると、面倒で問題が多くなります。

シェルによっては、( 、zshbashksh93すべての機能(追加のオリバーKiddleによって提案されzsh、この場合に役立ちます代わりに、10以上の最初のフリーFDを割り当てるための代替構文を持っている、それは彼らの開発者の間で議論された後、2005年に同時期に)):

myfunction() {
  local fd
  exec {fd}>&1
  # stdout was duplicated onto a new fd above 10, whose actual value
  # is stored in the fd variable
  ...
  # it should even be safe to re-enter the function here
  ...
  exec >&"$fd" {fd}>&-
}

また、rc.localサービスからscripが実行されるときに発生するため、fd 3がすでに使用されているという意味でコードが間違っていますexec {FD}>&1。しかし、これはbash 4でのみサポートされています。これは非常に悲しいことです。したがって、これは実際には移植性がありません。
alexey.e.egorov

@ alexey.e.egorov、編集を参照してください。
ステファンシャゼル16

Bash 3. *はこの機能をサポートしていません。このバージョンはCentos 5で使用されており、現在もサポートおよび使用されています。そして、無料の記述子を見つけることeval "exec $i>&1"は、面倒なため、避けたいものです。9を超えるfdsが無料であることを本当に信頼できますか?
alexey.e.egorov

@ alexey.e.egorov、いいえ、あなたはそれを後方に見ています。fds 3〜9は自由に使用でき(必要に応じて管理するのはユーザー次第です)、その目的のためのものです。9を超えるfdsはシェルによって内部的に使用される可能性があり、それらを閉じると厄介な結果になる可能性があります。ほとんどのシェルでは使用できません。bash足で自分を撃つことができます。
ステファンシャゼル16

2
@ alexey.e.egorov、起動時にスクリプトが(3..9)を開いている場合、それは呼び出し側がそれらを閉じるのを忘れたか、close-on-execフラグを設定し忘れたためです。それがfdリークと呼ばれるものです。さて、おそらく呼び出し元はそれらのfdsをあなたに渡すことを意図していたので、あなたはそれらからデータを読み書きできますが、それについては知っているでしょう。それらについて知らない場合は気にせず、自由に閉じることができます(呼び出し側のプロセスではなく、スクリプトのプロセスfdを閉じるだけです)。
ステファンシャゼル16

3

ご覧のとおり、bashスクリプトは、ファイル記述子を割り当てることができる通常のプログラミング言語とは異なります。

最も簡単な解決策は、サブシェルを使用してリダイレクトしたいものを実行し、処理を標準I / Oがそのままのトップシェルに戻すことができるようにすることです。

別の解決策はtty、TTYデバイスを識別し、スクリプトのI / Oを制御するために使用することです。例えば:

dev=$(tty)

できます。

echo message > $dev

>別の解決策は、ttyを使用してTTYデバイスを識別し、スクリプトのI / Oを制御することです。これはどうやって?
alexey.e.egorov

1
私は答えに例を含めました。
ジュリーペレティエ

1

$$ 対話型シェルの場合は現在のプロセスPIDを取得し、関連するシェルPIDをスクリプト化します。

以下を使用できます。

readlink -f /proc/$$/fd/1

例:

% readlink -f /proc/$$/fd/1
/dev/pts/33

% var=$(readlink -f /proc/$$/fd/1)

% echo $var                       
/dev/pts/33

1
機能的ですが、特定の/proc構造に依存する/dev/stdoutと、質問で述べたように使用する場合と同様に、移植性の問題が発生します。
ジュリーペレティエ

1
@JuliePelletier specif /proc構造に依存していますか?procfs..
heemayl

1
そうprocfsです、ほとんど常に存在するようにLinuxに一般化できますが、移植性に関する質問をよく目にし、他のシステムへの移植性を検討することを含む優れた開発方法論があります。 bash多数のオペレーティングシステムで実行できます。
ジュリーペレティエ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.