34

stdinGoプログラムの原文から読みたいです。たとえば、私がした場合echo test stdin | go run test.go、「test stdin」にアクセスしたいと思うでしょう。から読み込もうとしましos.Stdinたが、何も入っていない場合は入力待ちになります。最初にサイズも確認してみましたが、os.Stdin.Stat().Size()入力が渡されても 0 です。

私に何ができる?

4

3 に答える 3

73

を使用して stdin から読み取るos.Stdinと、期待どおりに動作するはずです。

package main

import "os"
import "log"
import "io/ioutil"

func main() {
    bytes, err := ioutil.ReadAll(os.Stdin)

    log.Println(err, string(bytes))
}

実行echo test stdin | go run stdin.goすると、「test stdin」が正常に出力されるはずです。

発生した問題を特定するために使用したコードを添付していただけると助かります。

行ベースの読み取りには、次を使用できますbufio.Scanner

import "os"
import "log"
import "bufio"

func main() {
    s := bufio.NewScanner(os.Stdin)
    for s.Scan() {
        log.Println("line", s.Text())
    }
}
于 2012-09-11T12:26:25.417 に答える
20

「初期標準入力」などがないため、あなたの質問自体には賢明な答えはないと思います。Unix ライクな OS と Windows は、「標準ストリーム」の概念を実装しています。これは次のように機能します (簡略化): プロセスが作成されると、自動的に 3 つのファイル記述子 (Windows ではハンドル) が開かれます — stdin、stdout、および stderr。間違いなく、あなたはこの概念に精通していますが、そこで「ストリーム」という言葉の意味を強調したいと思います - あなたの例では、あなたが呼び出すとき

$ echo 'test stdin' | ./stdin

シェルはpipeを作成し、 2 つのプロセス (1 つはバイナリ用echo、もう 1 つはバイナリ用) を生成し、作成したパイプを使用します。パイプの書き込み FD はechoの stdout に接続され、パイプの読み取り FD はバイナリの stdin に接続されます。次に、echoプロセスが標準出力に書き込もうとするものはすべて、プロセスの標準入力にパイプされます (sic!)。(実際には、今日のほとんどのシェルechoは組み込みプリミティブとして実装されていますが、これはセマンティクスをまったく変更しません/bin/echo。実際のプログラムである代わりに試してみることもできます。また、私はあなたのプログラムを参照していたことに注意し./stdinてください—これは明確にするためgo run stdin.goであり、最終的にはまさにこれを行います。)

ここでいくつかの重要なことに注意してください。

  • 書き込みプロセス(echoあなたの場合)は、標準出力に何も書き込む必要はありません(たとえば、標準出力にecho -n何も書き込まず、正常に終了しません)。
  • また、データの書き込みを任意に遅延させることもできます (そのような遅延を作成したいため、または OS によってプリエンプトされた、またはビジー状態のシステム リソースを待機しているシステムコールでスリープしているなどの理由で)。
  • OS は、パイプを介して転送をバッファリングします。これは、書き込みプロセスがパイプに送信するものが、読み取り側で任意のチャンクで出力される可能性があることを意味します。1
  • パイプを介して送信するデータが書き込み側にないことを知るには、次の 2 つの方法しかありません。
    • 何らかの方法でこれをデータ自体にエンコードします (これは、ライターとリーダーの間で合意されたデータ転送プロトコルを使用することを意味します)。
    • ライターはパイプの側を閉じる可能性があり、その結果、リーダー側で「ファイルの終わり」状態が発生します (ただし、バッファーが空になり、別の呼び出しreadが試行されて失敗した後でのみ)。

これをまとめましょう。観察している動作は正しく、正常です。stdin から何らかのデータを取得することを期待している場合、すぐに利用できると期待してはなりません。標準入力でもブロックしたくない場合は、無限ループで標準入力からの読み取りをブロックするゴルーチンを作成し (ただし、EOF 条件をチェックします)、収集したデータをチャネル経由で渡します (場合によっては、特定の処理の後、必要)。

1これが、パイプライン内の 2 つのパイプ間で通常発生する特定のツール ( など) にgrep、各行の書き込み後に stdout をフラッシュする特別なオプションがある場合がある理由です — 1 つの例については、マニュアルページ--line-bufferedのオプションについて読んでください。この「デフォルトでの完全なバッファリング」セマンティクスを認識していない人は、監視対象のファイルが更新されたことが明らかな場合に、なぜストールして何も表示されないように見えるのか困惑しています。greptail -f /path/to/some/file.log | grep whatever | sed ...


補足として、次のように、バイナリを「そのまま」実行する場合

$ ./stdin

これは、生成されたプロセスが stdin (または「初期 stdin」または whaveter) を持たないという意味ではありません。代わりに、その stdin は、シェルがキーボード インポートを受け取るのと同じストリームに接続されます (したがって、プロセスの stdin に何かを直接入力できます)。 )。

プロセスの標準入力をどこにも接続しない唯一の確実な方法は、

$ ./stdin </dev/null

Unix ライクな OS および

C:\> stdin <NUL

Windows で。この「null デバイス」readにより、プロセスは標準入力から最初に EOF を認識します。

于 2012-09-12T08:37:25.013 に答える
5

標準入力の内容を確認することはできませんが、標準入力が端末またはパイプに関連付けられているかどうかを確認できます。IsTerminal は、標準の unix fd 番号 (0,1,2) だけを受け取ります。syscall パッケージには変数が割り当てられているため、名前を付けたい場合は syscall.Stdin を実行できます。

package main

import (
    "code.google.com/p/go.crypto/ssh/terminal"
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    if ! terminal.IsTerminal(0) {
        b, _ := ioutil.ReadAll(os.Stdin)
        fmt.Print(string(b))
    } else {
        fmt.Println("no piped data")
    }
}
于 2013-05-25T20:54:52.710 に答える