1. Overview

In this tutorial, we’ll learn how to use the ftp command in Linux to transfer files between two systems. We can use the ftp command to transfer files from server to client and also from client to server.

FTP doesn’t use encryption. In other words, user credentials (username and password) and file data are sent as plain text. As a result, the connection between the client and the server is vulnerable to different network attacks such as sniffing and spoofing attacks. For more security, we can use SFTP or FTPS since they use encryption.

2. Connecting to the FTP Server

To connect to the FTP server, we can use this command:

$ ftp <ip address or domain name>

For example:

$ ftp localhost

After connecting, it will ask us to enter the username and the password. After that, it gives us the ftp> prompt:

$ ftp localhost
Connected to localhost.
220 (vsFTPd 3.0.3)
Name (localhost:baeldung): <enter username>
331 Please specify the password.
Password: <enter password>
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> 

We are now successfully connected to the FTP server.

3. Useful Commands

After connecting to the FTP server, we can run these commands:

  • get
  • mget
  • put
  • mput
  • mkdir
  • rmdir
  • delete
  • mdelete

Now let’s take a quick look at their usage.

3.1. Downloading Files

We can use get and mget to download a file from the FTP server:

ftp> ls
200 EPRT command successful. Consider using EPSV.
150 Here comes the directory listing.
-rw-rw-r--    1 1000     1000       180103 Apr 24 08:29 file.pdf
226 Directory send OK.
ftp> get file.pdf
local: file.pdf remote: file.pdf
200 EPRT command successful. Consider using EPSV.
150 Opening BINARY mode data connection for file.pdf (180103 bytes).
226 Transfer complete.
180103 bytes received in 0.00 secs (109.0537 MB/s)

We have successfully downloaded file.pdf.

3.2. Uploading Files

We can use the put and mput commands to upload single or multiple files to the FTP server, respectively:

ftp> put <file1>
ftp> mput <file1> <file2>

These upload the file from the current working directory from the local to the remote server.

3.3. Creating/Removing a Directory

We can use mkdir to create a new directory:

ftp> mkdir <new directory name>

And we can use rmdir to remove a directory:

ftp> rmdir <directory name>

3.4. Deleting a File

We can use delete or mdelete to remove files:

ftp> delete <file name>

4. Exiting the Prompt

After we are done transferring files, we can use exit or bye to exit:

ftp> exit

5. Copy a Directory to the Server

In this section, we’ll learn an advanced use case of copying an entire directory recursively to the server.

5.1. Understanding the Scenario

First, let’s start by looking into the directory structure in a tree format using the exa command:

$ exa --tree my_dir
my_dir
├── f1.txt
├── f2.txt
└── my_sub_dir
   ├── f3.txt
   └── f4.txt

Now, let’s see if we can use the mput command to copy all the files under the my_dir directory to the FTP server:

ftp> mput my_dir
local: my_dir remote: my_dir
my_dir: not a plain file.

We can see that our attempt failed because the mput command supports only files, not directories.

Next, let’s also see if we can use the * wildcard in the file path to copy files after creating the my_dir directory manually:

ftp> mkdir my_dir; mput my_dir/*;
usage: mkdir directory-name
ftp> mkdir my_dir
257 "/my_dir" created
ftp> mput my_dir/*
local: my_dir/f1.txt remote: my_dir/f1.txt
229 Entering Extended Passive Mode (|||40008|)
150 Ok to send data.
100% |******************************************************************************************************************************************************************************************************************|     3        6.36 KiB/s    00:00 ETA226 Transfer complete.
3 bytes sent in 00:00 (0.45 KiB/s)
local: my_dir/f2.txt remote: my_dir/f2.txt
229 Entering Extended Passive Mode (|||40003|)
150 Ok to send data.
100% |******************************************************************************************************************************************************************************************************************|     3       10.50 KiB/s    00:00 ETA226 Transfer complete.
3 bytes sent in 00:00 (0.98 KiB/s)
local: my_dir/my_sub_dir remote: my_dir/my_sub_dir
my_dir/my_sub_dir: not a plain file.
ftp>

Great! We got some success this time, wherein my_dir/f1.txt and my_dir/f2.txt files were transferred. However, it couldn’t copy the my_sub_dir subdirectory and the files under it.

In the next section, we’ll develop a script to solve this use case, as there’s no direct way to copy a complete directory recursively to an FTP server.

5.2. Creating Copy Script

To solve the use case, we must traverse through the directory to generate a set of FTP commands to replicate the entire directory structure from the local to the remote FTP server.

First, let’s create the copy.sh Bash script and initialize the src_dirs and files variables as an array of absolute paths for the source directories and files, respectively:

$ cat copy.sh
#!/bin/bash

SRC_DIRECTORY="$(realpath my_dir)"
PREFIX="$(dirname $SRC_DIRECTORY)/"

MKDIR_CMD=""
PUT_FILE_CMD=""

IFS=$'\n' src_dirs=($(find $SRC_DIRECTORY | tail -n +2  | xargs -n 1 dirname | sort | uniq))
IFS=$'\n' files=($(find $SRC_DIRECTORY -type f))

We must note that SRC_DIRECTORY is initialized as the absolute path of the my_dir directory. Further, the PREFIX variable denotes the absolute path of my_dir‘s parent directory.

Next, let’s iterate over the src_dirs and files arrays to derive the directory and file paths relative to the $PREFIX path:

for src_dir in "${src_dirs[@]}"
do
    DIR="${src_dir#"$PREFIX"}"
    MKDIR_CMD="${MKDIR_CMD}mkdir ${DIR}\n"
done

echo -e "$MKDIR_CMD"

for my_file in "${files[@]}"
do
    file_relative_path="${my_file#"$PREFIX"}"
    PUT_FILE_CMD="${PUT_FILE_CMD}put ${file_relative_path}\n"
done

echo -e "$PUT_FILE_CMD"

It’s important to observe that we used these loops to generate the values in the MKDIR_CMD and the PUT_FILE_CMD variables.

Finally, let’s add code to connect to the FTP server and execute the FTP commands stored in $MKDIR_CMD and $PUT_FILE_CMD:

cd "$PREFIX"

ftp ftp://ftpuser:[email protected]:21 <<END_SCRIPT
prompt
$(echo -e $MKDIR_CMD)
$(echo -e $PUT_FILE_CMD)
exit
END_SCRIPT

exit 0

That’s it! The copy script is now ready. However, before we see it in action, let’s note that we switched the current directory to the parent directory of the my_dir directory because all paths are relative to it.

5.3. Copy Script in Action

Before we can execute the copy.sh script, we need to give execution permissions for the current user using the chmod command:

$ chmod u+x copy.sh

Now, let’s go ahead and run the script:

$ ./copy.sh
mkdir my_dir
mkdir my_dir/my_sub_dir

put my_dir/f1.txt
put my_dir/my_sub_dir/f4.txt
put my_dir/my_sub_dir/f3.txt
put my_dir/f2.txt

Connected to 127.0.0.1.
220 FTP Server
331 Please specify the password.
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
200 Switching to Binary mode.
Interactive mode off.
257 "/my_dir" created
257 "/my_dir/my_sub_dir" created
local: my_dir/f1.txt remote: my_dir/f1.txt
229 Entering Extended Passive Mode (|||40005|)
150 Ok to send data.
100% |******************************************************************************************************************************************************************************************************************|     3       15.25 KiB/s    00:00 ETA226 Transfer complete.
3 bytes sent in 00:00 (2.26 KiB/s)
local: my_dir/my_sub_dir/f4.txt remote: my_dir/my_sub_dir/f4.txt
229 Entering Extended Passive Mode (|||40004|)
150 Ok to send data.
100% |******************************************************************************************************************************************************************************************************************|     3       19.40 KiB/s    00:00 ETA226 Transfer complete.
3 bytes sent in 00:00 (2.50 KiB/s)
local: my_dir/my_sub_dir/f3.txt remote: my_dir/my_sub_dir/f3.txt
229 Entering Extended Passive Mode (|||40004|)
150 Ok to send data.
100% |******************************************************************************************************************************************************************************************************************|     3       14.94 KiB/s    00:00 ETA226 Transfer complete.
3 bytes sent in 00:00 (2.16 KiB/s)
local: my_dir/f2.txt remote: my_dir/f2.txt
229 Entering Extended Passive Mode (|||40009|)
150 Ok to send data.
100% |******************************************************************************************************************************************************************************************************************|     3       15.75 KiB/s    00:00 ETA226 Transfer complete.
3 bytes sent in 00:00 (3.09 KiB/s)
221 Goodbye.

Great, it looks like our script worked. Further, we can also note the commands stored in the MKDIR_CMD and PUT_FILE_CMD variables that helped us achieve this.

Finally, we can verify this by connecting to the FTP server and executing the ls command:

% ftp ftp://ftpuser:[email protected]:21
Connected to 127.0.0.1.
220 FTP Server
331 Please specify the password.
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
200 Switching to Binary mode.
ftp> ls my_dir
229 Entering Extended Passive Mode (|||40002|)
150 Here comes the directory listing.
-rw-r--r--    1 ftp      ftp             3 Mar 21 14:13 f1.txt
-rw-r--r--    1 ftp      ftp             3 Mar 21 14:13 f2.txt
drwxr-sr-x    2 ftp      ftp          4096 Mar 21 14:13 my_sub_dir
226 Directory send OK.
ftp> ls my_dir/my_sub_dir
229 Entering Extended Passive Mode (|||40008|)
150 Here comes the directory listing.
-rw-r--r--    1 ftp      ftp             3 Mar 21 14:13 f3.txt
-rw-r--r--    1 ftp      ftp             3 Mar 21 14:13 f4.txt
226 Directory send OK.
ftp>

6. Conclusion

In this article, we learned how to connect to an FTP server, run several commands, and send files to/from the FTP server.

Additionally, we solved the use case of copying an entire directory recursively from the local to the remote FTP server by writing a Bash script.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.