1

I'm trying to write a bash script that will create an archive (tar) of files in a directory. The file extensions need to be passed in as arguments when calling the bash script (./backup.bash pdf txt bak) like so. I'm using the array to store those arguments. The (ls | grep -i {array}) is looking up all the files in the directory that match the file extensions entered into the array and will list the files found. the (find . -type) is using those extensions to find files associated with the extensions. The (tar cvf) is creating the backup file in the working directory with the name backup.tar. I listed the array at the end of the tar command, but thinking of it now maybe I could pipe the find command into the tar command.

my_array=([$@])
ls | grep -i \{my_array[$@]}
find . -type f \( -name "my_array[$@]" \)
tar cvf ./PATH/backup.tar my_array[$@]

closed as unclear what you're asking by G-Man, mkc, RalfFriedl, user88036, Isaac Oct 2 '18 at 8:04

Please clarify your specific problem or add additional details to highlight exactly what you need. As it's currently written, it’s hard to tell exactly what you're asking. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • 1
    You need to explain a lot more clearly what you are trying to do.  Almost nothing you’ve posted is a valid command.  You might really need to work one line at a time: what do you want/expect it to do, and what does it do?  Please do not respond in comments; edit your question to make it clearer and more complete. – G-Man Oct 2 '18 at 1:51
1

There are two main issues in solving this problem. You need to find all files that have certain filename suffixes, given by a user, and you need to add them to a tar archive.

The find command has a -name option that you rightly want to use, but it can only take a single filename pattern. Since the user of the script is giving us multiple filename suffixes, we would have to use as many -name options as there are suffixes.

This means that we have to construct an array of several -name "PATTERN" options, with -o in-between each (signifying a logical "OR" between them). This would then be used with find to search for filenames with any of the given filenames suffixes.

The following does that by modifying the array $@:

#!/bin/sh

for suffix do
    shift
    set -- "$@" -o -name "*.$suffix"
done
shift    # remove the very first "-o" from $@

find . -type f \( "$@" \)

This modifies the $@ array which, from the start, already contains the suffixes given on the command line. In the loop, we remove the front element from $@ and insert our words at the end of the array.

If calling this script as

sh script.sh sh txt c

it would construct a find command that would be equivalent to

find . -type f \( -name '*.sh' -o -name '*.txt' -o -name '*.c' \)

This finds all relevant files. Now we just have to add them to the archive.

With GNU tar (but not with e.g. BSD tar), the r action allows us to update or create an archive (BSD tar only updates but will not create a new archive).

backup=./PATH/backup.tar
rm -f "$backup"
find . -type f \( "$@" \) -exec tar -r -v -f "$backup" {} +

This would create the archive ./PATH/backup.tar with the relevant files.

The reason I don't use tar -c is that when we call tar from find like this, tar may be called more than once. If I used tar -c to create a fresh new archive, that archive would be truncated each time tar was called (which may be many times if find finds many thousands of files). Using tar -r instead, we just keep updating the archive instead.

So, the complete script wold possibly look something like this:

#!/bin/sh

backup=./PATH/backup.tar

if [ "$#" -eq 0 ]; then
    echo 'No filename suffixes given' >&2
    exit 1
fi

for suffix do
    shift
    set -- "$@" -o -name "*.$suffix"
done
shift    # remove the very first "-o" from $@

rm -f "$backup"
find . -type f \( "$@" \) -exec tar -r -v -f "$backup" {} +

Note that the use of quotes in the above script is quite deliberate. It will make it possible to archive files with any allowed filename, including names that contains spaces, newlines and other unusual characters.

Related:


If using a find implementation that has -print0, you may also pass the found pathnames to GNU tar as in the script below:

#!/bin/sh

backup=./PATH/backup.tar

if [ "$#" -eq 0 ]; then
    echo 'No filename suffixes given' >&2
    exit 1
fi

for suffix do
    shift
    set -- "$@" -o -name "*.$suffix"
done
shift    # remove the very first "-o" from $@

find . -type f \( "$@" \) -print0 | tar -c -v -f "$backup" --null -T -

With -print0, find will output nul-delimited pathnames that GNU tar will read with its --null -T - options.


That last script as a bash-specific script (using an array, names for the -name options):

#!/bin/bash

backup=./PATH/backup.tar

if [ "$#" -eq 0 ]; then
    echo 'No filename suffixes given' >&2
    exit 1
fi

names=( -name "*.$1" )
shift
for suffix do
    names+=( -o -name "*.$suffix" )
done

find . -type f \( "${names[@]}" \) -print0 | tar -c -v -f "$backup" --null -T -
0

With zsh and GNU tar or bsdtar:

#! /bin/zsh -
set -o extendedglob
output=file.tar.gz
printf '%s\0' **/*.(${(j:|:)~${(b)@}})~$output(D.) |
  tar --null -cf - -T - | xz > $output
  • ${(b)@}: quotes the positional parameters to prevent them from being taken as patterns
  • ${(j:|:)...}: joins the resulting words with |
  • ${~var}: treats expansion as a globbing pattern (now looking like jpg|gif|\* if the positional parameters were jpg, gif, *)
  • **/: any level of subdirectories
  • pattern~$output: exclude the output file itself from the glob expansion
  • (D.): glob qualifiers: include hidden files and only select regular files.

Not the answer you're looking for? Browse other questions tagged or ask your own question.