5

Here's a use-case to clarify my question. Say I have a calendar program that is set to run in ~/.bashrc, and it ensures the streaming output overwrites the same block of lines.

Is it possible to have the streaming output display in the terminal from the background process without clobbering new input? I already looked at Displaying stdout of a background process in specific location of the terminal, but the asker requires outputting new lines at termination, which I don't need to do.

Here's a screenshot of the program output, which currently runs in the foreground and terminates after outputting the formatted text once: Terminal after program output

I just want that formatted text to continually replace itself while allowing foreground processes to function as normal. A solution in Bash, C, and / or C++ using something like zsh or ANSI escape sequences would be perfect for me.

For reference, here's the current C code I'm using, but if it's easier for you, you could just formulate a solution that uses cal instead:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

const char months[12][10] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};
const char weekDays[7][10] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

void printCalendar(void);
int getWeekDay(int, int, int, int, int);
int getMaxDay(int, int);
void getDate(int *, int *, int *, int *, int *, int *, int *);
void formatTime(char *, int, int, int);

int main(void) {
  printCalendar();

  return 0;
}

void printCalendar(void) {
  int second, minute, hour, day, month, year, weekDay, maxDay, col, x = 0, i;
  char str[12];

  getDate(&second, &minute, &hour, &day, &month, &year, &weekDay);
  formatTime(str, hour, minute, second);
  maxDay = getMaxDay(month, year);

  printf("\e[3J");

  printf("%s %s\n", weekDays[weekDay], str);
  printf("%s %d, %d\n\n ", months[month], day, year);

  printf("Sun Mon Tue Wed Thu Fri Sat\n ");

  for (i = 1; i <= maxDay; i++) {
    col = getWeekDay(i, month, year, day, weekDay);

    if (x > col) {
      x = 0;
      printf("\n ");
    }

    while (x < col) {
      x++;
      printf("    ");
    }

    x++;

    if (i == day) {
      if (i < 10) {
        printf(" ");
      }

      printf(" \e[7m%d\e[0m ", i);
    } else {
      printf("%3d ", i);
    }
  }

  printf("\n\n");
}

int getWeekDay(int day, int month, int year, int rmday, int rwday) {
  return (day - rmday + rwday + 35) % 7;
}

int getMaxDay(int month, int year) {
  switch (month) {
    case 3: // April
    case 5: // June
    case 8: // September
    case 10:// November
      return 30;
    case 1: // February
      if ((year % 100 == 0 && year % 400 != 0) || year % 4 != 0) {
        return 28; // Not leap year
      }

      return 29; // Leap year
    default:
      return 31; // Remaining months
  }
}

void getDate(int *second, int *minute, int *hour, int *day, int *month, int *year, int *weekDay) {
  time_t now;
  struct tm *date;

  time(&now);
  date = localtime(&now);

  *second = (date -> tm_sec);
  *minute = (date -> tm_min);
  *hour = (date -> tm_hour);
  *day = (date -> tm_mday);
  *month = (date -> tm_mon);
  *year = (date -> tm_year) + 1900;
  *weekDay = (date -> tm_wday);
}

void formatTime(char *str, int hour, int minute, int second) {
  sprintf(str, "%02d:%02d:%02d %s", (hour % 12) ? (hour % 12) : 12, minute, second, hour / 12 ? "PM" : "AM");
  str[11] = '\0';
}

And the code in ~/.bashrc is just:

clear && ~/Documents/C/Calendar/calendar

Thanks for any help

  • 5
    Thoughts: screen (or probably tmux) with a split, where the upper section is just large enough for the command output. If you are okay with it updating only after pressing Return, then $PROMPT_COMMAND is sufficient – Fox May 5 '17 at 21:57
7

I recommend GNU screen for this. First, start up a new screen instance:

$ screen

Then make a split with Ctrl+A Shift+S. You can resize the top portion with the resize command. I found a height of 9 to be reasonable for cal:

Ctrl+A :resize 9

Then use any command that constantly produces output. I don't use watch or even have it on many systems, but

while true; do cal; sleep 3; done

works just as well.

Then Ctrl+A Tab moves you to the other (bottom) part of the split. Finally, Ctrl+A C opens a new shell in which you can run commands without interference from the other portion of the split.


If you want this to occur automatically, you can use .screenrc:

screen /bin/sh -c 'while true; do cal; sleep 3; done'
split
resize 9
focus
screen

See screen(1) for a full description of commands, and possible inspiration for alternative configurations.

  • I love this answer! Is there a way to do the keystrokes programmatically though? This would be perfect – Patrick Roberts May 5 '17 at 23:00
  • @PatrickRoberts I've edited my answer to accommodate that. I fully expect someone to recommend something else though – Fox May 5 '17 at 23:13
  • Much appreciated, thank you for your quick response! – Patrick Roberts May 5 '17 at 23:15
  • 1
    I ended up adding if ! screen -list | grep -q "on"; then clear && screen; fi to the ~/.bashrc and statup_message off; defshell -bash to the ~/.screenrc so the bottom split screen would have the normal bash interface, I wish I could upvote this again! – Patrick Roberts May 6 '17 at 0:08
  • 1
    @Fox if someone does, just send them away – ivanivan May 6 '17 at 0:51
0

Basically, yes. It is possible to have a background running application that outputs something on stdout:

ping 8.8.8.8 &> /dev/stdout &

So:

  • &> send stdout and stderr to stdout,
  • & at the end makes ping working in background.

But, as far as I understand your use-case properly, the better idea will be to have watch [1] application in foreground. It runs periodically a command that is specified as an argument and presents its execution results.

  • The first portion of this will interfere with (hinder the viewing of) foreground tasks, and the second portion (watch) prevents any other command from being run – Fox May 5 '17 at 22:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.