DEV Community

vindarel
vindarel

Posted on

FTP and SFTP clients for Common Lisp

You thought all companies would provide well-documented web APIs today? Well, some use a blank .docx and (S)FTP.

For Common Lisp, cl-ftp is one of those libraries that look unmaintained and undocumented, but it works very well, and it is short enough to quickly grab how to use it. It's quite well thought-out, even. It is a pure Lisp library, no system dependencies required.

For example you can do this to send a file:

(ftp:with-ftp-connection (conn :hostname hostname
                                   :username (find-ftp-username)
                                   :password password
                                   :passive-ftp-p t)
      (ftp:store-file conn local-filename filename))
Enter fullscreen mode Exit fullscreen mode

You'll have to look at this for more commands: https://github.com/pinterface/cl-ftp/blob/master/simple-client.lisp

we have ftp-shell, ftp-put, ftp-list, ftp-get, ftp-pwd, and etc for ls, help, cd, dir.

For SFTP, we must find another means. I didn't find a pure CL library, and it is annoyingly not straightforward to run a SFTP command with a password on the command line. Some solutions exist and I chose lftp. It is included in Debian.

We can run:

lftp sftp://user:password@host  -e "put local-file.name; bye"
Enter fullscreen mode Exit fullscreen mode

or better, with the password in an environment variable:

export LFTP_PASSWORD="just_an_example"
lftp --env-password sftp://user@host  -e "put local-file.name; bye"
Enter fullscreen mode Exit fullscreen mode

In my scripts I need to handle a connection profile, and even several ones for development, so I ended up with code that I replicate from project to project, hence a new utility:

https://github.com/vindarel/lftp-wrapper

Now do:

CL-USER> (use-package :lftp-wrapper)  ;; optional, or:
CL-USER> (uiop:add-package-local-nickname :lftp :lftp-wrapper)

CL-USER> (defvar profile (make-profile-from-plist (uiop:read-file-form "CREDS.lisp-expr"))
#<PROFILE protocol: "sftp", login: "user", port: 10022, server: "prod.com", password? T>

CL-USER> (defvar command (put :cd "I/" :local-filename "data.csv"))
#<PUT cd: "I/", filename: "data.csv" {1007153883}>

CL-USER> (run profile command)
…
success!
Enter fullscreen mode Exit fullscreen mode

We created a profile from credentials on file and from environment variables, we created a command object, and we executed the command for the profile.

We could wrap many more features from lftp. But I follow needs-driven development. So far, it works and it sends my files.

Top comments (0)