Unpossible
Unpossible

Reputation: 623

Embedded expect script return success or failure to bash

Im embedding an expect script inside of a bash script in order to place some ftp files on a server. Im trying to have the expect script return an output value of success or failure to the bash process so I can act on it, but I am failing miserably. The script looks like this:

sftper()
{
    local filename="$1"
    local user="user"
    local password="password"
    local host="ftpserver"
    local in=1

    IFS= read -r -d '' expect_commands <<EOD >echo_log
    spawn /usr/bin/sftp $user@$host
    expect "*assword:"
    send "$password\r"
    expect "sftp>"
    send "put $filename\r"
    expect "sftp>"
    send "bye\r"
    interact
EOD
    expect -c "${expect_commands//
    /;}"
    in="$?"
    return "$in"
}

`

Please, what am I doing wrong, and how can I fix this?

Upvotes: 1

Views: 1759

Answers (1)

Donal Fellows
Donal Fellows

Reputation: 137737

The single most surprising thing in your script is that you call interact, which is an Expect command to allow the user to be connected directly to the spawned program. However, it's unlikely to be useful after you have sent a bye command to sftp; a close; exit sequence would be much more expected (to shut down the controlling virtual terminal and quit the Expect script).

Thus, I'd expect something more like:

    IFS= read -r -d '' expect_commands <<EOD >echo_log
    spawn /usr/bin/sftp $user@$host
    expect "*assword:"
    send "$password\r"
    expect "sftp>"
    send "put $filename\r"
    expect "sftp>"
    send "bye\r"
    close
    exit
EOD

More generally, you ought to think about what happens if a problem occurs (e.g., if the password has been expired), and it is probably easier to write a full expect script instead of running the gauntlet of all that shell quoting. I've added a few small explanatory comments…

# Assuming this is in a file called do_sftp_put.exp

# set up the variables; first line is how to get a command line argument
set filename [lindex $argv 0]
set user "user"
set password "password"
set host "ftpserver"

# launch sftp
spawn /usr/bin/sftp $user@$host

# login sequence
expect "*assword:"
send "$password\r"
expect "sftp>"

# send the file
send "put $filename\r"
expect "sftp>"

# Shut down
send "bye\r"
close
exit

Then your shell script wrapper becomes much simpler:

sftper()
{
    expect /path/to/do_sftp_put.exp "$1"
    return $?
}

Not much fancy quoting is required here; this is how everything is designed to work. Yes, the expect script needs careful protection so that only you can read it as it contains live credentials, but you had that problem before in your shell code so that's nothing new.


[EDIT]: Of course, this still doesn't detect errors. For that, you need a more complex script. One of the most common errors is for the password to be wrong (or to not match the user). To solve that one, we take this bit:

# login sequence
expect "*assword:"
send "$password\r"
expect "sftp>"

and upgrade it to something like:

# login sequence
expect "*assword:"
send "$password\r"
expect {
    "password" {
        # It's asking for the password *again*; must be wrong!
        # Kill the sftp program and exit with an error code
        close
        exit 1
    }
    "sftp>"
}

Working out how to detect an error isn't always trivial (you can use regular expressions and so on, and you need to understand exactly what you are working against), and in some cases it's actually better to back up and try another approach entirely. For sftp specifically, I would be trying to get an SSH identity configured so that I could use key-based auth instead of password-based auth, as that's significantly more secure in practice in several ways. However, that's a substantial change to your operations so it is definitely out of the scope of the question.

Upvotes: 4

Related Questions