27

I notice that with the TERM environment variable set to either xterm or xterm-256color that Mac OS X’s Terminal.app utility respects most ANSI escape codes, as least when those escape codes pertain to changing text color.

For example:

echo -e "\033[0;31mERROR:\033[0m It worked"

Produces:

Screenshot

However, I’m more interested in the cursor location manipulation functionality afforded by ANSI escape codes. Unfortunately, that type of code doesn’t seem to work too well in Terminal.app, from what I’ve been able to gather. For example, what I want to do is something like this:

echo -e "\033[sHello world\033[uG'day"

ESC[s Saves the current cursor position, whereas ESC[u restores the last saved position. As a result of running the script above, I’d expect the five characters in “G'day” to overwrite the five characters of “Hello” after the cursor has been repositioned, producing the following:

G'day world

Indeed, this is exactly what I get with iTerm2.app, ConEmu for Windows (running either MinGW or MSYS Git’s copy of bash.exe), etc. What I see in Terminal.app, however, is this:

Hello worldG'day

Is there a reason for this, aside form Terminal.app simply lacking support for these codes? Is there a way to enable this functionality? Is there a chance I have something misconfigured? My TERM setting? Something else?

I’ve been searching everywhere, but haven’t found anything relevant to Terminal.app, specifically. I find it odd that it would support colored text via ANSI escape codes, but not cursor repositioning through the exact same technology. That seems like a fairly arbitrary subset of a fairly well-defined standard. That’s what makes me think I’ve got something misconfigured, rather than that Terminal.app is the one to blame… but, I suppose it’s possible that it simply can’t be done. (Probably one of the reasons iTerm2 exists in the first place?)

If anyone could shed some light on this situation, it’d be much appreciated!

UPDATE

So, I've done a bit more reading and experimenting, and discovered the following oddity:

After looking into n.m.'s answer below, I decided to write out the bytes that were being returned by tput to a file to see how they differed from the regular ANSI instruction.

$ echo "$(tput sc)Hello world$(tput rc)G'day" > out.bin
$ cat -e out.bin
^[7Hello world^[8G'day$

It appears that everything works as expected if I send it the sequences ESC 7 and ESC 8, but NOT if I send it ESC [s and ESC [u, which as I understand things is the more typical representation of the ANSI SCP and RCP codes (Save Cursor Position and Restore Cursor Position, respectively). Since putting ASCII decimal characters 7 or 8 next to an escaped octal byte representation is impossible (\0337 != ESC), environment variables can be used instead to avoid relying on tput:

$ esc=$'\033'
$ csi="${esc}["
$ echo "${csi}0;31mERROR:${csi}0m It worked."
ERROR: It worked.  # Color works, as before
$ echo "${csi}sHello world${csi}uG'day"
Hello worldG'day   # No dice
$ echo "${esc}7Hello world${esc}8G'day"
G'day world        # Success

I'm not sure why this is. If ESC 7 and ESC 8 are some kind of proprietary or custom code for ANSI SCP and RCP that has the potential to vary from terminal implementation to terminal implementation, it would explain to me why tput was created in the first place.

Unfortunately, I am unable to use tput for what I am currently working on, as I am not working exclusively in a bash environment. I am more curious as to how the raw bytes are interpreted from terminal to terminal, and more specifically, whether or not there's a way to get Terminal.app to respect the same ANSI escape codes that all of the other terminal emulators I've tried seem to have no problem with. Is that possible? At this point, I'm beginning to think it simply might not be, which is fine, but it would be nice to know for sure, and possibly also to learn the reason why.

dimo414
  • 47,227
  • 18
  • 148
  • 244
Mark G.
  • 2,849
  • 2
  • 15
  • 15
  • 1
    I'm not at my iMac, but it may be worth having a try using `printf` rather than `echo` and/or `/usr/bin/echo` rather than the shell's built-in `echo`. – Mark Setchell Sep 16 '14 at 22:28
  • 1
    I didn't have a `/usr/bin/echo`, but I did have a `/bin/echo`, and neither that nor `printf` worked with `"\033[sHello world\033[uG'day"`, unfortunately... However, both `printf` and `echo` worked when I sent `ESC` `7` and `ESC` `8` rather than `ESC` `[s` and `ESC` `[u` for the SCP and RCP codes, respectively. I updated the question in light of this behavior. – Mark G. Sep 18 '14 at 04:46
  • 1
    See [this answer](https://stackoverflow.com/questions/28986776/ansi-escape-sequence-save-restore-cursor-position-support) for why `ESC 7` and `ESC 8` work. – Clay Ellis Sep 21 '20 at 02:26

1 Answers1

27

Don't use ANSI codes. Use proper terminfo-based techniques. Xterm-based terminals are not specified to support all ANSI codes. Some do for compatibility, some don't.

Save cursor position sequence is given by the tput sc command and the restore cursor position is tput rc.

echo -e "$(tput sc)Hello world$(tput rc)G'day"

should work on any terminal that supports these sequences.

To see a readable representation of the supported sequences, use the infocmp command. The output might be quite long. If you are interested in sc and rc:

infocmp | grep --color ' [sr]c='

Disclaimer: tested on my Linux machine, don't have a Mac nearby.

UPDATE

Terminal.app is modeled after xterm and xterm is modelled after the VT100 terminal. VT100 did not implement CSI u and CSI s sequences, but used DEC private ESC 7 and ESC 8 sequences (source). Later VT models supported both CSI s/u and ESC 7/8, under different name and with slightly different functionality (source).

ECMA 48 doesn't seem to specify any save/restore cursor position sequences (source (PDF)), or I could not find them there. I don't know where CSU s/u come from. Their name in VT510 documentation suggests they are somehow connected with SCO. This source suggests they are in fact private sequences with no standard meaning. SUN terminals use SCI s to do a reset. It is probably an error to brand these two sequences ANSI.

Modern versions of xterm and other X11 terminal programs (konsole, rxvt...) do support both ESC 7/8 and CSI s/u, but the terminfo database only advertises ESC 7/8. Terminal.app apparently only supports ESC 7/8.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • 1
    Thanks for the info! I was not aware of the tput or infocmp commands, that's good stuff to know. Unfortunately, I'm not actually working in a pure bash environment (though it is one of my use cases) so I don't think I can accept this as the definitive answer. At the moment, I'm also working with Python and C++, and am trying to figure out exactly what bytes need to be written out in order to maximize compatibility with multiple terminals. So far, all of them seem to be behaving as I expect, except for Terminal.app, which is unfortunate... hence my wondering if I have something misconfigured. – Mark G. Sep 18 '14 at 03:35
  • 2
    Please ask a question that you need answered. If you want to manipulate the terminal in C and Python, ask about manipulating the terminal in C and Python. This question asks no such thing, so it's not mentioned in the answer. It specifically mentions bash and is tagged accordingly, so that's what gets covered. – n. m. could be an AI Sep 18 '14 at 03:39
  • 1
    The question is, is it possible for Terminal.app to respect typical ANSI escape codes, (and if so, how,) or does it only support an arbitrary subset? The fact that the SCP and RCP codes don't work without additional utilities (like tput for bash or curses/blessings/colorama/etc. for Python/C++) has me a bit concerned about what other standard codes it may not support. It would be good to know how to enable that functionality, or, if that's not possible, it would be good to know which codes I should just avoid using. Sorry if I wasn't clear in the question, I added an update that might clarify. – Mark G. Sep 18 '14 at 04:16
  • 1
    You are doing it the wrong way. The answer is clear, if your intention is to support several terminals use `tput` which is precisely the right tool for the job. – Diego Torres Milano Sep 18 '14 at 04:18
  • 2
    `tput` may be the right tool for use in bash scripts and the like, but outside of making a subprocess call, I don't see how it would be ideal for use by another program that wants to print to the terminal and make proper use of ANSI codes while doing so. The question pertains more to Terminal.app's expected behavior than how to do this in bash specifically, so I went ahead and removed the bash tag. – Mark G. Sep 18 '14 at 04:32
  • 3
    The Unix standard is terminfo, not ANSI codes. Every self-respecting program that manipulates terminals uses terminfo. If you need to know how to do it in C, **ask how to do it in C**. – n. m. could be an AI Sep 18 '14 at 04:57
  • 1
    I have updated the answer with some info about control sequences. – n. m. could be an AI Sep 18 '14 at 06:55
  • 3
    I'm not concerned with the practicals of how it's best done in specific languages just yet. With this question, I was trying to understand the status of the standards involved, and how the behavior of Terminal.app either met or deviated from those standards. Your update provided a lot of useful info in that regard, and is exactly the kind of info I was looking for. I'm satisfied now knowing a bit of the history, that SCP and RCP probably came about later as a part of some separate spec, hence why they don't always seem to work. Thanks for helping me research this, I'll flag this as the answer. – Mark G. Sep 20 '14 at 21:44
  • 1
    There are use-cases where you actually want an ansi-terminal. For instance, try running nc dura-bbs.net 6359 It won't work if the terminal doesn't support ansi. The default terminal.app in OS X doesn't work. – Albert Veli Apr 28 '19 at 09:39
  • @AlbertVeli if you want to *run* existing sofrware, you need to supply hardware it supports. If you want to *write* software, you decide what to support. – n. m. could be an AI Apr 28 '19 at 09:49
  • @n.m. Yes, I know. I just wanted to point out a use-case where you actually want the terminal to support ansi sequences. The use-case is; connecting to BBS systems over the internet. Some BBS:es work using only ascii but some require ansi. – Albert Veli Apr 28 '19 at 09:54