taleeb.dev

Bash - Paths and file names manipulation

2018-02-06 · bash, linux, scripting

In this post, we dive into some of the interesting ways Bash lets you work with file names and paths. This is usually helpful when you want to write a quick Bash script to copy, move, or rename files. I recently had to deal with hundreds of log files generated by dozens of containers. The issue was that each container writes logs inside its own folder, but the log files all have the same names inside each folder.

    # tree /var/log/containers
    ├── Container_01
    │   ├── java.log
    │   ├── access.log
    │   ├── security.log
    │   └── application.log
    ├── Container_02
    │   ├── java.log
    │   ├── access.log
    │   ├── security.log
    │   └── application.log

As you can see from the example above, if I want to back up the log files and move them all to a remote server, I have two options:

For me, the second option works best — this way I don’t have to worry about multiple directories on the remote backup host, and all the log files stay under the same location. To achieve this, I had to do some path manipulation.

  1. I need to get the directory name. Assuming our path is /var/log/containers:
    for D in /var/log/containers/*; do
        BASE=${D##*/} # This is the same as "dirname $D"
        echo $BASE
    done

This gets the name of each directory inside the path we provided and saves the value in the variable BASE, which we can use later. The echo statement is not necessary — it is there just for debugging. Note that the loop produces a full path; ##*/ strips everything up to the last slash and gives us the directory name.

  1. Now, for each directory gathered in the previous step, we need to get the list of files inside it. To achieve this:
    for F in $D; do
        echo $F
    done

Again, this gives us the full path. To manipulate it, we need to slice the path into two parts: the full path minus the file name, assigned to one variable, and the short file name, assigned to another.

To get the path minus the file name => %/* To get the file name => ##*/ (similar to what we used to get BASE):

    for F in $D/*; do
        FULLPATH=${F%/*}
        FILENAME=${F##*/}
    done
  1. Rename or move the files

At this point, all we have to do is combine the variables to move the files:

    mv $F $FULLPATH/$BASE-$FILENAME
    # This is the full command if we were to run it from a terminal:
    # mv /var/log/containers/Container_01/java.log /var/log/containers/Container_01/Container_01-java.log

If we put it all together, we get the following snippet:

    for D in /var/log/containers/*; do
        BASE=${D##*/}
        for F in $D/*; do
            FULLPATH=${C%/*}
            FILENAME=${C##*/}
            mv $F $FULLPATH/$BASE-$FILENAME
        done
    done

Some other methods to manipulate files:

    FILE=/path/to/file.txt

    # To get file name without extension:
    echo ${FILE%.*}

    # To get the file's directory name:
    echo ${FILE%/*}
    # or:
    dirname $FILE

    # To get the file name and extension:
    basename $FILE