简体   繁体   中英

Remove OS X Apps via Shell Script

I'm trying to create a script that will remove a list of applications in OS X. My general thinking:

  1. Applications are just directories in OS X
  2. Those directories have a Contents sub-directory that contains an Info.plist that can be used to identify the app.

So the core logic would be

  1. Walk the drive
  2. IF the current folder has a subfolder named Contents that contains a file called Info.plist that contains certain text, delete the current folder.

I've been playing around with find -exec, but am open to other approaches.

This is what I have for find -exec that isn't quite working

find /Applications -maxdepth 3 -type f -name Info.plist -exec sh -c "grep '<string>Example</string>' | xargs dirname | xargs echo rm --" 2>/dev/null

I realize

defaults read $dir/Contents/Info CFBundleExecutable

is probably better than grep to extract the package name, but don't think that is why the above isn't working (no output from the test "echo rm" at all, and inserting a tee command to output to a file didn't do anything either).

The above line, if it worked, would go in a loop running for each app to be removed with a list of app names in a variable.

I am totally open to other approaches, especially if there's a more efficient way to do this.

It's saner to keep the logic in your parent shell, rather than shuffling it off to a subprocess:

#!/usr/bin/env bash

# works correctly with names echo doesn't handle -- ones containing spaces, backslashes, etc
log_command() { printf '%q ' "$@" && echo; }

while IFS= read -r -d '' plist; do
  if grep -e '<string>Example</string>' "$plist"; then
    log_command rm -r -- "${plist%/*}"
  fi
done < <(find /Applications -maxdepth 3 -type f -name Info.plist -print0)

Note that <(...) -- process substitution -- is a bash-only feature, so it isn't guaranteed to work if your script is started with sh instead of bash .

This also can be easily modified to use better practices, so it's easy to modify to:

#!/usr/bin/env bash
log_command() { printf '%q ' "$@" && echo; }
while IFS= read -r -d '' plist; do
  dir=${plist%/*}
  if [[ "$(defaults read "$dir"/Contents/Info CFBundleExecutable)" = example ]]; then
    log_command rm -r -- "$dir"
  fi
done < <(find /Applications -maxdepth 3 -type f -name Info.plist -print0)

However, if you really want find to be the parent process, you can do that:

find /Applications -maxdepth 3 -type f -name Info.plist -exec bash -c '
  log_command() { printf '%s\n' "$@" && echo; }
  for plist do
    if grep -e "<string>Example</string>" "$plist"; then
      log_command rm -r -- "${plist%/*}"
    fi
  done
' _ {} +

The use of -exec ... {} + passes as many results as possible to each copy of bash . The _ fills in $0 , so the filenames that were found and placed on the command line are put in $1 , $2 , etc; this is what for loops over when not given an explicit list.

Since you're limiting it to a depth of 3, and that's also practically the minimum depth, and the rest of the path is going to be highly constrained (it must be *.app/Contents/ ) you don't really need find . A simple glob pattern should suffice, and should also be more efficient (since it doesn't have to search eg Contents/Resources ):

for plist in /Applications/*.app/Contents/Info.plist; do
    if [ "$(defaults read "${plist%.plist}" CFBundleExecutable)" = Example ]; then
        echo rm -R -- "${plist%/Contents/Info.plist}"    # remove "echo" to actually do it
    fi
done

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM