I have a problem with my shell script and shellcheck.net. The following script works like a charm, but shellcheck tells me I have to quote variables, but quoting variables in the script stops the script from working (I need word splitting).

Can someone help me, please?

apt-get $COMMAND -y -qq $PACKAGES >/dev/null &
        ^-- SC2086 (info): Double quote to prevent globbing and word splitting.
                        ^-- SC2086 (info): Double quote to prevent globbing and word splitting.
  • 1
    Ignore the error and run the script that works? Commented Mar 22, 2023 at 9:48
  • 1
    A tip for shellcheck - If you search online for the message numbers SC2086 - you'll find documentation and suggestions on how to improve your script.
    – popey
    Commented Mar 22, 2023 at 9:55
  • 1
    As no package name will ever contain whitespace, you might be OK in this case. Using an array instead would still be cleaner.
    – choroba
    Commented Mar 22, 2023 at 10:11
  • 1
    One quotes "shell variables" in case they contain blanks or funny characters. After all, one cannot trust the code that set the variable. I question your assertion that quoting your variables broke your script. Did you shellcheck the quoted version?
    – waltinator
    Commented Mar 22, 2023 at 15:47
  • It's worth clarifying that ShellCheck is not failing here, it's just giving advice.
    – wjandrea
    Commented Mar 23, 2023 at 3:01

2 Answers 2


At a guess - because you really haven't included enough information about the problem - you are doing something like

PACKAGES='foo bar baz'

and then when you double-quote the variable like

apt-get "$COMMAND" -y -qq "$PACKAGES"

the apt-get command fails with an error like E: Unable to locate package foo bar baz. In other words, you're relying on the shell's default word-splitting behavior to turn the unquoted expansion $PACKAGES into individual tokens.

The right way to fix that in bash is to use an array variable instead:

PACKAGES=(foo bar baz)

apt-get "$COMMAND" -y -qq "${PACKAGES[@]}"

See also:

I'd also suggest avoinding ALLCAPS variable names - but that's a battle for another day


bash handles the expansion of a quoted parameter as a single token regardless of spaces, tabs, or newlines separating the words originally held in that parameter and therefore it will be wholly passed as a single argument … The same behavior is demonstrated in e.g.:

for f in *; do stat "$f"; done

that passes a filename with spaces, tabs or newlines as a single argument to stat if the parameter is quoted i.e. "$f"

The right way to split multiple package names held in the parameter packages and separated by spaces passing them to apt-get as individual arguments is already mentioned in the answer by @steeldriver as array elements and you will still need to quote the expansion of the array elements as well.

However, ShellCheck is correct and you should listen ... The shell splits words based on the value/s of IFS ... That can be changed with e.g. IFS=-, then your shell will split words around - instead of the default space ... Many scripts that you or other users run might change that without you noticing it at all ... and given the fact that you are already utilizing a script for some task, the odds are you are into the habit of scripting things ... Consider this(When you add quotes):

$ command="remove"

IFS=-; apt-get "$command" -s "$packages"; unset IFS
NOTE: This is only a simulation!
      apt-get needs root privileges for real execution.
      Keep also in mind that locking is deactivated,
      so don't depend on the relevance to the real current situation!
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following packages will be REMOVED:
0 to upgrade, 0 to newly install, 1 to remove and 12 not to upgrade.
Remv python3-pip [22.0.2+dfsg-1ubuntu0.2]

Versus this(When you drop quotes):

$ command="remove"

IFS=-; apt-get $command -s $packages; unset IFS
NOTE: This is only a simulation!
      apt-get needs root privileges for real execution.
      Keep also in mind that locking is deactivated,
      so don't depend on the relevance to the real current situation!
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Note, selecting 'python3-pip' instead of 'pip'
The following packages were automatically installed and are no longer required:
  checkpolicy fonts-dejavu gdal-data gir1.2-gtk-vnc-2.0 gir1.2-libosinfo-1.0
  gir1.2-libvirt-glib-1.0 gir1.2-spiceclientglib-2.0 gir1.2-spiceclientgtk-3.0
  libaec0 libarmadillo10 libarpack2 libblosc1 libcfitsio9 libcharls2
  libdcmtk16 libembree3-3 libfreexl1 libfyba0 libgdal30 libgdcm3.0
  libgeos-c1v5 libgeos3.10.2 libgeotiff5 libgovirt-common libgovirt2
  libhdf4-0-alt libhdf5-103-1 libhdf5-hl-100 libjemalloc2 libkmlbase1
  libkmldom1 libkmlengine1 liblog4cplus-2.0.5 libnetcdf19 libodbcinst2
  libogdi4.1 libopencolorio1v5 libopencv-core4.5d libopencv-imgcodecs4.5d
  libopencv-imgproc4.5d libopencv-videoio4.5d libopenimageio2.2 libopenvdb8.1
  libosdcpu3.4.4 libproj22 libpugixml1v5 libqhull-r8.0 librttopo1 libsocket++1
  libspatialite7 libspnav0 libsquish0 libsuperlu5 libsz2 libtbb2 libtbbmalloc2
  libtinyxml2.6.2v5 liburiparser1 libxerces-c3.2 libyaml-cpp0.7
  policycoreutils proj-bin proj-data python-babel-localedata python3-pip-whl
  python3-setuptools-whl selinux-utils semodule-utils unixodbc-common
Use 'apt autoremove' to remove them.
The following additional packages will be installed:
  libnetplan0 libqt5gui5-gles libqt5quick5-gles policykit-1-gnome
Suggested packages:
The following packages will be REMOVED:
  apport apport-gtk aptdaemon apturl apturl-common blender blender-data
  bpfcc-tools chrome-gnome-shell command-not-found csvkit deja-dup dkms
  duplicity foomatic-db-compressed-ppds gdm3 gedit gedit-common
  gnome-control-center gnome-menus gnome-online-accounts gnome-shell
  gnome-shell-extension-appindicator gnome-shell-extension-desktop-icons-ng
  gnome-shell-extension-manager gnome-shell-extension-prefs
  gnome-shell-extension-system-monitor gnome-shell-extension-ubuntu-dock
  gnome-shell-extensions gnome-terminal gnome-tweaks gnome-user-docs
  gvfs-backends hplip hplip-data ibus ibus-table language-selector-common
  language-selector-gnome libatk-bridge2.0-dev libatk1.0-dev libatspi2.0-dev
  libcairo2-dev libgdk-pixbuf-2.0-dev libglib2.0-dev libglib2.0-dev-bin
  libgtk-3-dev libharfbuzz-dev libinput-tools libpango1.0-dev libqt5gui5
  libqt5opengl5 libqt5opengl5-dev libqt5quick5 libqt5quickwidgets5
  libqt5webchannel5-dev libqt5webenginewidgets5 libqwt-qt5-6 libsmbclient
  libvulkan-dev libwacom-bin linssid linux-hwe-5.19-tools-5.19.0-35
  linux-tools-5.15.0-39 linux-tools-5.15.0-39-generic
  linux-tools-5.19.0-35-generic linux-tools-common
  linux-tools-virtual-hwe-22.04-edge lsb-release meson
  nautilus-extension-gnome-terminal nautilus-share netplan.io
  networkd-dispatcher nfs-common openprinting-ppds orca perf-tools-unstable
  plymouth-theme-spinner plymouth-theme-ubuntu-text policycoreutils-dev
  policycoreutils-python-utils printer-driver-foo2zjs
  printer-driver-foo2zjs-common printer-driver-m2300w
  printer-driver-postscript-hp printer-driver-ptouch printer-driver-pxljr
  printer-driver-sag-gdi python3 python3-agate python3-agatedbf
  python3-agateexcel python3-agatesql python3-apport python3-apt
  python3-aptdaemon python3-aptdaemon.gtk3widgets python3-attr python3-audit
  python3-babel python3-bcrypt python3-blinker python3-bpfcc python3-brlapi
  python3-bs4 python3-cairo python3-certifi python3-cffi-backend
  python3-chardet python3-click python3-colorama python3-commandnotfound
  python3-cryptography python3-csvkit python3-cups python3-cupshelpers
  python3-dateutil python3-dbfread python3-dbus python3-debconf python3-debian
  python3-decorator python3-defer python3-dev python3-distro
  python3-distro-info python3-distupgrade python3-distutils python3-et-xmlfile
  python3-fasteners python3-future python3-gdbm python3-gi python3-gi-cairo
  python3-greenlet python3-html5lib python3-httplib2 python3-ibus-1.0
  python3-icu python3-idna python3-importlib-metadata python3-iniconfig
  python3-isodate python3-jdcal python3-jeepney python3-jwt python3-keyring
  python3-launchpadlib python3-lazr.restfulclient python3-lazr.uri python3-ldb
  python3-leather python3-lib2to3 python3-libevdev python3-libvirt
  python3-libxml2 python3-lockfile python3-louis python3-lxml
  python3-macaroonbakery python3-mako python3-markupsafe python3-monotonic
  python3-more-itertools python3-msgpack python3-nacl python3-neovim
  python3-netaddr python3-netifaces python3-networkx python3-numpy
  python3-oauthlib python3-olefile python3-openpyxl python3-packaging
  python3-paramiko python3-parsedatetime python3-pexpect python3-pil
  python3-pip python3-pkg-resources python3-pluggy python3-problem-report
  python3-protobuf python3-ptyprocess python3-py python3-pyatspi
  python3-pygments python3-pymacaroons python3-pynvim python3-pyparsing
  python3-pytest python3-pytimeparse python3-pyudev python3-renderpm
  python3-reportlab python3-reportlab-accel python3-requests python3-rfc3339
  python3-secretstorage python3-selinux python3-semanage python3-sepolgen
  python3-sepolicy python3-setools python3-setuptools python3-six
  python3-slugify python3-software-properties python3-soupsieve
  python3-speechd python3-sqlalchemy python3-sqlalchemy-ext python3-sss
  python3-systemd python3-talloc python3-toml python3-tz python3-unidecode
  python3-uno python3-update-manager python3-urllib3 python3-venv
  python3-wadllib python3-webencodings python3-wheel python3-xdg python3-xkit
  python3-xlrd python3-yaml python3-zipp python3.10-venv python3.11-venv
  qtbase5-dev qtdeclarative5-dev qtpositioning5-dev qttools5-dev
  qttools5-dev-tools qtwebengine5-dev rhythmbox-plugin-alternative-toolbar
  rhythmbox-plugins samba-libs selinux-policy-dev setools
  software-properties-common software-properties-gtk ssh-import-id sssd
  sssd-ad sssd-ad-common sssd-common sssd-ipa sssd-krb5 sssd-krb5-common
  sssd-ldap sssd-proxy system-config-printer system-config-printer-common
  system-config-printer-udev totem-plugins ttf-mscorefonts-installer tuptime
  ubuntu-advantage-desktop-daemon ubuntu-advantage-tools ubuntu-desktop
  ubuntu-desktop-minimal ubuntu-docs ubuntu-drivers-common ubuntu-minimal
  ubuntu-release-upgrader-core ubuntu-release-upgrader-gtk ubuntu-session ufw
  unattended-upgrades update-manager update-manager-core update-notifier
  update-notifier-common usb-creator-common usb-creator-gtk virt-manager
  virtinst virtualbox virtualbox-dkms virtualbox-qt vlc-plugin-samba xfsprogs
  xorg xserver-xorg yelp
The following NEW packages will be installed:
  libqt5gui5-gles libqt5quick5-gles policykit-1-gnome
The following packages will be upgraded:
1 to upgrade, 3 to newly install, 291 to remove and 10 not to upgrade.
Remv apport-gtk [2.20.11-0ubuntu82.3]
Remv apport [2.20.11-0ubuntu82.3]
Remv ubuntu-desktop [1.481]
Remv ubuntu-desktop-minimal [1.481]
Remv gnome-control-center [1:41.7-0ubuntu0.22.04.6]
Remv language-selector-gnome [0.219.1
etc. The list goes on and on

Are you still wiling to take the risk?

  • While you raise a point, this example doesn't really reflect the actual issue the OP will face. You changed the delimiter, and gave python3 as an example with the remove command. As per the comment, since no package names include spaces, for this particular case the original script works. But yes, not quoting it's still a bad practice that should be avoided. Commented Mar 22, 2023 at 12:42
  • 2
    @ArturMeinild Given the following two facts 1) we spend a lot of time helping users who messed up their system Python(some how python seems their first go to choice) ... and 2) not all users are at the same level of experience as yours ... This has a higher probability of happening to a user running such scripts more than the frequency their system boots :-) As it seems.
    – Raffa
    Commented Mar 22, 2023 at 12:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .