6

I have a bash script that prints a nice big colorful table, using escape codes for foreground and background generated from tput. My curses application needs to call this bash script and put the output on the screen.

When I try to do that, curses explodes with a stacktrace ending at:

File "./dostuff.py", line 38, in print_art
    screen.addstr(y, x_start, line)
TypeError: must be str, not bytes

Where "line" is something like:

'\x1b[44m\x1b[30mcard major minor revision runs updated\x1b(B\x1b[m\x1b(B\x1b[m\n'

Is there any way to make curses interpret these color codes? Any processing I could do to the string with the color codes to make curses display it? Or do I have to basically remove color from the bash script and then reimplement the colorization in python?

EDIT:

The command that gets the bash output is something like:

print_art(subprocess.Popen(["./automount", "backup", "list"], stdout=subprocess.PIPE).communicate()[0])

By calling decode() on the byte string, I can get curses to print the strings, albeit with literal escape sequences. Unless I hear from anyone else, I'm just going to parse these literal escape sequences manually and convert to use curses color methods.

ACK_stoverflow
  • 3,148
  • 4
  • 24
  • 32
  • 2
    As the error message says, It is not about color codes. `subprocess` uses `bytes` by default. Usually you could pass `universal_newlines=True` to get output from the subprocess as a string but in this case you should probably find `curses` API that accepts `bytes` directly. – jfs Jun 04 '15 at 23:22
  • @J.F.Sebastian, there doesn't appear to be any curses API that accepts bytes. However, I did make some progress and now have a (somewhat crappy) direction to go in, unless anyone else has another idea. See my edit. – ACK_stoverflow Jun 04 '15 at 23:31
  • 2
    there is no need to decode manually. As I said, to get `str`, enable text mode: `text = subprocess.check_output(["./automount", "backup", "list"], universal_newlines=True)`. Also, `addstr()` accepts `bytes` on my machine on Python 3.4 (no type error). It won't help with color codes that are on a lower abstraction level than `curses` (you could something like `colorama` on Windows if there is no way to pass the codes transparently). – jfs Jun 05 '15 at 00:19
  • @J.F.Sebastian I always find `.decode("utf-8")` shorter and more transparent than `universal_newlines=True`. YMMV. – 4ae1e1 Jun 05 '15 at 00:44
  • Yes, the name of the parameter is unfortunate (the way the meaning of the `universal_newlines` is overloaded in Python 3). But `.decode("utc-8")` is different. `universal_newlines=True`, has the same effect as: `io.TextIOWrapper(io.BytesIO(data)).read()` – jfs Jun 05 '15 at 01:14
  • @J.F.Sebastian I do need to decode manually - if I use `decode()` or `universal_newlines`, curses will print the shell escape codes literally, so I have to strip these from the strings and apply the proper curses color arguments to the `addstr()` calls. – ACK_stoverflow Jun 05 '15 at 15:18
  • as I said twice already: the color codes issue is orthogonal to the text (Unicode) vs. binary (bytes) issue (all color codes are ascii and therefore the shouldn't be any problems with encoding/decoding them in any ascii-based locale). For example, `addstr()` on my machine accepts both `str` and `bytes` types but it does not help (as expected) with handling of the ansi color codes. Again, look at `colorama` package and see how it translates ansi codes to Windows API calls: it may help you to estimate the amount of work required if you want to translate the codes into curses API calls. – jfs Jun 05 '15 at 18:21
  • See also the [answer](https://stackoverflow.com/a/908440/500942) to "How to write binary data in stdout in python 3?" – jalanb Nov 09 '17 at 21:45
  • Is this the same thing? http://www.linuxhowtos.org/Tips%20and%20Tricks/ansi_escape_sequences.htm – JGFMK Feb 22 '19 at 10:47
  • @JGFMK Yes, the output I was asking about were ANSI color escape codes. – ACK_stoverflow Mar 06 '19 at 02:50
  • i think it's not about python or ncurse, but you terminal, some terminal does not recognize such color indicator ? consider state your system and terminal emulator – Jack Wu Dec 05 '19 at 02:14
  • Read this: http://mywiki.wooledge.org/BashFAQ/037 – Nic3500 Sep 15 '20 at 01:26
  • Might be interesting: https://stackoverflow.com/questions/18368796/how-to-create-scrollable-console-application-that-support-ansi-escape-code-seque – pro-gramer Feb 28 '21 at 09:38
  • This answers your question: [ANSI colors in C and ncurses](https://stackoverflow.com/questions/4373690/ansi-colors-in-c-and-ncurses). – Thomas Dickey May 29 '22 at 18:59
  • 1
    FWIW `universal_newlines=True` is now (since Python 3.7 IIRC) a legacy keyword argument which is still supported, but the preferred version is simply `text=True` which is both shorter and more descriptive. – tripleee May 30 '22 at 07:52

1 Answers1

0

I tried copying code from network:

#!/bin/python
import curses
screen = curses.initscr()
screen.addstr(0, 0, "This string gets printed at position (0, 0)")
screen.addstr(3, 1, "Try Russian text:
")  # Python 3 required for unicode
screen.addstr(4, 4, "X")
line='\x1b[44m\x1b[30mcard           major  minor  revision  runs  updated\x1b(B\x1b[m\x1b(B\x1b[m\n'
y=10
x_start=5
print( type(x_start) )
print( type(y) )
screen.addstr( y, x_start, line)
screen.addch(5, 5, "Y")
screen.refresh()
curses.napms(3000)
curses.endwin()

Sample output:

$ ./dostuff.py
<class 'int'>
             <class 'int'>
This string gets printed at position (0, 0)


 Try Russian text: Привет
    X
     Y




     ^[[44m^[[30mcard           major  minor  revision  runs  updated^[(B^[[m^[(B^[[m

After few seconds updated output:

$ ./dostuff.py
<class 'int'>
             <class 'int'>

We can press Ctrl s and Ctrl q to stop current output at current terminal. Hence check the type of following variables:

y
x_start
line

if any mismatch, start using something like:

str(y)
or
str(x_start)
or
str(line)

to handle those exception.