4

I am trying to clear multiple directories stored in an array. Here's a simplified example (I have more directories).

#!/bin/bash    

$IMAGES_DIR="/Users/michael/scripts/imagefiles"
$BACKUP_DIR="/Users/michael/scripts/imagebackups"

...

array=( $IMAGES_DIR $BACKUP_DIR )
for i in ${array[@]}
do
    if [ "$(ls -A $i)" ]; then     # check that directory has files in it
        rm "$i/"*                  # remove them 
    fi
done

I get errors for each directory, e.g.:

rm: /Users/michael/scripts/imagefiles/*: No such file or directory

5

How about accomplishing it all in a single command?

You can capture the file existence check, globbing and removal with one find call. In the case of GNU's version of find we'd have this:

for f in "${array[@]}"; do
     find "$f" -type f -delete
done

If you don't have GNU find use this invocation:

find "$f" -type f -exec rm -f {} +

(If instead of clearing files from the entire directory hierarchy you only want to clear files that are immediate children then add -maxdepth 1 before -type f.)

But wait, there's more....

As John1024 wisely notes you can forgo the loop altogether by passing the array as the first parameter to find:

     find "${array[@]}" -type f -delete

That's because: 1) find will accept multiple directories to be searched and processed in one execution 2) the shell will split the array such that each element (directory) becomes an individual positional parameter to find.

| improve this answer | |
  • 1
    Simpler: find "${array[@]}" -type f -delete – John1024 Dec 24 '17 at 3:53
  • 1
    Good one @John1024 ... I shoulda thought of that. – B Layer Dec 24 '17 at 3:54
0

Change your code to this:

#!/bin/bash    

IMAGES_DIR="/Users/michael/scripts/imagefiles"
BACKUP_DIR="/Users/michael/scripts/imagebackups"

array=( $IMAGES_DIR $BACKUP_DIR )
for i in "${array[@]}"
do
    if [ "$(ls -A "$i")" ]; then     
        rm "${i:?}"/*                 
    fi
done

Errors:

  1. Placing $ on the left hand side of variable assignments
  2. Not quoting the $i in if [ "$(ls -A $i)" ];then
  3. Use "${var:?}" to ensure this, rm "$i/"* never expands to /*
| improve this answer | |
0

The -f option to rm makes the utility not complain if the file operands do not exist. It would also not exit with a failure.

What's possibly happening in your case is that one or several of the directories are empty apart from files or directories with hidden names, so the * at the end of your pattern isn't expanded to anything. You can make the * glob match hidden names in bash by setting the dotglob shell option.

You also should not put $ on variable names in assignments, and you need to double quote variable expansions to avoid word splitting and filename generation (globbing). Variable names should be lower case so that you don't inadvertently clobber environment variables or special shell variables.

Alternative formulation of your code that takes these things into account;

#!/bin/bash    

shopt -s dotglob

images_dir="/Users/michael/scripts/imagefiles"
backup_dir="/Users/michael/scripts/imagebackups"

dirs=( "$images_dir" "$backup_dir" )

for dir in "${dirs[@]}"; do
    rm -f "$dir"/*
done

The above would try to remove all files from the directories. If the directories have subdirectories, you'll get errors from rm regarding this. Would you want to delete these too, use -r with rm.

| improve this answer | |
0

Don't call an external process to check, but use the shell internal function test

if test -d "$i"; then # check that directory exists

Between the test and the call to rm it is possible other processes already wiped the directory out, so the rm will fail. You can use the switch -f or redirects the errors

/bin/rm "$i/*" 2> /dev/null # remove the files

| improve this answer | |
-1

If your goal is to remove all files in an array of folders, another alternative would be to pass in the array in rm like so:

#!/bin/bash    

$IMAGES_DIR="/Users/michael/scripts/imagefiles"
$BACKUP_DIR="/Users/michael/scripts/imagebackups"

# ...

# to preserve whitespaces in path strings, add double quotes around each variable
array=( "$IMAGES_DIR" "$BACKUP_DIR" )

# this appends "/*" to the end of each dirname if you want to delete the contents of the directories
# without deleting the directories themselves
array=( "${array[@]/%//*}" )

# this will spread the array into multiple calls to rm
rm -rf ${array[@]}

One-liner example:

rm -rf ${array[@]/%//*}

But if you want to delete the folders as well, you can just use

rm -rf ${array[@]}

One difference to note with B Layer's solution, is that while find will print any errors in finding the paths provided, rm will not in this example.

EDIT:

as kusalananda pointed out, it's important to note that spreading the array like this could be dangerous if there are whitespaces or shell-globbing characters in you paths. To avoid splitting the array by these characters, make sure to add double quotes around the variables added to the array.

| improve this answer | |
  • Note that using the expansion of array would split it on whitespaces as well as expand any other shell globbing characters in the pathname (apart from the * that you add to the end). – Kusalananda Sep 24 '19 at 6:22
-2

Please find the below awk oneliner to achieve the same , As tested it worked fine


i="/root/";ls -ltr /root/| grep "^-rw" | awk -v i="$i" '{print "rm -rvf" " " i$9}' | sh  

for example i have assigned variable i = path /root/ you can change as per your requirement

i="/root/" ===> path

| improve this answer | |
  • @steeldriver As cross verified from above command I am getting the same result let me know if there is any difference in output – Praveen Kumar BS Dec 25 '17 at 2:53
  • This would fail on any file whose name contains whitespace or backslashes. Also, since you're removing files (not directories), why are you using -r with rm? – Kusalananda Sep 24 '19 at 6:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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