Repository: ipython/ipython

Issue:
Terminal title should be disabled when using IPython in a pipeline
This came up recently here: https://github.com/sharkdp/bat/issues/403

Here's an example of how IPython prints control characters even when used in a pipeline:

```
/$ ipython -c "import os; print(os.isatty(1))"
True

/$ ipython -c "import os; print(os.isatty(1))" | gcat -A
^[]0;IPython: /^GFalse$
```

It would be good if IPython checked `isatty` (as shown above) to not add terminal title control characters if `ipython` is being used in a pipeline.

To be clear, this isn't just a convenience feature, as it can actually cause unexpected behavior when used in a pipeline. For example, one would expect that a `grep` for the letter `o` in the output of "False" above would return false:

```
/$ ipython -c "import os; print(os.isatty(1))" | grep o
01;31mon: /False
```

But it returns a match! Running the same command using stock Python correctly returns nothing (and 1 return code):

```
/$ python3 -c "import os; print(os.isatty(1))" | grep o
/$:1
```


Discussion:
That seem reasonable to me. Do you want to send a PR ? The code setting the terminal title is likely in `IPython/terminal/interactiveshell.py`.
> That seem reasonable to me.

Great!

> Do you want to send a PR ?

Ok will do! FYI I won't be able to get to it before the weekend.
> Ok will do! FYI I won't be able to get to it before the weekend.

No problem, large majority on the work in this repo is done on Nights and Week-end. 

Resolving patch:
diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py
--- a/IPython/terminal/interactiveshell.py
+++ b/IPython/terminal/interactiveshell.py
@@ -405,14 +405,14 @@ def _autosuggestions_provider_changed(self, change):
     @observe('term_title')
     def init_term_title(self, change=None):
         # Enable or disable the terminal title.
-        if self.term_title:
+        if self.term_title and _is_tty:
             toggle_set_term_title(True)
             set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
         else:
             toggle_set_term_title(False)
 
     def restore_term_title(self):
-        if self.term_title:
+        if self.term_title and _is_tty:
             restore_term_title()
 
     def init_display_formatter(self):
diff --git a/IPython/terminal/ipapp.py b/IPython/terminal/ipapp.py
--- a/IPython/terminal/ipapp.py
+++ b/IPython/terminal/ipapp.py
@@ -318,6 +318,7 @@ def start(self):
             self.shell.mainloop()
         else:
             self.log.debug("IPython not interactive...")
+            self.shell.restore_term_title()
             if not self.shell.last_execution_succeeded:
                 sys.exit(1)
 
diff --git a/IPython/utils/terminal.py b/IPython/utils/terminal.py
--- a/IPython/utils/terminal.py
+++ b/IPython/utils/terminal.py
@@ -62,15 +62,27 @@ def _restore_term_title():
     pass
 
 
+_xterm_term_title_saved = False
+
+
 def _set_term_title_xterm(title):
     """ Change virtual terminal title in xterm-workalikes """
-    # save the current title to the xterm "stack"
-    sys.stdout.write('\033[22;0t') 
+    global _xterm_term_title_saved
+    # Only save the title the first time we set, otherwise restore will only
+    # go back one title (probably undoing a %cd title change).
+    if not _xterm_term_title_saved:
+        # save the current title to the xterm "stack"
+        sys.stdout.write("\033[22;0t")
+        _xterm_term_title_saved = True
     sys.stdout.write('\033]0;%s\007' % title)
 
 
 def _restore_term_title_xterm():
+    # Make sure the restore has at least one accompanying set.
+    global _xterm_term_title_saved
+    assert _xterm_term_title_saved
     sys.stdout.write('\033[23;0t') 
+    _xterm_term_title_saved = False
 
 
 if os.name == 'posix':


Test patch: