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
.