add_history problem while trying to make a minishell [duplicate] - c

I'm using the readline library in C to create a bash-like prompt within bash. When I tried to make the prompt colorful, with color sequences like these, the coloring works great, but the cursor spacing is messed up. The input is wrapped around too early and the wrap-around is to the same line so it starts overwriting the prompt. I thought I should escape the color-sequences with \[ and \] like
readline("\[\e[1;31m$\e[0m\] ")
But that prints the square brackets, and if I escape the backslashes it prints those too. How do I escape the color codes so the cursor still works?

The way to tell readline that a character sequence in a prompt string doesn't actually move the cursor when output to the screen is to surround it with the markers RL_PROMPT_START_IGNORE (currently, this is the character literal '\001' in readline's C header file) and RL_PROMPT_END_IGNORE (currently '\002').
And as #Joachim and #Alter said, use '\033' instead of '\e' for portability.

I found this question when looking to refine the GNU readline prompt in a bash script. Like readline in C code, \[ and \] aren't special but \001 and \002 will work when given literally via the special treatment bash affords quoted words of the form $'string'. I've been here before (and left unsatisfied due to not knowing to combine it with $'…'), so I figured I'd leave my explanation here now that I have a solution.
Using the data provided here, I was able to conclude this result:
C1=$'\001\033[1;34m\002' # blue - from \e[1;34m
C0=$'\001\033[0;0m\002' # reset - from \e[0;0m
while read -p "${C1}myshell>$C0 " -e command; do
echo "you said: $command"
done
This gives a blue prompt that says myshell> and has a trailing space, without colors for the actual command. Neither hitting Up nor entering a command that wraps to the next line will be confused by the non-printing characters.
As explained in the accepted answer, \001 (Start of Heading) and \002 (Start of Text) are the RL_PROMPT_START_IGNORE and RL_PROMPT_END_IGNORE markers, which tell bash and readline not to count anything between them for the purpose of painting the terminal. (Also found here: \033 is more reliable than \e and since I'm now using octal codes anyway, I might as well use one more.)
There seems to be quite the dearth of documentation on this; the best I could find was in perl's documentation for Term::ReadLine::Gnu, which says:
PROMPT may include some escape sequences. Use RL_PROMPT_START_IGNORE to begin a sequence of non-printing characters, and RL_PROMPT_END_IGNORE to end the sequence.

Related

What does "\e" do? What does "\e[1;1H\e[2J" do?

I was looking for some alternative to system("cls") that works on MacOS and I found this:
printf("\e[1;1H\e[2J");
However I do not know what this is doing.
Post where I found this
\e is escape and what that printf() line is telling the terminal to move the cursor to line 1 column 1 (\e[1;1H) and to move all the text currently in the terminal to the scrollback buffer (\e[2J).
These are ANSI escape codes and here's some resources: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
https://bluesock.org/~willkg/dev/ansi.html
https://en.wikipedia.org/wiki/ANSI_escape_code (suggested by tadman)
Edit: I also recommend the use of \e[H\e[2J\e[3J as that is what cls/clear prints. This tells the terminal to move the cursor to the top left corner (\e[H), clear the screen (\e[2J), and clear the scrollback buffer (\e[3J).
if your language does't support \e you can instead replace it with \x1b or \033
Hexadecimal
printf("\x1b[1;1H\x1b[2J");
Decimal
printf("\033[1;1H\033[2J");

Exercise of indentation of C code

I am trying to improve the indentation of this C code. I am not picky as to the exact rules used for indentation, I just care that it looks like something more or less standard!
Interestingly, the code looks good on my browser but once downloaded it is clear that the indentation is caused by a mixture of "tabs" and "spaces" and it looks ugly on both SublimeText2 and TextWrangler.
I tried
indent file.c
but it returns a long series of error that seem related to the presence counterslash followed by newline ("\\n" or "\\\n" if the slash needs to be escaped) within strings. So I tried to remove those with
tr "\\\n" " " < file.c > newfile.c
but I did not quite reach my goal.
When I download the file, it has CRLF (DOS or Windows style) line endings. When you run that through Unix indent, it doesn't like the backslashes followed by CR instead of NL (aka LF). If you replace the CRLF line endings, it should be formattable by indent.
The problem is that indent expects the backslashes to be followed by a newline, but CR is not a newline.
If you have a dos2unix or dtou command, use that. If not, use tr:
tr -d '\015' < sfs_code.c > x31; mv x31 sfs_code.c
You can also improve things by replacing the tabs with 8 spaces. Again, there may be other tools that can do the job, but the classic tool is pr:
pr -e8 -l1 -t sfs_code.c > x31; mv x31 sfs_code.c
The backslash newline sequences for splitting strings over multiple lines is very 1980s-style coding. Since the C89/C90 standard introduced string concatenation, it isn't necessary (though it is still recognized as legitimate C).
At 6781 lines, that's a pretty big file. It includes a number of other source files (.c extensions) and some headers. I poked at it with uncrustify which had no problem with it; after removing the CR characters, indent was fine with it too.
(I used my own ule — Uniform Line Endings — program to do the conversion. It's one of a lot of different possible techniques.)
Note that your tr command failed because it mapped both backslash and newline to blank (and left the CR characters unchanged), which is not what you want. The tr command maps single characters; it is not appropriate for mapping combinations of characters as you wanted and needed.

How is erasing output in terminal implemented in C?

Some applications running in terminal can erase their outputs. e.g.
when it tells you to wait, it will show a sequence of dots alternating between different lengths.
How is erasing output in terminal implemented in C? Is it done by reverse line feed?
Can a program only erase the previous characters in the current line, not the characters in the previous line in stdout?
Thanks.
It depends on the terminal.
The COMSPEC shell on Windows (often called the DOS prompt or command.com) exposes an API in C to control the cursor. I haven't done any Windows programming so I can't tell you much about it.
Most other terminals (especially on unixen) emulate protocols that resemble the VT100 serial terminal (the VT100 terminal was a physical device, a monitor and keyboard, that you attached to a modem or serial port to communicate with a server).
On VT100 terminals, carriage return and line feed are separate commands, both one byte. The carriage return command sets the cursor to the beginning of the line. The line feed command moves the cursor down a line (but doesn't bring the cursor to the beginning of the line by itself). Most shells on unixen automatically insert a carriage return after a line feed but almost none inserts a line feed after a carriage return.
With this knowledge, the simplest implementation is to simply output a carriage return and reprint the entire line:
printf("\rprogress: %d percent ", x);
Note the extra spaces at the end of the line. Printing "\r" doesn't erase the line so reprinting over the old line may end up leaving some of the old string on screen. The extra spaces is used to try and erase the remainder of the old line.
If you googled "VT100 escape secquence", you'll find commands that will allow you to do things like erase the current line, change color of text, goto a specific row/column on screen etc. The most popular use of VT100 sequences is to output coloured text. But you can also do other things with them.
The next simplest implementation is to cleanly delete the line and reprint it:
printf("\033[2K\rprogress: %d percent", x);
The \033[2K is the escape sequence to delete the current line (ESC[2K).
From here you can get more creative if you want. You can use the cursor save/restore command with the erase until end of line command to only erase the part you want to update (instead of the entire line). You can use the goto commands to put the cursor in a specific location on screen to update text there etc.
It should be noted that the more advanced stuff such as VT102 sequences or some of the full ANSI escape sequences are generally not portable accross terminals (by terminals I don't mean the shell, I mean the terminals: rxvt, xterm, linux terminal, hyperterminal(on windows) etc).
If you want portability (and/or sane API) you should use the curses or ncurses libraries.
If you wanted to know how it's done, then that's how it's done. It's just printing specially formatted strings to screen (except for the COMSPEC shell). Kind of like HTML but old-school.

C command-line parser for handling comments

I have a tool that takes input and makes output:
$ tool input > output
I'd like to add an option that is a long string — say, a "comment" option. This comment text is an argument to the option and is a sentence enclosed in forward tick marks:
$ tool --comment='I am commenting on the use of comments' input > output_plus_comment
This is different from the usual --foo=bar key-value pairing, where foo is the option name and bar is a one-word value (e.g., true, red, ...).
Is there a good command-line parser library for C that handles this particular case?
Tokenizing the command line into arguments for your program is the responsibility of your shell, not yours. So there's nothing for you to do.
Just put quotation marks around strings that contain spaces, or escape spaces with backslashes on your command line, and your --foo value can contain as many spaces as you like.

VT100 escape sequence to remove already printed newline?

Is there a combination of VT100 escape sequences that will allow my C program to print something like:
Waiting......
to a console, in such a way that the dots appear one by one? Essentially, I want a command that will let me insert an extra '.' in front of a newline that has already been sent.
I'm looking for a quick one-liner for linux; it does not have to be portable. ncurses is overkill for this.
you can add ESC[K (clear to end of line) to ESC[A (up one line), and print your new line text
an example using Python:
import random, time
for _ in range(100):
print('\x1b[A\x1b[Kthis will print each line cleanly: %d' %(random.randint(0, 100000)))
time.sleep(0.1)
if you really want to be neat about things, use ESC7 (save cursor) and ESC8 (restore cursor)
then, you write your line and at the end of it you use ESC7. at the beginning of the print statement, you use ESC8. note, unless you turn off automatic newlines, this will not work at the bottom of your tty. it will work on all lines but the bottom.
import random, time
print('this will print each dot cleanly: \x1b7')
for _ in range(10):
print('\x1b8.\x1b7')
print('print more foo: %d' %_)
time.sleep(0.1)
for shell scripting (bash), you would use printf "..." without a \n, or echo -n
An easy way to do this is to use the escape sequence
"\x1b[A"
to move the cursor up one line. Then, re-print the "Waiting..." message, with one more dot than the last time.

Resources