Repository: ipython/ipython

Issue:
Suggest %pip or %conda when user types !pip or !conda
To guide users towards doing things right, could we suggest they use the magics instead of the shell invocations? Possibly with a short explanation why. Was also mentioned in <https://github.com/ipython/ipython/pull/11524#issuecomment-444875270>.

Discussion:

Patch:
diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py
index 22b531ec1..47db3fe2b 100644
--- a/IPython/core/interactiveshell.py
+++ b/IPython/core/interactiveshell.py
@@ -2521,6 +2521,23 @@ def system_raw(self, cmd):
           Command to execute.
         """
         cmd = self.var_expand(cmd, depth=1)
+        # warn if there is an IPython magic alternative.
+        main_cmd = cmd.split()[0]
+        has_magic_alternatives = ("pip", "conda", "cd", "ls")
+
+        # had to check if the command was an alias expanded because of `ls`
+        is_alias_expanded = self.alias_manager.is_alias(main_cmd) and (
+            self.alias_manager.retrieve_alias(main_cmd).strip() == cmd.strip()
+        )
+
+        if main_cmd in has_magic_alternatives and not is_alias_expanded:
+            warnings.warn(
+                (
+                    "You executed the system command !{0} which may not work "
+                    "as expected. Try the IPython magic %{0} instead."
+                ).format(main_cmd)
+            )
+
         # protect os.system from UNC paths on Windows, which it can't handle:
         if sys.platform == 'win32':
             from IPython.utils._process_win32 import AvoidUNCPath


Test patch:
diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py
index 84bea9cd4..9092ce5b4 100644
--- a/IPython/core/tests/test_interactiveshell.py
+++ b/IPython/core/tests/test_interactiveshell.py
@@ -600,9 +600,16 @@ def test_control_c(self, *mocks):
         try:
             self.system("sleep 1 # wont happen")
         except KeyboardInterrupt:
-            self.fail("system call should intercept "
-                      "keyboard interrupt from subprocess.call")
-        self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGINT)
+            self.fail(
+                "system call should intercept "
+                "keyboard interrupt from subprocess.call"
+            )
+        self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
+
+    def test_magic_warnings(self):
+        for magic_cmd in ("ls", "pip", "conda", "cd"):
+            with self.assertWarnsRegex(Warning, "You executed the system command"):
+                ip.system_raw(magic_cmd)
 
 # TODO: Exit codes are currently ignored on Windows.
 class TestSystemPipedExitCode(ExitCodeChecks):