En Windows, ¿puedo redirigir stdout a una tubería (con nombre) en la línea de comando?

17

¿Hay alguna manera de redirigir la salida estándar de un proceso en la consola Win32 a una tubería con nombre ? Las canalizaciones con nombre están integradas en Windows y, aunque serían un concepto útil, nunca las he visto utilizadas desde la línea de comandos.

Es decir. como example.exe >\\.\mypipe. (Es posible que esta sintaxis no sea correcta, pero usted entiende). Me gustaría poder redirigir stdout y stderr a diferentes tuberías al mismo tiempo.

Me gustaría evitar el uso de archivos físicos como un sustituto, para evitar lidiar con la lentitud de IO, las memorias intermedias de IO, los bloqueos de archivos, los derechos de acceso, el espacio disponible en el disco duro, la decisión de sobrescribir, la persistencia no deseada, etc.

Otra razón es porque el conjunto tradicional de herramientas de Windows no está diseñado alrededor de una filosofía basada en archivos (de texto) tanto como en Unix . Además, las tuberías con nombre no podrían montarse fácilmente en Windows, si es que lo hacen.

Finalmente, existe la curiosidad de si un buen concepto podría utilizarse.

n611x007
fuente
1
¿Quieres decir como redirigir a una tubería con nombre o una ranura de correo? ¿Tiene / está dispuesto a escribir el extremo receptor?
ixe013
Sí, como una tubería con nombre o una ranura de correo. Todavía no tengo pero estoy dispuesto a escribir el extremo receptor.
n611x007

Respuestas:

8

No estoy seguro de por qué no desea redirigir a un archivo. Hay dos métodos que proporcionaré aquí. Un método es redirigir y leer desde un archivo, el otro es un conjunto de programas.


Tubos con nombre

Lo que hice fue escribir dos programas para .NET 4. Uno envía la salida a una tubería con nombre, el otro lee de esta tubería y se muestra en la consola. El uso es bastante simple:

asdf.exe | NamedPipeServer.exe "APipeName"

En otra ventana de consola:

NamedPipeClient.exe "APipeName"

Desafortunadamente, esto solo puede redirigir stdout(o stdincombinarse), no stderrpor sí solo, debido a limitaciones en el operador de canalización ( |) en el Símbolo del sistema de Windows. Si descubre cómo enviar a stderrtravés de ese operador de tubería, debería funcionar. Alternativamente, el servidor podría modificarse para iniciar su programa y redirigir específicamente stderr. Si eso es necesario, hágamelo saber en un comentario (o hágalo usted mismo); no es demasiado difícil si tiene algún conocimiento de la biblioteca "Proceso" de C # y .NET.

Puede descargar el servidor y el cliente .

Si cierra el servidor después de la conexión, el cliente se cerrará inmediatamente. Si cierra el cliente después de la conexión, el servidor se cerrará tan pronto como intente enviar algo a través de él. No es posible volver a conectar una tubería rota, principalmente porque no me molesto en hacer algo tan complicado en este momento. También está limitado a un cliente por servidor .

Código fuente

Estos están escritos en C #. No tiene mucho sentido tratar de explicarlo. Usan .NET NamedPipeServerStream y NamedPipeClientStream .

El servidor:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Pipes;
using System.IO;

namespace NamedPipeServer
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args == null || args.Length == 0)
            {
                Console.Error.WriteLine("[NamedPipeServer]: Need pipe name.");
                return;
            }

            NamedPipeServerStream PipeServer = new NamedPipeServerStream(args[0], System.IO.Pipes.PipeDirection.Out);
            PipeServer.WaitForConnection();
            StreamWriter PipeWriter = new StreamWriter(PipeServer);
            PipeWriter.AutoFlush = true;

            string tempWrite;

            while ((tempWrite = Console.ReadLine()) != null)
            {
                try
                {
                    PipeWriter.WriteLine(tempWrite);
                }
                catch (IOException ex)
                {
                    if (ex.Message == "Pipe is broken.")
                    {
                        Console.Error.WriteLine("[NamedPipeServer]: NamedPipeClient was closed, exiting");
                        return;
                    }
                }
            }

            PipeWriter.Close();
            PipeServer.Close();
        }
    }
}

El cliente:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Pipes;
using System.IO;

namespace NamedPipeClient
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args == null || args.Length == 0)
            {
                Console.Error.WriteLine("[NamedPipeClient]: Need pipe name.");
                return;
            }

            NamedPipeClientStream PipeClient = new NamedPipeClientStream(".", args[0], System.IO.Pipes.PipeDirection.In);
            PipeClient.Connect();
            StreamReader PipeReader = new StreamReader(PipeClient);

            string tempRead;

            while ((tempRead = PipeReader.ReadLine()) != null)
            {
                Console.WriteLine(tempRead);
            }

            PipeReader.Close();
            PipeClient.Close();
        }
    }
}

Redireccionando a un archivo

type NUL>StdErr.temp
start powershell -c Get-Content StdErr.temp -Wait
MyExecutable.exe 2>StdErr.temp
  1. Crea un archivo vacío
  2. Inicie una nueva ventana de consola que vea el archivo
  3. Ejecute el ejecutable y redirija la stderrsalida a ese archivo

Esto proporciona el efecto deseado de una ventana de consola para mirar stdout(y proporcionar stdin), y otra para mirar stderr.

Cualquier cosa que imite tailfuncionaría. El método PowerShell funciona de forma nativa en Windows, pero puede ser un poco lento (es decir, hay cierta latencia entre la escritura en el archivo y la visualización en la pantalla). Vea esta pregunta de StackOverflow para otras tailalternativas.

El único problema es que el archivo temporal puede crecer bastante. Una posible solución es ejecutar un bucle que solo se imprime si el archivo tiene contenido y borrar el archivo inmediatamente después, pero eso provocaría una condición de carrera.

Beto
fuente
2
Los usuarios de UNIX son los que están molestos porque Windows falla una vez más al implementar una idea de 40 años de una manera sensata. No debería necesitar escribir un programa personalizado cada vez que quiera hacer algo básico. facepalm
bambams
Vea a continuación: puede usar la ruta UNC asignada a una tubería con nombre y acceder a ella directamente.
Erik Aronesty
15

Me sorprende que esto no haya sido respondido correctamente ya. De hecho, hay una ruta UNC asignada a las tuberías con nombre por el sistema, accesible en cualquier máquina de la red, que se puede usar como un archivo normal:

program.exe >\\.\pipe\StdOutPipe 2>\\.\pipe\StdErrPipe

Suponiendo que existan tuberías llamadas "StdOutPipe" y "StdErrPipe" en esta máquina, este intenta conectarse y escribir en ellas. La pipeparte es lo que especifica que desea una tubería con nombre.

IllidanS4 quiere que Monica regrese
fuente
Creo que esto debería marcarse como la respuesta correcta, ya que es justo lo que está preguntando @ n611x007, ¡no se requieren programas externos para hacer esto!
arturn
el problema es que aún necesita lanzar un servicio de algún tipo que cree esas tuberías ... no existen independientemente del programa que creó y se destruyen cuando el programa desaparece.
Erik Aronesty
@ ErikAronesty Asumí que estas tuberías ya existen. De lo contrario, no hay forma de crearlos solo con cmd.exe.
IllidanS4 quiere que Mónica regrese el
Sí, eso es lo bueno de las tuberías Unix, puedes crear tuberías desde la línea de comando
Erik Aronesty
1

No con el shell estándar (CMD.EXE). Para los programadores, es bastante fácil . Simplemente tome las dos tuberías de un proceso que comenzó.

MSalters
fuente
1
Solo prb es que la muestra usa tuberías anónimas, que no admiten io superpuesto (asíncrono) y, por lo tanto, son propensas a puntos muertos, o al menos debe usarse PeekNamedPipe.
Fernando González Sánchez
1
Las esperas de bloqueo no son puntos muertos, y el problema fundamental (el subproceso que consume datos impide que el subproceso productor lo produzca) no se resuelve con E / S superpuestas de todos modos.
MSalters
Me refería a esto: blogs.msdn.com/b/oldnewthing/archive/2011/07/07/10183884.aspx , un ejemplo de punto muerto.
Fernando González Sánchez
1
@FernandoGonzalezSanchez: más o menos el mismo problema. Tenga en cuenta que la solución recomendada (hilo adicional) evita cualquier necesidad de E / S asíncrona.
MSalters
1
Sí, el problema en la muestra msdn es que el padre está atascado para siempre esperando, en la función ReadFromPipe, línea bSuccess = ReadFile (g_hChildStd_OUT_Rd, chBuf, BUFSIZE, & dwRead, NULL); leerá 70 bytes la primera vez y la segunda se quedará para siempre (PeekNamedPipe, que falta, no está bloqueando).
Fernando González Sánchez
-1

Sus preferencias para una tubería de datos de Windows desde un servidor a una ventana de dos de cliente, ya sea de forma inmediata o posterior, podrían satisfacerse con una pequeña unidad de RAM. La misma memoria se asigna para los datos, escritos / leídos con un nombre similar a un sistema de archivos. El cliente elimina el archivo usado y espera otro, o lo deja desaparecer cuando la computadora se apaga.

user999850
fuente