3

I'm trying to write a bash script that runs a remote script via ssh as below:

#!/bin/bash
logfilepattern="*drupal*.gz php*.gz error*.gz"
function getlogcounts {
    echo "in getlogcounts"
    echo  $1
    echo  $2

    ssh $1 bash -c '  \  #script string starts here
    cd $2
    for file in `ls $logfilepattern`
    do
     ls -l $file
    done
'
}

getlogcounts me@remoteserver.com "/var/log/mylogs"

$1 and $2 seem to come across ok into the function, but inside the script string they don't appear to have a value... hence cd $2 evaluates to cd and ls $logfilepattern evaluates to ls.

Max
  • 61
  • 2
  • 3
  • 1
  • also, that for loop is pointless. It can be replaced with ls -l $logfilepattern – cas Oct 04 '15 at 05:22
  • Thanks @cas . The for loop is there cause I'm trying to do a bit more which I omitted for clarity (my mistake!)... it should really be for file inls $logfilepatterndo zcat $file | wc -l function getlogcounts { echo in getlogcounts echo "$1" echo "$2"

    ssh "$1" bash -c ' cd "$2" for file in $logfilepattern do zcat $file | wc -l done ' }

    Now I'm getting: bash: -c: option requires an argument

    Sorry for the formatting... noob at this...

    – Max Oct 04 '15 at 05:48
  • even so, don't parse ls (see http://unix.stackexchange.com/questions/128985/why-not-parse-ls). for file in $logpattern; do ... ; done is sufficient, and works better. – cas Oct 04 '15 at 05:51

2 Answers2

2

Here's a version which has been simplified and, more importantly, quoting has been corrected (double-quotes around variables, single quotes around non-variable text where required)

#!/bin/bash
logfilepattern='*drupal*.gz php*.gz error*.gz'

function getlogcounts {
    echo in getlogcounts
    echo "$1"
    echo "$2"

    ssh "$1" "cd $2 ; ls -l $logfilepattern"
}

getlogcounts me@remoteserver.com /var/log/mylogs
cas
  • 78,579
2

You're executing a script on the remote machine. That script happens to contain $2 and $logfilepattern (the text between single quotes is passed literally as an argument to the ssh command, so it's a code snippet to execute on the other side). These refer to variables of the shell running on the remove side.

Actually, you're running two shells on the remote side, but not usefully so. SSH always invokes a shell; you can pass it multiple arguments but they're just joined with spaces in between. The shell on the remote side executes

bash -c   \  #script string starts here
cd $2
…

The first line invokes bash with two arguments: -c and a single space. This invokes a shell to do nothing. The rest of the remote command is executed in the shell invoked by SSH.

The simple way to do what you're trying to do is

ssh "$1" "cd $2 && ls -l $logfilepattern"

Notes:

  • Parsing the output of ls is pointless: for file in `ls *` is a complex way of writing for file in *, except that parsing the output of ls breaks if the file names contain special characters.
  • Both $2 and $logfilepattern are parsed as part of the script that's executed on the remote side. If they contain something like $(touch foo), then that piece of code will be executed. In this particular use case, it isn't necessarily a problem, but it's something you need to be aware of.
  • Using && after cd ensures that the ssh command will return immediately with a failure status if the cd command fails.

If you want to pass content unmodified to a remote shell over SSH, using the command line alone is problematic, because whatever you pass is expanded on the remote side. One trick that often works (but not always, it depends on the configuration of SSH both on the client and on the server) is to use variables whose name begins with LC_; these are assumed to be information about the locale, and often transmitted.

LC_DIRECTORY=$2 LC_LOGFILEPATTERN=$logfilepattern ssh "$1" 'cd "$LC_DIRECTORY" && ls -l $LC_LOGFILEPATTERN'

Note the quoting:

  • The command to execute on the remote machine is cd "$LC_DIRECTORY" && ls -l $LC_LOGFILEPATTERN. It's in single quotes to pass that literal text as an argument to the ssh command.
  • $LC_DIRECTORY is in double quotes in the script that's executed on the remote side so that the cd command is invoked on the content of the variable.
  • LC_LOGFILEPATTERN is not double-quoted because it's a whitespace-separated list of wildcard patterns, which is precisely how an unquoted variable expansion is treated.

If you're unable to transmit variables, you'll need to add a layer of quoting to protect variables whose value contains special characters.

q_directory=$(printf %sa "$2" | sed s/\'/\'\\\'\'/g)
q_directory="'${directory%a}'"
q_logfilepattern=$(printf %sa "$logfilepattern" | sed s/\'/\'\\\'\'/g)
q_logfilepattern="'${q_logfilepattern%a}'"
ssh "$1" "logfilepattern=$q_logfilepattern; cd $q_logfilepattern && ls \$logfilepattern"