Home > OS >  why redirection &>file with readline in a pipe breaks, but not 1>file or 2>file
why redirection &>file with readline in a pipe breaks, but not 1>file or 2>file

Time:12-04

readline might not be the problem, but i can't reproduce this behavior without it

here is a minimalist reproduction of a shell, it prints a prompt on STDOUT and if the command start with "echo " it prints what comes next :

mybash.c :

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <readline/readline.h>

int     main(void)
{
    char    *prompt;
    char    *line_input;

    prompt = "[mybash]> ";
    while (1)
    {
        line_input = readline(prompt);
        if (line_input)
        {
            if (!strncmp(line_input, "echo ", 5))
            {
                write(1, line_input   5, strlen(line_input) - 5);
                write(1, "\n", 1);
            }
            free(line_input);
        }
        if (!line_input)
        {
            write(2, "exit\n", 5);
            exit(0);
        }
    }
    return (0);
}

at first it looks like it works fine :

exit happens because i press Ctrl-D, or in later examples because pipes ends

[bash]$ gcc mybash.c -lreadline
[bash]$ ./a.out
[mybash]> echo hello
hello
[mybash]> anything
[mybash]> exit
[bash]$

now i put it in a pipe to capture the output

[bash]$ echo "echo hello" | ./a.out &>file.log
[bash]$ cat file.log
 [mybash]> echo hello
 hello
 [mybash]> exit
[bash]$

(i added the empty space at beginning of lines in cat output for readability)

ok, now i change the prompt for something very long :

prompt = WHITE"[mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> "RESET;

and the output works fine in classic interactivity, it works also fine in pipes with a redirection of only STDERR or STDOUT, but if i redirect both, it breaks :

interactive prompt :

[bash]$ ./a.out
[mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> echo hello
hello
[mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> anything
[mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> exit
[bash]$

pipe without redirection :

[bash]$ echo "echo hello" | ./a.out
[mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> echo hello
hello
[mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> anything
[mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> exit
[bash]$

in case it's not clear, i only wrote the first command echo "echo hello" | ./a.out, the rest prompted "automatically", unlike the previous example

STDOUT redirection :

[bash]$ echo "echo hello" | ./a.out 1>file.log
exit
[bash]$ cat file.log
 [mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> echo hello
 hello
 [mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> [%]
[bash]$

STDERR redirection :

[bash]$ echo "echo hello" | ./a.out 2>file.log
[mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> echo hello
hello
[mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> [%]
[bash]$ cat file.log
 exit
[bash]$

STDOUT and STDERR redirections in interactive mode :

[bash]$ ./a.out &>file.log
(i type here without seeing anything)
[bash]$ cat file.log
 [mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> echo hello
 hello
 [mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> anything
 [mybash_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]> exit
[bash]$

and STDOUT & STDERR redirections in pipe :

[bash]$ echo "echo hello" | ./a.out &>file.log
[bash]$ cat file.log
 _file]>echo hellog_prompt_to_test_the_output_in_case_of_a_redirection_in_a
 hello
  exith_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]>
[bash]$

sorry, colors of stackoverflow breaks a little in this example

as you can see, in the last example the prompt line doesn't go to a new line, it writes over itself at the beginning, but it only occurs with &> redirection and pipes, i don't understand why ?

and of course, the same tests on bash instead of mybash works very well, the prompt doesn't break

CodePudding user response:

It seems like a bug. I can reproduce it with libreadline v7 but not with v8. With v7 I get:

cat file.log
]> echo helloy_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]
hello
]> exit_a_very_long_prompt_to_test_the_output_in_case_of_a_redirection_in_a_file]

You can run the commands through strace:

... strace -x -f -o strace1.out -s 2048 ./a.out ...

and compare. You will notice that if at least one input or output is real terminal, you get:

ioctl(2, TCGETS, ... = 0
ioctl(0, TCGETS, ... = -1 ENOTTY (Inappropriate ioctl for device)

or

ioctl(2, TCGETS, ... = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(0, TCGETS, ... = 0

and then:

write(1, "[..._in_a_file]> ", 83) = 83

But when none of the inputs nor outputs are real terminal, e.g. input is a pipe and output is redirection to a file, you get:

ioctl(2, TCGETS, ... = -1 ENOTTY (Inappropriate ioctl for device)
ioctl(0, TCGETS, ... = -1 ENOTTY (Inappropriate ioctl for device)
...
write(1, "[..._in_a_file]\r]> ", 85) = 85

Notice that \r]>? The \r causes overwriting.

CodePudding user response:

The syntax is wrong, AFAIK.

Use echo "echo hello" | ./a.out 1>file.log 2>&1.

  • Related