1

I have ZSH as my default shell and I am using OhMyZsh on top of it. Some times I need to delete some files and reset some permissions/ownership so I am working in an alias for it. Lets say I have a PHP/Symfony project under /home/parallels/Development/prj1. Having the below script I execute it as fixperms prj1 it works but ....

  • if I execute the script one time and it deletes the content of $CURRENT_PROJECT/$PROJECT_CACHE I end up with the following error:
fixperms:4: no matches found: /home/parallels/Development/prj1/framework/var/cache/*
  • if I remove the * at the end of $CURRENT_PROJECT/$PROJECT_CACHE/* it deletes the cache folder which I don't want

  • the file dev.log is never deleted

What I am missing here?

PROJECT=framework
PROJECT_VAR=$PROJECT/var
PROJECT_CACHE=$PROJECT_VAR/cache
PROJECT_LOG=var/log/dev.log

fixperms () {
  if [ -n "$1" ]; then
    CURRENT_PROJECT=$HOME/Development/"$1"

    cd $CURRENT_PROJECT && sudo rm -rf $CURRENT_PROJECT/$PROJECT_CACHE/* && sudo rm -rf $CURRENT_PROJECT/$PROJECT_LOG && sudo chown -R "$(whoami)":root $CURRENT_PROJECT && sudo chmod -R 777 $CURRENT_PROJECT/$PROJECT_VAR
  else
    echo "Missing project param."
  fi
}

Note: I am not a bash/shell script guru so any improvements are more than welcome and appreciate in advance

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
ReynierPM
  • 843
  • 3
  • 12
  • 22

2 Answers2

2

As you chain all the actions with && it will break if one fails - and anything after that, on that line, will not be executed. Not sure if this is intended or not.

Try for example:

echo A && false && echo B
echo A && true && echo B

Hence also why dev.log is never deleted when rm of project-cache fails. Nothing after rm -rf $CURRENT_PROJECT/$PROJECT_CACHE/*, on that line, is executed.

zsh has nullglob off by default hence why * fails.


Anyhow;

You could check if the directory is empty first, then rm. Easier would be to simply remove it and re-create it.

Optionally one could setopt nullglob and unsetopt nullglob after.

Other notes:

Can be you have a reason for using upper-case variables, but find it best to avoid it as much as possible. It can crash with environment variables, builtins etc.

You should quote more.

I assume $project_log is a file, if so -r is useless.

echo / printf errors to stderr.

project=framework
project_var=$project/var
project_cache=$project_var/cache
project_log=var/log/dev.log

fixperms () {
    if [ -n "$1" ]; then
        current_project="$HOME/Development/$1"

        # Change to project directory. Exit if it fails.
        # The shell should give descriptive error.
        pushd "$current_project" || exit 1

        [ -e "./$project_cache/" ] && sudo rm -r "./$project_cache/"
        sudo mkdir "./$project_cache"

        [ -e "./$project_log" ] && sudo rm "./$project_log"

        sudo chown -R "$(whoami):root" .

        sudo chmod -R 777 "./$project_var"
        # Restore directory we started from
        popd
    else
        printf 'fixperms(): Missing argument: project\n' >&2
    fi
}

You might want return 1 instead of exit 1 all depending on how you use the function ...

Removed -f from rm as we check for existence anyway.

ibuprofen
  • 2,781
  • 1
  • 14
  • 33
  • this works awesome, however, `popd` always returns to `/` even if I am executing the function from a different path, let's say `/tmp`. On the other side, any reason why I should not be using `-f` with `rm`? I mean is a personal choice of you but must be a reason for why you don't use it. Thank you so much! – ReynierPM Aug 24 '21 at 11:55
  • 1
    @ReynierPM That is strange. Is this a function you `source` so that is part of the shell? I'm not using `zsh` but `bash` (though I have thought of switching). Does `pushd` and `popd` work *normal* in shell? Optionally try `cd path` and then `cd -` to get back. Also: https://unix.stackexchange.com/a/273088/140633 – ibuprofen Aug 24 '21 at 20:07
  • @ReynierPM I updated a bit in regards to `-f` and `nullglob` – ibuprofen Aug 24 '21 at 21:07
  • No one wants to address why the OP thinks they should be using sudo in this script? – Marc Wilson Aug 24 '21 at 22:07
  • @MarcWilson Yes. Likely should. Brushed it of as he likely has his reasons. (Project that require root and writes files as root or the like.) But esp as it is a sub of $HOME it is worth a question. – ibuprofen Aug 24 '21 at 22:29
  • @MarcWilson the main reason I am using `sudo` is that those files are generated inside a Docker container and they are owned by root therefore it will be owned outside by `root` as well. I could create a user inside the container and have everything working under that use but that give me some headaches in the past so easier for me run the command as `sudo` than be messing up with the container stuff – ReynierPM Aug 25 '21 at 12:05
1
sudo rm -rf $CURRENT_PROJECT/$PROJECT_CACHE/*

Here's how this command is executed (I only mention the parts that matter):

  1. The shell expands the variables in the last argument. The result is /home/parallels/Development/prj1/framework/var/cache/*.
  2. The last argument contains a wildcard pattern, so it checks if that pattern matches a file. Since your account doesn't have the permission to list files in /home/parallels/Development/prj1/framework/var/cache, the pattern does not match any files (the shell doesn't distinguish between “I could determine that there are no matching files” and “I could not determine whether there were any matching files”). Wildcard patterns that don't match any files are left alone.
  3. The shell executes the program sudo with the arguments rm, -rf, /home/parallels/Development/prj1/framework/var/cache/*.
  4. sudo executes rm with the arguments -rf, /home/parallels/Development/prj1/framework/var/cache/*.
  5. rm removes the specified files, i.e. if there's a file called * in the directory /home/parallels/Development/prj1/framework/var/cache, this file is removed (and if * is a directory, its contents are removed recursively).

You need to arrange for the wildcard expansion (or whatever method used to enumerate the files to delete) to be performed by a shell that has the the permission to read the directory. This means the wildcard expansion has to happen inside a shell running under sudo, instead of in the shell that calls sudo. For example:

sudo sh -c 'rm -rf "$0"/*' "$CURRENT_PROJECT/$PROJECT_CACHE"

Note the care taken around when things are expanded. sudo sh -c 'rm -rf $CURRENT_PROJECT/$PROJECT_CACHE/*' would not work because the variables are not defined in the inner shell (it would run rm -rf //*, which is not something you should try). sudo sh -c "rm -rf $CURRENT_PROJECT/$PROJECT_CACHE/*" would only work if the values of the variables involved don't contain characters such as whitespace, since the values are interpreted as snippets of shell code rather than as file names.

Note that I do not recommend running the command above, because it's fragile: in case the path doesn't turn out to be exactly what you intended, you could end up deleting anything on the system, and since the command is running as root, this can be very bad. Rather, you should run a command with only the necessary permissions:

sudo -u parallelsowner sh -c 'rm -rf "$0"/*' "$CURRENT_PROJECT/$PROJECT_CACHE"

or

sudo -g parallelsgroup sh -c 'rm -rf "$0"/*' "$CURRENT_PROJECT/$PROJECT_CACHE"

where parallelsowner is the user who owns the directory in question and its subdirectories, or parallelsgroup is a group that has write access to the directory and all of its subdirectories.

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175