So, I am attempting to execute the following from within a shell script;

ssh -q $CUR_HOST "cd $LOGS_DIR; echo cd $LOGS_DIR; find . -name *.log -mmin +1440 -exec gzip {} \; exit"

When this runs, it generates the following error;

find: paths must precede expression
Usage: find [-H] [-L] [-P] [path...] [expression]

I've tried numerous variations based on things found on Google, but none seem to resolve this problem. Could anyone tell me what is wrong with this?

As a side note, when I manually login to the target host and run the same find command there, it works just fine. So, I'm led to think that it's something to do with the command being embedded in the double quotes of the ssh command.

  • 1
    Quote *.log ('*.log') to prevent globbing in the remote shell. – Stéphane Chazelas Jun 29 '14 at 9:22
  • Thanks...Do I need to also include the parens or just the single quotes? – Skittles Jun 29 '14 at 9:26
  • I just tried '*.log', but still the same error. – Skittles Jun 29 '14 at 9:31

You forgot to add an ';':

ssh -q $CUR_HOST "cd $LOGS_DIR; echo cd $LOGS_DIR; find . -name *.log -mmin +1440 -exec gzip {} \;; exit"

Because of the missing ;, find interprets exit as a path.

  • 1
    That's precisely what the doctor ordered! Makes sense now as the first semi-colon was escaped for the find exec. Thank you. – Skittles Jun 29 '14 at 10:30

There are several syntax problems, one of which is fatal and another which is likely to bite you at some point.

By the way, you can use + instead of ; to end the -exec directive; this way gzip will be executed in batches, which is slightly faster.

If the value of LOGS_DIR is /somewhere/with/logs, then the following command is executed on the remote host:

cd /somewhere/with/logs; echo cd /somewhere/with/logs; find . -name *.log -mmin +1440 -exec gzip {} \; exit

or, substituting newlines for the equivalent semicolon operator:

cd /somewhere/with/logs
echo cd /somewhere/with/logs
find . -name *.log -mmin +1440 -exec gzip {} \; exit

When find sees that spurious exit after the -exec … ; directive, it doesn't know what to do with it; it hazards a (wrong) guess that you meant it to be a path to traverse. You need a command separator: put another ; after \; (with or without a space before).

A second problem is that the wildcard pattern *.log will be expanded in the directory /somewhere/with/logs. If there is a matching file (i.e. if there is a file of the form /somewhere/with/logs/something.log), the pattern will be replaced by that matching file. It's only if there is no match that *.log remains unchanged by the remote shell and is seen by find. Add quotes around the pattern to prevent this.

A third problem is with the value of LOGS_DIR. It is expanded locally into a string which is executed as a shell script on the remote side. So if there are any shell special characters (whitespace, $, etc.) in it, this can execute an arbitrary remote command. A quick-and-dirty way to quote most characters is to put single quotes around the variable expansion; this will break only if the value contains single quotes. If your local script is running bash, you can replace single quotes by the 4-character sequence '\'' to protect the value of LOGS_DIR:

ssh -q "$CUR_HOST" "cd '${LOGS_DIR//\'/\'\\\'\'}'; find . -name '*.log' -mmin +1440 -exec gzip {} +; exit"
  • Note that if the login shell of the remote user is (t)csh, that escaping will still not work if $LOGS_DIR contains newline characters. – Stéphane Chazelas Jun 29 '14 at 16:10
  • Although I already accepted the previously submitted answer, this one's verbosity is extremely admirable and highly appreciated! I would just like to personally extend a thanks to you for the detail to which you displayed in this answer. – Skittles Jul 5 '14 at 7:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.