В окне терминала напечатано много текста из моего кода C#, некоторый текст печатается другим приложением, которое я запускал через C# System.Diagnostics (без RedirectStandardOutput я не хочу использовать эту async вещь), и оно печатало свой собственный текст и некоторый текст печатается функцией C# Console.WriteLine.
Я хочу сохранить весь этот текст сверху вниз в текстовый файл. Я не хочу запускать какой-либо файл и сохранять его текст, поскольку все выполнение уже выполнено и весь текст уже напечатан. Я просто хочу сохранить весь этот текст в файл в конце программы.
ПРИМЕЧАНИЕ. Ниже приведен не мой настоящий код (очевидно), но он выглядит примерно так.
from rich.progress import track
import time, os
print("Python Test")
for i in track(range(20), description = "Processing..."):
time.sleep(0.1) # Simulate work being done
os.system("color 08")
using System.Diagnostics;
Console.WriteLine("Test");
// Create a new process instance
Process process = new Process();
// Configure the process using StartInfo
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = $"/c echo Hello world! & timeout /t 2 & python test.py";
// Start the process
process.Start();
// Wait for CMD to finish
process.WaitForExit();
Console.WriteLine("Test1");
/*
---- Save all of that above text that was printed here at the end of the code. ----
*/
Я не хочу использовать RedirectStandardOutput, потому что, насколько я знаю, если я сделаю это и распечатаю вещи из process, используя эти async вещи, во-первых, он не сможет печатать обновления в режиме реального времени, такие как в этой timeout /t 2 части, это также не сможет учесть изменение цвета окна color 08.
Мне нужна такая система, которая позволит коду выполняться нормально, но когда код достигает конца, он экспортирует текст в файл, очень похоже на то, как работает функция Export Text в терминале Windows.
Вот чего я хочу достичь:
Терминал должен работать нормально, никаких изменений в его работе.
После того, как все сделано в конце моего кода на C#, я хочу сохранить весь этот текст в текстовый файл.
@Compo Я только что привел пример терминала Windows. Пример: предположим, вы выполнили что-то в терминале Windows, а затем хотели сохранить весь полученный текст в файл, чтобы использовать его функцию Export Text. Аналогично, что есть в C#, я могу использовать для экспорта всего текста после завершения печати. Мой вопрос очень ясен.
Существует множество различных «терминалов». У каждого могут быть разные проблемы и/или решения. Мы ожидаем решения конкретной проблемы, а не общей. Если это только для cmd.exe, удалите тег [терминал ]. Если это не специально для cmd.exe, удалите тег [ cmd ] и предоставьте дополнительную информацию (вероятно, включая тег [ windows]).
@Compo Это не общая проблема с терминалом. Это конкретная проблема C#. Я только что использовал функцию Export Text терминала Windows в качестве примера того, какого результата я хочу достичь на C#. Позвольте мне прояснить это для вас. Есть код, который печатает много чего. Некоторые из них напечатаны System.DiagnosticsProcess, некоторые — Console.WriteLine. В конце моей программы на C# я хочу сохранить весь этот напечатанный текст в текстовый файл без использования async или RedirectStandardOutput. Я все ясно сказал. Почему вас так беспокоит мой пример терминала?
Однако его необходимо распечатать для конкретной программы, а не для каждой, которую можно запустить в вашей целевой операционной системе! Если вам нужно получить доступ к буферу конкретной консоли, нам нужно знать, какая именно для конкретного вопроса.
Командная строка @Compo. Моя целевая ОС — только Windows 10 и 11.
Почему вы вообще чувствуете, что вам нужен cmd? Почему бы просто не вызвать то, что вы хотите, напрямую? Вам не нужно cmd, чтобы получить стандартный результат.





Это то что ты хочешь?
void Main()
{
var sb = new StringBuilder();
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe ",
Arguments = $"/c echo Hello world! & timeout /t 2 & color 08",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
};
using (var process = new Process{StartInfo = startInfo})
{
process.OutputDataReceived += (sender, args) => sb.AppendLine(args.Data);
process.ErrorDataReceived += (sender, args) => sb.AppendLine(args.Data);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
}
Console.WriteLine("start log");
Console.WriteLine(sb.ToString()); //can tace output and save to file
Console.WriteLine("end log");
}
выход:
start log
Hello world!
Waiting for 2 seconds, press a key to continue ...081080
end log
Я уже пробовал, и это не решение моей проблемы. Однако спасибо за ваше решение.
Ядро ОС всех операционных систем использует эти три основных потока ввода-вывода (ввода-вывода): STDIN, STDOUT и STDERR. STDIN — это поток ввода-вывода, обрабатывающий информацию, связанную с вводом, STDOUT — это поток ввода-вывода, обрабатывающий информацию, связанную с выводом, а STDERR — это поток ввода-вывода, обрабатывающий информацию, связанную с ошибками. Все службы, приложения и компоненты ядра ОС используют эти потоки для управления информацией ввода-вывода в любой ОС.
В предоставленном вами коде проблема заключается в том, что вы хотите обработать информацию, предоставляемую работой пакетного сценария, без перенаправления потока STDOUT. Приложение C# может читать выходные данные пакетного сценария с помощью файлов (например, файлов конфигурации JSON), сокетов, каналов и потока STDOUT. Вышеупомянутые методы являются методами межпроцессного взаимодействия (вы можете проверить эту ссылку для получения дополнительной информации: https://stackoverflow.com/a/76196178/16587692). Эти методы необходимо использовать, поскольку между приложением C# и пакетным сценарием нет прямого канала связи. В этой ситуации наиболее жизнеспособным методом является использование потока STDOUT. Вы можете сохранить цвета консоли, изменив их с помощью приложения C#, а не изменяя их путем передачи аргументов пакетному сценарию. Вы также можете записать вывод в реальном времени в нужный файл журнала, но в качестве предостережения не читайте из файла журнала, пока приложение C# записывает в этот файл, поскольку это вызовет состояние гонки, и это может повредить вашу Ячейки HDD/SSD, в которых находится файл.
using System.Text;
using System.Diagnostics;
namespace Test
{
class Program
{
static void Main(string[] args)
{
Operation().Wait();
Console.ReadLine();
}
private static void SetPermissions(string file_name)
{
#pragma warning disable CA1416 // Validate platform compatibility
// CHECK IF THE CURRENT OS IS WINDOWS
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows) == true)
{
// GET THE SPECIFIED FILE INFORMATION OF THE SELECTED FILE
FileInfo settings_file_info = new FileInfo(file_name);
// GET THE ACCESS CONTROL INFORMATION OF THE SELECTED FILE AND STORE THEM IN A 'FileSecurity' OBJECT
System.Security.AccessControl.FileSecurity settings_file_security = settings_file_info.GetAccessControl();
// ADD THE ACCESS RULES THAT ALLOW READ, WRITE, AND DELETE PERMISSIONS ON THE SELECTED FILE FOR THE CURRENT USER
settings_file_security.AddAccessRule(new System.Security.AccessControl.FileSystemAccessRule(System.Security.Principal.WindowsIdentity.GetCurrent().Name, System.Security.AccessControl.FileSystemRights.Write, System.Security.AccessControl.AccessControlType.Allow));
settings_file_security.AddAccessRule(new System.Security.AccessControl.FileSystemAccessRule(System.Security.Principal.WindowsIdentity.GetCurrent().Name, System.Security.AccessControl.FileSystemRights.Read, System.Security.AccessControl.AccessControlType.Allow));
settings_file_security.AddAccessRule(new System.Security.AccessControl.FileSystemAccessRule(System.Security.Principal.WindowsIdentity.GetCurrent().Name, System.Security.AccessControl.FileSystemRights.Delete, System.Security.AccessControl.AccessControlType.Allow));
// UPDATE THE ACCESS CONTROL SETTINGS OF THE FILE BY SETTING THE
// MODIFIED ACCESS CONTROL SETTINGS AS THE CURRENT SETTINGS
settings_file_info.SetAccessControl(settings_file_security);
}
else
{
// IF THE OS IS A UNIX BASED OS, SET THE FILE PERMISSIONS FOR READ AND WRITE OPERATIONS
// WITH THE 'UnixFileMode.UserRead | UnixFileMode.UserWrite' BITWISE 'OR' OPERATION
File.SetUnixFileMode(file_name, UnixFileMode.UserRead | UnixFileMode.UserWrite);
}
#pragma warning restore CA1416 // Validate platform compatibility
}
private static async Task<bool> Operation()
{
// Process object
System.Diagnostics.Process proc = new System.Diagnostics.Process();
string file_name = String.Empty;
string arguments = String.Empty;
// CHECK IF THE CURRENT OS IS WINDOWS AND SET THE FILE PATH AND ARGUMETS ACCORDINGLY
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows) == true)
{
file_name = @"C:\Users\teodo\PycharmProjects\Test\.venv\Scripts\python.exe";
arguments = @"C:\Users\teodo\PycharmProjects\Test\main.py";
}
else
{
file_name = @"python3";
arguments = @"/mnt/c/Users/teodo/PycharmProjects/Test/main.py";
}
// Path where the python executable is located
proc.StartInfo.FileName = file_name;
// Path where python executable is located
proc.StartInfo.Arguments = arguments;
// Start the process
proc.Start();
// Named pipe server object with an "In" direction. This means that this pipe can only read messages. On Windows it creates a pipe at the
// '\\.\pipe\pipe-sub-directory\pipe-name' virtual directory, on Linux it creates a Unix Named Socket in the '/tmp' directory
System.IO.Pipes.NamedPipeServerStream fifo_pipe_connection = new System.IO.Pipes.NamedPipeServerStream("/tmp/fifo_pipe", System.IO.Pipes.PipeDirection.In);
// Create a backlog text file if none is existent, set its permissions as Read/Write,
// and create a stream that allows direct read and write operations to the file
FileStream fs = File.Open("backlog.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite);
SetPermissions("backlog.txt");
try
{
// Wait for a client to connect synchronously
fifo_pipe_connection.WaitForConnection();
while (true)
{
// Initiate a binary buffer byte array with a size of 1 Kb
byte[] buffer = new byte[1024];
// Read the received bytes into the buffer byte array and also
// store the number of bytes read into an integer named 'read'
int read = await fifo_pipe_connection.ReadAsync(buffer, 0, buffer.Length);
// Write the received bytes into the 'backlog.txt' file
await fs.WriteAsync(buffer, 0, read);
// Flush the bytes within the stream's buffer into the file
await fs.FlushAsync();
// If the number of bytes read is equal to '0' there are no bytes
// left to read on the Pipe's stream and the read loop is closed
if (read == 0)
{
break;
}
}
}
catch
{
}
finally
{
fs?.DisposeAsync();
fifo_pipe_connection?.DisposeAsync();
}
return true;
}
}
}
Каналы FIFO, также известные как именованные каналы, представляют собой тип сокета, который использует файловую систему операционной системы для облегчения обмена информацией между приложениями. Трубы FIFO подразделяются на 3 категории: входящие трубы, внешние трубы и двусторонние трубы. Входящие каналы — это каналы, по которым сервер каналов может получать информацию только от клиентов каналов, исходящие каналы — это каналы, по которым сервер каналов может отправлять информацию только клиентам каналов, а двусторонние каналы — это каналы, по которым сервер каналов может как отправлять и получать информацию от Клиентов Pipe.
Если необходимо сохранить целостность вывода, каналы FIFO являются лучшим вариантом для метода межпроцессного взаимодействия. В этом сценарии лучшим решением будет внутренний канал, поскольку приложение C# должно получать информацию от приложения Python. Чтобы иметь кросс-платформенные возможности, приложения C# и Python используют условные операторы, чтобы проверить, на какой платформе ОС работают приложения.
using System.Text;
using System.Diagnostics;
namespace Test
{
class Program
{
static void Main(string[] args)
{
Operation().Wait();
Console.ReadLine();
}
private static void SetPermissions(string file_name)
{
#pragma warning disable CA1416 // Validate platform compatibility
// CHECK IF THE CURRENT OS IS WINDOWS
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows) == true)
{
// GET THE SPECIFIED FILE INFORMATION OF THE SELECTED FILE
FileInfo settings_file_info = new FileInfo(file_name);
// GET THE ACCESS CONTROL INFORMATION OF THE SELECTED FILE AND STORE THEM IN A 'FileSecurity' OBJECT
System.Security.AccessControl.FileSecurity settings_file_security = settings_file_info.GetAccessControl();
// ADD THE ACCESS RULES THAT ALLOW READ, WRITE, AND DELETE PERMISSIONS ON THE SELECTED FILE FOR THE CURRENT USER
settings_file_security.AddAccessRule(new System.Security.AccessControl.FileSystemAccessRule(System.Security.Principal.WindowsIdentity.GetCurrent().Name, System.Security.AccessControl.FileSystemRights.Write, System.Security.AccessControl.AccessControlType.Allow));
settings_file_security.AddAccessRule(new System.Security.AccessControl.FileSystemAccessRule(System.Security.Principal.WindowsIdentity.GetCurrent().Name, System.Security.AccessControl.FileSystemRights.Read, System.Security.AccessControl.AccessControlType.Allow));
settings_file_security.AddAccessRule(new System.Security.AccessControl.FileSystemAccessRule(System.Security.Principal.WindowsIdentity.GetCurrent().Name, System.Security.AccessControl.FileSystemRights.Delete, System.Security.AccessControl.AccessControlType.Allow));
// UPDATE THE ACCESS CONTROL SETTINGS OF THE FILE BY SETTING THE
// MODIFIED ACCESS CONTROL SETTINGS AS THE CURRENT SETTINGS
settings_file_info.SetAccessControl(settings_file_security);
}
else
{
// IF THE OS IS A UNIX BASED OS, SET THE FILE PERMISSIONS FOR READ AND WRITE OPERATIONS
// WITH THE 'UnixFileMode.UserRead | UnixFileMode.UserWrite' BITWISE 'OR' OPERATION
File.SetUnixFileMode(file_name, UnixFileMode.UserRead | UnixFileMode.UserWrite);
}
#pragma warning restore CA1416 // Validate platform compatibility
}
private static async Task<bool> Operation()
{
// Process object
System.Diagnostics.Process proc = new System.Diagnostics.Process();
string file_name = String.Empty;
string arguments = String.Empty;
// CHECK IF THE CURRENT OS IS WINDOWS AND SET THE FILE PATH AND ARGUMETS ACCORDINGLY
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows) == true)
{
file_name = @"C:\Users\teodo\PycharmProjects\Test\.venv\Scripts\python.exe";
arguments = @"C:\Users\teodo\PycharmProjects\Test\main.py";
}
else
{
file_name = @"python3";
arguments = @"/mnt/c/Users/teodo/PycharmProjects/Test/main.py";
}
// Path where the python executable is located
proc.StartInfo.FileName = file_name;
// Path where python executable is located
proc.StartInfo.Arguments = arguments;
// Start the process
proc.Start();
// Named pipe server object with an "In" direction. This means that this pipe can only read messages. On Windows it creates a pipe at the
// '\\.\pipe\pipe-sub-directory\pipe-name' virtual directory, on Linux it creates a Unix Named Socket in the '/tmp' directory
System.IO.Pipes.NamedPipeServerStream fifo_pipe_connection = new System.IO.Pipes.NamedPipeServerStream("/tmp/fifo_pipe", System.IO.Pipes.PipeDirection.In);
// Create a backlog text file if none is existent, set its permissions as Read/Write,
// and create a stream that allows direct read and write operations to the file
FileStream fs = File.Open("backlog.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite);
SetPermissions("backlog.txt");
try
{
// Wait for a client to connect synchronously
fifo_pipe_connection.WaitForConnection();
while (true)
{
// Initiate a binary buffer byte array with a size of 1 Kb
byte[] buffer = new byte[1024];
// Read the received bytes into the buffer byte array and also
// store the number of bytes read into an integer named 'read'
int read = await fifo_pipe_connection.ReadAsync(buffer, 0, buffer.Length);
// Print the bytes sent by the Python application on the pipe
Console.WriteLine(Encoding.UTF8.GetString(buffer));
// Write the received bytes into the 'backlog.txt' file
await fs.WriteAsync(buffer, 0, read);
// Flush the bytes within the stream's buffer into the file
await fs.FlushAsync();
// If the number of bytes read is equal to '0' there are no bytes
// left to read on the Pipe's stream and the read loop is closed
if (read == 0)
{
break;
}
}
}
catch
{
}
finally
{
fs?.DisposeAsync();
fifo_pipe_connection?.DisposeAsync();
}
return true;
}
}
}
import os
import sys
import time
from rich.progress import track
import platform
import socket
fifo_write = None
unix_named_pipe = None
def operation():
try:
write("!!! Python Test !!!\n\n")
# Simulate work being done
set_range = range(20)
for i in track(set_range, description = "Processing..."):
time.sleep(0.1)
# SEND THE CURRENT PROGRESS AS A PERCENTAGE OVER THE PIPE
write("Processing..." + str((100 / set_range.stop) * (i + 1)) + "%\n")
# CHANGE THE TERMINAL COLOR TO GREY
if platform.system() == "Windows":
os.system("color 08")
else:
print("\n\n")
os.system(r"echo '\e[91m!!! COLOR !!!'")
# PAUSE THE CURRENT THREAD FOR 2 SECONDS
time.sleep(2)
# CHANGE THE TERMINAL COLOR TO WHITE
if platform.system() == "Windows":
os.system("color F")
else:
os.system(r"echo '\e[00m!!! COLOR !!!'")
print("\n\n")
write("[ Finished ]")
except KeyboardInterrupt:
sys.exit(0)
def write(msg):
if platform.system() == "Windows":
fifo_pipe_write(msg)
else:
unix_named_socket_write(msg)
def fifo_pipe_write(msg):
try:
global fifo_write
if fifo_write is not None:
# WRITE THE STRING PASSED TO THE FUNCTION'S AS AN ARGUMENT
# IN THE FIFO PIPE FILE USING THE GLOBAL STREAM
fifo_write.write(msg)
except KeyboardInterrupt:
sys.exit(0)
def unix_named_socket_write(msg):
try:
global unix_named_pipe
if unix_named_pipe is not None:
unix_named_pipe.send(str(msg).encode(encoding = "utf-8"))
except KeyboardInterrupt:
pass
def stream_finder() -> bool:
is_found = False
try:
# INITIATE A PIPE SEARCH SEQUENCE FOR 10 SECONDS
for t in range(0, 10):
try:
try:
# IF THE OS IS WINDOWS SEARCH FOR A PIPE FILE
if platform.system() == "Windows":
# IF PIPE IS FOUND RETURN TRUE AND STORE THE OPENED PIPE FILE STREAM GLOBALLY
global fifo_write
fifo_write = open(r"\\.\pipe\tmp\fifo_pipe", "w")
is_found = True
break
# ELSE, SEARCH FOR A NAMED UNIX SOCKET
else:
# IF SOCKET IS FOUND RETURN TRUE AND STORE THE OPENED SOCKET GLOBALLY
global unix_named_pipe
unix_named_pipe = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
unix_named_pipe.connect("/tmp/fifo_pipe")
is_found = True
break
except FileNotFoundError:
# IF PIPE IS NOT FOUND
print("\n\n[ Searching pipe ]")
except OSError:
# IF PIPE IS NOT FOUND
print("\n\n[ Searching pipe ]")
# MAKE THE LOOP WAIT 1 SECOND FOR EACH ITERATION
time.sleep(1)
except KeyboardInterrupt:
sys.exit(0)
return is_found
if __name__ == "__main__":
try:
# INITIATE THE PIPE SEARCHING OPERATION
found = stream_finder()
# IF PIPE SEARCHING OPERATION IS SUCCESSFUL
if found is True:
print("\n\n[ Pipe found ]\n\n")
# INITIATE THE MAIN OPERATION
operation()
except KeyboardInterrupt:
sys.exit(0)
Да, это работает, но дело в том, что какой-то цветной текст отображается каким-то другим приложением. Например, в своем вопросе я привел этот пример color 08, потому что в моем реальном коде есть скрипт Python, который использует модуль rich для панели загрузки (он похож на обновление цвета и в реальном времени, вы знаете). В этом случае ваш код не сможет отображать изменения цвета, которые выполняются другими приложениями и скриптами. Хотя ваше решение очень близко к тому, что я на самом деле хочу.
Я обновил свой вопрос скриншотами, поэтому, пожалуйста, проверьте его.
Прежде всего, вам нужно четко понимать, выполняется ли этот вывод непосредственно в командной строке Windows,
cmd.exe, или в окне/вкладке терминала Windows,wt.exe. Как только вы все поймете, Отредактируйте тело вопроса и теги соответствующим образом.