0

I'm trying to pass a dynamic command that executes ls as a string that lists the files of a directory that contains spaces. However, my ls command always interprets my one directory containing spaces as multiple directories no matter what I do.

Consider the following simplified version of my shell script:

#!/bin/sh

export WORK_DIR="/Users/jthoms/Dropbox (My Company)/backup-jthoms/Work"
echo "WORK_DIR=$WORK_DIR"

export LS_CMD="ls -A \"$WORK_DIR/dependencies/apache-tomcat-8.0.45/logs\""
echo "LS_CMD=$LS_CMD"

if [ -n "$($LS_CMD)" ]
then
    echo "### Removing all logs"
    sudo rm "$WORK_DIR/dependencies/apache-tomcat-8.0.45/logs/*"
else
    echo "### Not removing all logs"
fi

This script results in the following output:

WORK_DIR=/Users/jthoms/Dropbox (My Company)/backup-jthoms/Work
LS_CMD=ls -A "/Users/jthoms/Dropbox (My Company)/backup-jthoms/Work/dependencies/apache-tomcat-8.0.45/logs"
ls: "/Users/jthoms/Dropbox: No such file or directory
ls: (My: No such file or directory
ls: Company)/backup-jthoms/Work/dependencies/apache-tomcat-8.0.45/logs": No such file or directory
### Not removing all logs

How can I correctly escape my shell variables so that the ls command interprets my directory as a single directory containing spaces instead of multiple directories?

I recently changed this script which used to work fine for directories containing no spaces but now doesn't work for this new case. I'm working on Bash on MacOSX. I have tried various forms of escaping, various Google searches and searching for similar questions here on SO but to no avail. Please help.

entpnerd
  • 10,049
  • 8
  • 47
  • 68
  • `[ -n "$($LS_CMD)" ]` - what should that do? Why do you check if output from `ls` is nonempty? Looks like XY problem, you are trying to see if a directory is non empty, you don't care about `ls`. – KamilCuk Sep 30 '19 at 13:39
  • 1
    The escaped quotes in `LS_CMD` are treated as literal characters, not syntactic quotes that protect whitespace from word-splitting. You need to define a *function* here, not shove a command line into a variable. – chepner Sep 30 '19 at 13:43
  • As @chepner said, don't use variables to hold commands. Or, at elast, move to an array, not just a string. – Poshi Sep 30 '19 at 13:45

3 Answers3

1

Variables are for data. Functions are for code.

# There's no apparent need to export this shell variable.
WORK_DIR="/Users/jthoms/Dropbox (My Company)/backup-jthoms/Work"
echo "WORK_DIR=$WORK_DIR"

ls_cmd () {
  ls -A "$1"/dependencies/apache-tomcat-8.0.45/logs
}

if [ -n "$(ls_cmd "$WORK_DIR")" ]; then
then
  echo "### Removing all logs"
  sudo rm "$WORK_DIR/dependencies/apache-tomcat-8.0.45/logs/"*
else
  echo "### Not removing all logs"
fi

However, you don't need ls for this at all (and in general, you should avoid parsing the output of ls). For example,

find "$WORK_DIR/dependencies/apache-tomcat-8.0.45/logs/" -type f -exec rm -rf {} +
entpnerd
  • 10,049
  • 8
  • 47
  • 68
chepner
  • 497,756
  • 71
  • 530
  • 681
  • @entpnerd Note that I used `rm -rf` specifically to allow for deleting directories. If you really only want to delete regular files, you can use `-exec rm {} +` instead. – chepner Oct 01 '19 at 17:31
1

You could use

# ...
if [ -n "$(eval "$LS_CMD")" ]
# ...

See http://mywiki.wooledge.org/BashFAQ/050

Or even

# ...
if [ -n "$(bash -c "$LS_CMD")" ]
# ...

But you are probably better off using a dedicated function and/or even something more specific to your problem (using find instead of ls is usually a good idea in these cases, see some examples in the answers for this question).

jp48
  • 1,186
  • 10
  • 17
1

Use arrays, not strings, to store commands:

ls_cmd=(ls -A "$WORK_DIR/dependencies/apache-tomcat-8.0.45/logs")
echo "ls_cmd=${ls_cmd[*]}"

if [ -n "$("${ls_cmd[@]}")" ]; then …

(The syntax highlighting on the last line is incorrect and misleading: we’re not unquoting ${ls_cmd[@]}; in reality, we are using nested quotes via a subshell here.)

That way, word splitting won’t interfere with your command.

Note that you can’t export arrays. But you don’t need to, in your code.

As others have noted, it’s often a better idea to use functions here. More importantly, you shouldn’t parse the output of ls. There are always better alternatives.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214