diff --git a/.github/workflows/linux_unit_tests.yaml b/.github/workflows/linux_unit_tests.yaml
index cf5d8660c2499428ae2677a9fbe3913c1154cfb3..287aee1a7553862abe7e883c597ba175da70966a 100644
--- a/.github/workflows/linux_unit_tests.yaml
+++ b/.github/workflows/linux_unit_tests.yaml
@@ -26,9 +26,12 @@ jobs:
     - name: Install System packages
       run: |
           sudo apt-get -y update
-          sudo apt-get install -y coreutils gfortran graphviz gnupg2 mercurial ninja-build patchelf
+          # Needed for unit tests
+          sudo apt-get install -y coreutils gfortran graphviz gnupg2 mercurial
+          sudo apt-get install -y ninja-build patchelf
           # Needed for kcov
-          sudo apt-get -y install cmake binutils-dev libcurl4-openssl-dev zlib1g-dev libdw-dev libiberty-dev
+          sudo apt-get -y install cmake binutils-dev libcurl4-openssl-dev
+          sudo apt-get -y install zlib1g-dev libdw-dev libiberty-dev
     - name: Install Python packages
       run: |
           pip install --upgrade pip six setuptools codecov coverage
@@ -69,9 +72,11 @@ jobs:
     - name: Install System packages
       run: |
           sudo apt-get -y update
-          sudo apt-get install -y coreutils gfortran gnupg2 mercurial ninja-build patchelf zsh fish
+          # Needed for shell tests
+          sudo apt-get install -y coreutils csh zsh tcsh fish dash bash
           # Needed for kcov
-          sudo apt-get -y install cmake binutils-dev libcurl4-openssl-dev zlib1g-dev libdw-dev libiberty-dev
+          sudo apt-get -y install cmake binutils-dev libcurl4-openssl-dev
+          sudo apt-get -y install zlib1g-dev libdw-dev libiberty-dev
     - name: Install Python packages
       run: |
           pip install --upgrade pip six setuptools codecov coverage
diff --git a/.github/workflows/macos_unit_tests.yaml b/.github/workflows/macos_unit_tests.yaml
index 1e60f769189ac17429993c1ff0e172e75d5989c0..895a4cffe7dd2a094f59da86c1307e516c69bfdd 100644
--- a/.github/workflows/macos_unit_tests.yaml
+++ b/.github/workflows/macos_unit_tests.yaml
@@ -26,7 +26,7 @@ jobs:
           pip install --upgrade flake8 pep8-naming
     - name: Setup Homebrew packages
       run: |
-        brew install gcc gnupg2 dash kcov
+        brew install dash fish gcc gnupg2 kcov
     - name: Run unit tests
       run: |
         git --version
diff --git a/lib/spack/spack/cmd/cd.py b/lib/spack/spack/cmd/cd.py
index a810e36ef3167cb6512f1744edf51557fd59e60a..396f4f355224d010e2da7b37a98e0fe6ea7ce59e 100644
--- a/lib/spack/spack/cmd/cd.py
+++ b/lib/spack/spack/cmd/cd.py
@@ -3,8 +3,9 @@
 #
 # SPDX-License-Identifier: (Apache-2.0 OR MIT)
 
-from spack.cmd.common import print_module_placeholder_help
+import llnl.util.tty as tty
 
+import spack.cmd.common
 import spack.cmd.location
 
 description = "cd to spack directories in the shell"
@@ -20,4 +21,8 @@ def setup_parser(subparser):
 
 
 def cd(parser, args):
-    print_module_placeholder_help()
+    lines = [
+        "`spack cd` requires spack's shell support.",
+        "",
+    ] + spack.cmd.common.shell_init_instructions()
+    tty.msg(*lines)
diff --git a/lib/spack/spack/cmd/common/__init__.py b/lib/spack/spack/cmd/common/__init__.py
index 00804493cda596d78198d3bae33021d83752e6c1..d09fef2fe09d03aa5c643a19d904f6f3e089184a 100644
--- a/lib/spack/spack/cmd/common/__init__.py
+++ b/lib/spack/spack/cmd/common/__init__.py
@@ -3,35 +3,21 @@
 #
 # SPDX-License-Identifier: (Apache-2.0 OR MIT)
 
-
 import spack.paths
-from llnl.util import tty
-
-
-shell_init_instructions = [
-    "To initialize spack's shell commands:",
-    "",
-    "    # for bash and zsh",
-    "    . %s/setup-env.sh" % spack.paths.share_path,
-    "",
-    "    # for csh and tcsh",
-    "    setenv SPACK_ROOT %s" % spack.paths.prefix,
-    "    source %s/setup-env.csh" % spack.paths.share_path, ""
-]
+import llnl.util.tty.color as color
 
 
-def print_module_placeholder_help():
-    """
-    For use by commands to tell user how to activate shell support.
-    """
-    msg = [
-        "This command requires spack's shell integration.", ""
-    ] + shell_init_instructions + [
-        "This exposes a 'spack' shell function, which you can use like",
-        "    $ spack load package-foo", "",
-        "Running the Spack executable directly (for example, invoking",
-        "./bin/spack) will bypass the shell function and print this",
-        "placeholder message, even if you have sourced one of the above",
-        "shell integration scripts."
+def shell_init_instructions():
+    return [
+        "To set up shell support, run the command below for your shell.",
+        "",
+        color.colorize("@*c{For bash/zsh/sh:}"),
+        "  . %s/setup-env.sh" % spack.paths.share_path,
+        "",
+        color.colorize("@*c{For csh/tcsh:}"),
+        "  source %s/setup-env.csh" % spack.paths.share_path,
+        "",
+        color.colorize("@*c{For fish:}"),
+        "  source %s/setup-env.fish" % spack.paths.share_path,
+        "",
     ]
-    tty.msg(*msg)
diff --git a/lib/spack/spack/cmd/env.py b/lib/spack/spack/cmd/env.py
index 7bd805252801b5aa87cc5477bc84ea8bdb3e751f..7d9165e0fb4cf8f51134945bf775aca48add8fe7 100644
--- a/lib/spack/spack/cmd/env.py
+++ b/lib/spack/spack/cmd/env.py
@@ -85,14 +85,16 @@ def env_activate(args):
     env = args.activate_env
     if not args.shell:
         msg = [
-            "This command works best with Spack's shell support",
+            "`spack env activate` works best with spack's shell support.",
             ""
-        ] + spack.cmd.common.shell_init_instructions + [
-            'Or, if you want to use `spack env activate` without initializing',
-            'shell support, you can run one of these:',
+        ] + spack.cmd.common.shell_init_instructions() + [
+            'Or, if you want to use `spack env activate` without shell',
+            'support, you can run one of these:',
             '',
-            '    eval `spack env activate --sh %s`   # for bash/sh' % env,
-            '    eval `spack env activate --csh %s`  # for csh/tcsh' % env,
+            '    eval `spack env activate --sh   %s`  # bash/zsh/sh' % env,
+            '    eval `spack env activate --csh  %s`  # csh/tcsh' % env,
+            '    eval `spack env activate --fish %s`  # fish' % env,
+            ''
         ]
         tty.msg(*msg)
         return 1
@@ -142,14 +144,16 @@ def env_deactivate_setup_parser(subparser):
 def env_deactivate(args):
     if not args.shell:
         msg = [
-            "This command works best with Spack's shell support",
+            "`spack env deactivate` works best with spack's shell support.",
             ""
-        ] + spack.cmd.common.shell_init_instructions + [
-            'Or, if you want to use `spack env activate` without initializing',
-            'shell support, you can run one of these:',
+        ] + spack.cmd.common.shell_init_instructions() + [
+            'Or, if you want to use `spack env deactivate` without shell',
+            'support, you can run one of these:',
+            '',
+            '    eval `spack env deactivate --sh`   # bash/zsh/sh',
+            '    eval `spack env deactivate --csh`  # csh/tcsh',
+            '    eval `spack env deactivate --fish  # fish',
             '',
-            '    eval `spack env deactivate --sh`   # for bash/sh',
-            '    eval `spack env deactivate --csh`  # for csh/tcsh',
         ]
         tty.msg(*msg)
         return 1
diff --git a/lib/spack/spack/cmd/load.py b/lib/spack/spack/cmd/load.py
index 3938602882371fe9c9b44f774f54b3682466b4dd..9b3e94839446aa5c287eaa41798c174d1ec53cea 100644
--- a/lib/spack/spack/cmd/load.py
+++ b/lib/spack/spack/cmd/load.py
@@ -62,16 +62,18 @@ def load(parser, args):
              for spec in spack.cmd.parse_specs(args.specs)]
 
     if not args.shell:
-        specs_string = ' '.join(args.specs)
+        specs_str = ' '.join(args.specs) or "SPECS"
         msg = [
-            "This command works best with Spack's shell support",
+            "`spack load` works best with spack's shell support.",
             ""
-        ] + spack.cmd.common.shell_init_instructions + [
-            'Or, if you want to use `spack load` without initializing',
-            'shell support, you can run one of these:',
+        ] + spack.cmd.common.shell_init_instructions() + [
+            'Or, if you want to use `spack load` without shell',
+            'support, you can run one of these:',
+            '',
+            '    eval `spack load --sh   %s`  # bash/zsh/sh' % specs_str,
+            '    eval `spack load --csh  %s`  # csh/tcsh' % specs_str,
+            '    eval `spack load --fish %s`  # fish' % specs_str,
             '',
-            '    eval `spack load --sh %s`   # for bash/sh' % specs_string,
-            '    eval `spack load --csh %s`  # for csh/tcsh' % specs_string,
         ]
         tty.msg(*msg)
         return 1
diff --git a/lib/spack/spack/cmd/unload.py b/lib/spack/spack/cmd/unload.py
index cbee2fc76960fa56c5dac51f0360f17168f79d4c..2dbce8253649e619ed08814777d46e1286c4c7d1 100644
--- a/lib/spack/spack/cmd/unload.py
+++ b/lib/spack/spack/cmd/unload.py
@@ -53,15 +53,18 @@ def unload(parser, args):
         specs = spack.store.db.query(hashes=hashes)
 
     if not args.shell:
+        specs_str = ' '.join(args.specs) or "SPECS"
         msg = [
-            "This command works best with Spack's shell support",
+            "`spack unload` works best with spack's shell support.",
             ""
-        ] + spack.cmd.common.shell_init_instructions + [
-            'Or, if you want to use `spack unload` without initializing',
-            'shell support, you can run one of these:',
+        ] + spack.cmd.common.shell_init_instructions() + [
+            'Or, if you want to use `spack unload` without shell',
+            'support, you can run one of these:',
+            '',
+            '    eval `spack unload --sh   %s`  # bash/zsh/sh' % specs_str,
+            '    eval `spack unload --csh  %s`  # csh/tcsh' % specs_str,
+            '    eval `spack unload --fish %s`  # fish' % specs_str,
             '',
-            '    eval `spack unload --sh %s`   # for bash/sh' % args.specs,
-            '    eval `spack unload --csh %s`  # for csh/tcsh' % args.specs,
         ]
         tty.msg(*msg)
         return 1
diff --git a/lib/spack/spack/test/cmd/cd.py b/lib/spack/spack/test/cmd/cd.py
index e3900c0d8f330d84e2ae2d3663b168e760dc1ef5..eda6994aecf6b10306acb28cc0f0e4c8849c577c 100644
--- a/lib/spack/spack/test/cmd/cd.py
+++ b/lib/spack/spack/test/cmd/cd.py
@@ -14,4 +14,4 @@ def test_cd():
 
     out = cd()
 
-    assert "To initialize spack's shell commands:" in out
+    assert "To set up shell support" in out
diff --git a/lib/spack/spack/test/cmd/env.py b/lib/spack/spack/test/cmd/env.py
index 955693ca0f7a89deabe759cb90335a4bf16e52bc..a0a79138d754767aa255087e32660125b5db5dd6 100644
--- a/lib/spack/spack/test/cmd/env.py
+++ b/lib/spack/spack/test/cmd/env.py
@@ -1110,7 +1110,7 @@ def test_env_activate_view_fails(
         tmpdir, mock_stage, mock_fetch, install_mockery, env_deactivate):
     """Sanity check on env activate to make sure it requires shell support"""
     out = env('activate', 'test')
-    assert "To initialize spack's shell commands:" in out
+    assert "To set up shell support" in out
 
 
 def test_stack_yaml_definitions(tmpdir):
diff --git a/lib/spack/spack/test/cmd/load.py b/lib/spack/spack/test/cmd/load.py
index e6664a9d39bc2a2cdfe250277cb874f8a1163723..b1697bbfc408b6e9fc4384d0605b8141b708b2f2 100644
--- a/lib/spack/spack/test/cmd/load.py
+++ b/lib/spack/spack/test/cmd/load.py
@@ -102,7 +102,7 @@ def test_load_fails_no_shell(install_mockery, mock_fetch, mock_archive,
     install('mpileaks')
 
     out = load('mpileaks', fail_on_error=False)
-    assert "To initialize spack's shell commands" in out
+    assert "To set up shell support" in out
 
 
 def test_unload(install_mockery, mock_fetch, mock_archive, mock_packages,
@@ -135,4 +135,4 @@ def test_unload_fails_no_shell(install_mockery, mock_fetch, mock_archive,
     os.environ[uenv.spack_loaded_hashes_var] = mpileaks_spec.dag_hash()
 
     out = unload('mpileaks', fail_on_error=False)
-    assert "To initialize spack's shell commands" in out
+    assert "To set up shell support" in out