Never been to CodeSnippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world (or not, you can keep them private!)

2 total

app2dock

An exercise in using PlistBuddy.


# first rebuild the Launch Services database
lsregister -kill -r -f -all system,local,user     # Mac OS X 10.5
lsregister -kill -r -f -domain local -domain system -domain user
lsregister -kill -r -f /

# entire system
set -xv
eval lsregister -kill -r -f $(/bin/df -l | /usr/bin/sed -E -e 1d -e 's/^([^ ]+[ ]+)[^\/]+//' -e "s/^(.+)$/'\1'/" | /usr/bin/tr '\n' ' ')
set +xv


dockplistfile="${HOME}/Library/Preferences/com.apple.dock.plist"

cp -ip "${dockplistfile}" "${dockplistfile}.orig"

plutil -convert xml1 -o /dev/fd/1 "${dockplistfile}" | /usr/bin/pl
plutil -convert xml1 -o ~/Desktop/com.apple.dock.plist "${dockplistfile}"
open -e ~/Desktop/com.apple.dock.plist


# get an example entry from $dockplistfile (last application in the Dock)
# cf. http://codesnippets.joyent.com/posts/show/1814

num_apps_dock=$(/usr/libexec/PlistBuddy -c "Print :persistent-apps" "${dockplistfile}" | awk -F '=' '/CFURLString = /{print $2}' | wc -l)
echo ${num_apps_dock}

/usr/libexec/PlistBuddy -c "Print :persistent-apps:'${num_apps_dock}'" "${dockplistfile}" 
#/usr/libexec/PlistBuddy -c "Print :persistent-apps:${num_apps_dock}" "${dockplistfile}" 

/usr/libexec/PlistBuddy -c "Print :persistent-apps:'${num_apps_dock}':tile-data:file-data:_CFURLString" "${dockplistfile}"


open /bin/bash


# we will first create an example entry step by step
# note: persistent-apps is an array that stores dictionaries

dockplistfile="${HOME}/Library/Preferences/com.apple.dock.plist"

# get a free array index number (that is, the last + 1 array index)
free_num_apps_dock=$(( 1 + $(/usr/libexec/PlistBuddy -c "Print :persistent-apps" "${dockplistfile}" | awk -F '=' '/CFURLString = /{print $2}' | wc -l) ))
echo ${free_num_apps_dock}

# add a dictionary to the persistent-apps array at array index ${free_num_apps_dock}
/usr/libexec/PlistBuddy -c "Add :persistent-apps:'${free_num_apps_dock}' dict" "${dockplistfile}" 

# print the dictionay in the persistent-apps array at array index ${free_num_apps_dock}
/usr/libexec/PlistBuddy -c "Print :persistent-apps:'${free_num_apps_dock}'" "${dockplistfile}" 

# add a dictionary named "tile-data" to the previously added dictionary 
/usr/libexec/PlistBuddy -c "Add :persistent-apps:'${free_num_apps_dock}':tile-data dict" "${dockplistfile}" 

/usr/libexec/PlistBuddy -c "Print :persistent-apps:'${free_num_apps_dock}'" "${dockplistfile}" 

# add the dictionary 'file-data' to the dictionary 'tile-data'
/usr/libexec/PlistBuddy -c "Add :persistent-apps:'${free_num_apps_dock}':tile-data:file-data dict" "${dockplistfile}" 

/usr/libexec/PlistBuddy -c "Print :persistent-apps:'${free_num_apps_dock}'" "${dockplistfile}" 

# add key - value pairs to the file-data dictionary
/usr/libexec/PlistBuddy -c "Add :persistent-apps:'${free_num_apps_dock}':tile-data:file-data:thisIsAKey string 'this is a string value'" "${dockplistfile}" 
/usr/libexec/PlistBuddy -c "Add :persistent-apps:'${free_num_apps_dock}':tile-data:file-data:_CFURLString string '/path/to/app'" "${dockplistfile}" 
/usr/libexec/PlistBuddy -c "Add :persistent-apps:'${free_num_apps_dock}':tile-data:file-data:_CFURLStringType integer 0" "${dockplistfile}" 

/usr/libexec/PlistBuddy -c "Print :persistent-apps:'${free_num_apps_dock}'" "${dockplistfile}" 

# delete the dictionay in the persistent-apps array at array index ${free_num_apps_dock}
/usr/libexec/PlistBuddy -c "Delete :persistent-apps:'${free_num_apps_dock}'" "${dockplistfile}" 

# print all dictionaries of the persistent-apps array
/usr/libexec/PlistBuddy -c "Print :persistent-apps" "${dockplistfile}" 


# short version
dockplistfile="${HOME}/Library/Preferences/com.apple.dock.plist"
free_num_apps_dock=$(( 1 + $(/usr/libexec/PlistBuddy -c "Print :persistent-apps" "${dockplistfile}" | awk -F '=' '/CFURLString = /{print $2}' | wc -l) ))
echo ${free_num_apps_dock}
/usr/libexec/PlistBuddy -c "Add :persistent-apps:'${free_num_apps_dock}':tile-data:file-data:thisIsAKey string 'this is a string value'" "${dockplistfile}" 
/usr/libexec/PlistBuddy -c "Add :persistent-apps:'${free_num_apps_dock}':tile-data:file-data:_CFURLString string '/path/to/app'" "${dockplistfile}" 
/usr/libexec/PlistBuddy -c "Add :persistent-apps:'${free_num_apps_dock}':tile-data:file-data:_CFURLStringType integer 0" "${dockplistfile}" 
/usr/libexec/PlistBuddy -c "Print :persistent-apps:'${free_num_apps_dock}'" "${dockplistfile}" 
/usr/libexec/PlistBuddy -c "Delete :persistent-apps:'${free_num_apps_dock}'" "${dockplistfile}" 
/usr/libexec/PlistBuddy -c "Print :persistent-apps" "${dockplistfile}" 


:<<-'COMMENT'
# example entry
Dict {
    tile-data = Dict {
        file-data = Dict {
            thisIsAKey = this is a string value
            _CFURLString = /path/to/app
            _CFURLStringType = 0
        }
    }
}

COMMENT



#-----------------------------------------------------------------------------



# first rebuild the Launch Services database
lsregister -kill -r -f -all system,local,user     # Mac OS X 10.5
lsregister -kill -r -f -domain local -domain system -domain user
lsregister -kill -r -f /

# entire system
set -xv
eval lsregister -kill -r -f $(/bin/df -l | /usr/bin/sed -E -e 1d -e 's/^([^ ]+[ ]+)[^\/]+//' -e "s/^(.+)$/'\1'/" | /usr/bin/tr '\n' ' ')
set +xv


# app2dock
# allows case insensitive name of application as input
# uses lsregister & egrep -i

# lsregister
/usr/bin/sudo /bin/ln -is $(/usr/bin/locate *lsregister | /usr/bin/head -n 1) /bin/lsregister 


unset -f app2dock
function app2dock() {

   declare appname dockplistfile free_num_apps_dock path num index

   declare -a fullpaths

   dockplistfile="${HOME}/Library/Preferences/com.apple.dock.plist"

   for appname in "${@}"; do

      appname="${appname%/}"

      declare IFS=$'\n'     # bug: does not work for file paths with embedded newlines \n

      fullpaths=( $(/bin/lsregister -dump | /usr/bin/egrep -i "path: +.*\/[^\/]*${appname}[^\/]*\.app$" | \
                       /usr/bin/sed -E 's/^[[:space:]]+path:[[:space:]]+//') )


      if [[ ${#fullpaths[@]} -eq 0 ]]; then

         echo "No such application: ${appname}"
         continue

      elif [[ ${#fullpaths[@]} -eq 1 ]]; then

         path="${fullpaths[0]}"

      else

         echo
         for ((i=0; i < ${#fullpaths[@]}; i++)); do 
            printf "\e[1m%+8s\e[m    %s\n" "$[ ${i} +1 ]:" "${fullpaths[${i}]##*/}"   # print basename only
         done

         echo
         read -e -p $'\e[1mChoose one of the numbers above\e[m:  '  num
         echo


         # check if $num is a positive integer
         # do not allow leading zeros
         if [[ "${num//[[:digit:]]/}" != "" ]] || [[ "${num#"${num%%[!0]*}"}" != "${num}" ]]; then
            echo 'Wrong input!'
            continue
         fi

         index=$[ $num - 1 ]

         [[ ${index} -ge ${#fullpaths[@]} ]] && echo 'Number too high!' && continue

         path="${fullpaths[${index}]}"


      fi


      path="${path%/}"   # cut off trailing slash if any

      [[ ! -d "${path}" ]] && echo "No such directory: ${path}" && continue

      # if $path does not end with .app ...
      [[ "${path}" == "${path%.app}" ]] && echo "Argument error: ${path}" && continue


      # escape single & double quotes in $path
      path="${path//\'/\'}"
      path="${path//\"/\\\"}"

      printf "%s\n" "$path"
      
      free_num_apps_dock=$(( 1 + $(/usr/libexec/PlistBuddy -c "Print :persistent-apps" "${dockplistfile}" | \
             /usr/bin/awk -F '=' '/CFURLString = /{print $2}' | /usr/bin/wc -l) ))
      #echo ${free_num_apps_dock}
      /usr/libexec/PlistBuddy -c "Add :persistent-apps:'${free_num_apps_dock}':tile-data:file-data:_CFURLString string '${path}'" "${dockplistfile}" 
      /usr/libexec/PlistBuddy -c "Add :persistent-apps:'${free_num_apps_dock}':tile-data:file-data:_CFURLStringType integer 0" "${dockplistfile}" 
      #/usr/libexec/PlistBuddy -c "Print :persistent-apps:'${free_num_apps_dock}'" "${dockplistfile}" 
      /usr/libexec/PlistBuddy -c save "${dockplistfile}" &>/dev/null

   done  # for

   /usr/bin/killall -HUP Dock

   return 0

}



# app2dock_fp
# only allows full file path to application as input

function app2dock_fp() {
   declare dockplistfile free_num_apps_dock path
   dockplistfile="${HOME}/Library/Preferences/com.apple.dock.plist"

   for path in "${@}"; do
      path="${path%/}"
      if [[ ! -d "${path}" ]] || [[ "${path}" == "${path%.app}" ]]; then echo "Argument error: ${path}"; continue; fi
      free_num_apps_dock=$(( 1 + $(/usr/libexec/PlistBuddy -c "Print :persistent-apps" "${dockplistfile}" | \
             /usr/bin/awk -F '=' '/CFURLString = /{print $2}' | /usr/bin/wc -l) ))
      #echo ${free_num_apps_dock}

      # escape single & double quotes in $path
      path="${path//\'/\'}"
      path="${path//\"/\\\"}"

      /usr/libexec/PlistBuddy -c "Add :persistent-apps:'${free_num_apps_dock}':tile-data:file-data:_CFURLString string '${path}'" "${dockplistfile}" 
      /usr/libexec/PlistBuddy -c "Add :persistent-apps:'${free_num_apps_dock}':tile-data:file-data:_CFURLStringType integer 0" "${dockplistfile}" 
      #/usr/libexec/PlistBuddy -c "Print :persistent-apps:'${free_num_apps_dock}'" "${dockplistfile}" 
      /usr/libexec/PlistBuddy -c save "${dockplistfile}" &>/dev/null

   done

   /usr/bin/killall -HUP Dock
   return 0
}



# remove_app_from_dock
# allows case insensitive name of application as input

function remove_app_from_dock() {

   declare appname dockplistfile nums=""

   dockplistfile="${HOME}/Library/Preferences/com.apple.dock.plist"

   for appname in "${@}"; do

      appname="${appname%/}"

      nums=""
      nums=$(/usr/libexec/PlistBuddy -c "Print :persistent-apps" "${dockplistfile}" | /usr/bin/awk -F '=' '/CFURLString = /{print $2}' | \
            /usr/bin/nl | /usr/bin/egrep -i "${appname}" | /usr/bin/awk '{print $1}' | /usr/bin/sort -rn)

      if [[ -z "${nums}" ]]; then echo "No application: ${appname} found in the Dock."; continue; fi

      for num in ${nums}; do
         /usr/libexec/PlistBuddy -c "Delete :persistent-apps:'${num}'" "${dockplistfile}" 
      done

      # test
      #for num in ${nums}; do
      #   /usr/libexec/PlistBuddy -c "Print :persistent-apps:'${num}'" "${dockplistfile}" 
      #done

      /usr/libexec/PlistBuddy -c save "${dockplistfile}" &>/dev/null

   done

   /usr/bin/killall -HUP Dock

   return 0

}


app2dock_fp /Applications/Chess.app /Applications/Chess.app

app2dock chess grapher

remove_app_from_dock chess grapher

Listing Dock items with PlistBuddy


# for PlistBuddy see: http://codesnippets.joyent.com/posts/show/1484
# for updating Bash to get in-process regular expression matching see: http://codesnippets.joyent.com/posts/show/1714

# for a command line Dock management tool see: 
# dockutil, http://patternbuffer.wordpress.com/category/dockutil/


# the awk solution should work except when a quotation mark (") is part of a file path, etc.

# applications
defaults read com.apple.dock persistent-apps | awk -F '"' '/CFURLString" = [^[:digit:]]/{print $4}' | nl

# files, folders, URLs
defaults read com.apple.dock persistent-others | awk -F '"' '/CFURLString" = [^[:digit:]]/{print $4}' | nl


# alternative with PlistBuddy & awk
dockplistfile="${HOME}/Library/Preferences/com.apple.dock.plist"
/usr/libexec/PlistBuddy -c "Print persistent-apps" "${dockplistfile}" | awk '/CFURLString = /{print $0}' | nl
/usr/libexec/PlistBuddy -c "Print persistent-apps" "${dockplistfile}" | awk -F '=' '/CFURLString = /{print $2}' | nl
/usr/libexec/PlistBuddy -c "Print persistent-others" "${dockplistfile}" | awk -F '=' '/CFURLString = /{print $2}' | nl


# applications

count=1
dockplistfile="${HOME}/Library/Preferences/com.apple.dock.plist"

# list first application
/usr/libexec/PlistBuddy -c "Print :persistent-apps:1:tile-data:file-data:_CFURLString" "${dockplistfile}"

until [[ "$(/usr/libexec/PlistBuddy -c "Print :persistent-apps:${count}" "${dockplistfile}" 2>&1)" =~ 'Does Not Exist' ]]; do
   /usr/libexec/PlistBuddy -c "Print :persistent-apps:${count}:tile-data:file-data:_CFURLString" "${dockplistfile}"
   let count++
done | /usr/bin/egrep -v 'Does Not Exist'


apps="$(
count=1
dockplistfile="${HOME}/Library/Preferences/com.apple.dock.plist"
until [[ "$(/usr/libexec/PlistBuddy -c "Print :persistent-apps:${count}" "${dockplistfile}" 2>&1)" =~ 'Does Not Exist' ]]; do
   /usr/libexec/PlistBuddy -c "Print :persistent-apps:${count}:tile-data:file-data:_CFURLString" "${dockplistfile}"
   let count++
done | /usr/bin/egrep -v 'Does Not Exist'
)"

printf "%s\n" "${#apps[@]}"
printf "%s\n" "${apps[@]}" | sed 's|/$||'



# files, folders, URLs

count=1
dockplistfile="${HOME}/Library/Preferences/com.apple.dock.plist"

until [[ "$(/usr/libexec/PlistBuddy -c "Print :persistent-others:${count}" "${dockplistfile}" 2>&1)" =~ 'Does Not Exist' ]]; do
   /usr/libexec/PlistBuddy -c "Print :persistent-others:${count}:tile-data:file-data:_CFURLString" "${dockplistfile}" 2>/dev/null
   /usr/libexec/PlistBuddy -c "Print :persistent-others:${count}:tile-data:url:_CFURLString" "${dockplistfile}" 2>/dev/null
   let count++
done | /usr/bin/egrep -v 'Does Not Exist'



docs="$(
count=1
dockplistfile="${HOME}/Library/Preferences/com.apple.dock.plist"
until [[ "$(/usr/libexec/PlistBuddy -c "Print :persistent-others:${count}" "${dockplistfile}" 2>&1)" =~ 'Does Not Exist' ]]; do
   /usr/libexec/PlistBuddy -c "Print :persistent-others:${count}:tile-data:file-data:_CFURLString" "${dockplistfile}" 2>/dev/null
   /usr/libexec/PlistBuddy -c "Print :persistent-others:${count}:tile-data:url:_CFURLString" "${dockplistfile}" 2>/dev/null
   let count++
done | /usr/bin/egrep -v 'Does Not Exist'
)"


printf "%s\n" "${#docs[@]}"
printf "%s\n" "${docs[@]}" | sed 's|/$||'
2 total