├── LICENSE ├── README.md ├── Rakefile └── src ├── 32bitSafePerl ├── clipcat ├── dict ├── eject ├── launch ├── ql ├── swuser ├── trash ├── urlmnt └── with /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2006 Morgan Aldridge 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tools-osx README 2 | ================ 3 | 4 | by Morgan Aldridge 5 | 6 | [![tools-osx on OpenHub](https://www.openhub.net/p/tools-osx/widgets/project_thin_badge.gif)](https://www.openhub.net/p/tools-osx) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=DBY3R8ARLDELE¤cy_code=USD&source=url) 7 | 8 | OVERVIEW 9 | -------- 10 | 11 | A small collection of Mac OS X command line tools that I’ve developed over the years. Similar to the NextStep and Mac OS X commands `open`, `pbcopy`, and `pbpaste`, most of these tools are `bash` scripts created as an exercise to hone my bash-fu. There are also a number of Perl and Ruby scripts submitted by others. 12 | 13 | TOOLS 14 | ----- 15 | 16 | ### clipcat 17 | 18 | `clipcat` prints and concatenates Mac OS text clippings. Submitted by Daphne Preston-Kendal. 19 | 20 | ### dict 21 | 22 | `dict` performs a look-up in the Mac OS X dictionary. Note: unlike the other scripts provided, this one requires that you have [MacRuby](http://macruby.org/) installed. Submitted by Daphne Preston-Kendal. 23 | 24 | ### eject 25 | 26 | `eject` ejects a volume or network volume and all other volumes on the same local device. A shorter, simpler, smarter wrapper of `diskutil`’s various eject/unmount options and allows specifying either a full path in `/Volumes` or just the volume name. 27 | 28 | ### launch 29 | 30 | `launch` searches for and launches applications. It's a slightly smarter wrapper for `open -a` with wildcard matching via Spotlight, if available, or `find`. 31 | 32 | ### ql 33 | 34 | `ql` opens files with the Mac OS X [Quick Look](https://support.apple.com/en-ca/guide/mac-help/mh14119/mac) feature. 35 | 36 | ### swuser 37 | 38 | `swuser` switches users using the Mac OS X [Fast User Switching](https://support.apple.com/en-ca/guide/mac-help/mchlp2439/mac) feature. You can either switch out to the login window or to another user. Note: unfortunately, this is not currently compatible with `screen`. 39 | 40 | ### trash 41 | 42 | `trash` allows trashing of files instead of tempting fate with `rm`. Correctly handles trashing files on other volumes, uses the same filename renaming scheme as Finder for duplicate file names, can list trash contents w/disk usage summary, and empty trash (including securely) w/confirmation. Does not require Finder to be running. 43 | 44 | ### with 45 | 46 | `with` sets the application with which specified documents will be opened. It can even change and open in one fell swoop. Submitted by Daphne Preston-Kendal. 47 | 48 | INSTALLATION 49 | ------------ 50 | 51 | ### Installing from Source 52 | 53 | Installing all tools: 54 | 55 | 1. Clone this repository or download and extract the tools. 56 | 2. Run `rake`. 57 | 3. Run `rake install`. (If you don't have write permission on `/usr/local/bin`, you'll need to prepend `sudo`.) 58 | 59 | Installing one or more specific tools: 60 | 61 | 1. Clone this repository or download and extract the tools. 62 | 2. Run `rake` 63 | 3. Run `rake 'install[ql,trash]'`, specifying individual tools in a comma separated list between the square brackets. (If you don't have write permission on `/usr/local/bin`, you'll need to prepend `sudo`.) 64 | 65 | ### Installing Using a `zsh` Plugin Manager 66 | 67 | _Note: You cannot install `clipcat`, `dict`, or `with` using this method._ 68 | 69 | macOS 10.15 Catalina and newer now use `zsh` as the default shell. If you're using a `zsh` plugin manager, you can install individual tools as follows and they'll be automatically downloaded, installed, and kept up-to-date: 70 | 71 | * Using [zinit](https://github.com/zdharma/zinit): Add the following to your `~/.zshrc` file for each tool you wish to install, `trash`, for example: 72 | 73 | ```shell 74 | zinit wait'1' lucid light-mode as"program" pick"src/trash" for morgant/tools-osx 75 | ``` 76 | 77 | OTHER TOOLS 78 | ----------- 79 | 80 | Looking for other Mac OS X-specific command line tools to complement these? Check out the following: 81 | 82 | * [`appswitch`](http://sabi.net/nriley/software/#appswitch) and [`launch`](http://sabi.net/nriley/software/#launch) by Nicholas Riley 83 | * [`asprint`](http://hasseg.org/asprint/), [`icalBuddy`](http://hasseg.org/icalBuddy/), [`setWeblocThumb`](http://hasseg.org/setWeblocThumb/), and [`trash`](http://hasseg.org/trash/) by Ali Rantakari 84 | * [`contacts`](https://web.archive.org/web/20160319061131/http://www.gnufoo.org/contacts/) by Shane Celis 85 | * [`dark-mode`](https://github.com/sindresorhus/dark-mode) by Sindre Sorhus 86 | * [`dockutil`](https://github.com/kcrawford/dockutil) by Kyle Crawford 87 | * [`duti`](https://github.com/moretension/duti) by Andrew Mortensen 88 | * [`get-location`](https://github.com/lindes/get-location) by David Lindes 89 | * [`iloc`](https://derailer.org/iloc/) by Nate Weaver 90 | * [`imsg`](https://github.com/chrisfsampaio/imsg) by Christian Sampaio 91 | * [`ithief`](https://web.archive.org/web/20120418093332/http://cachivaches.chauca.net/ithief/) by Israel Chauca Fuentes 92 | * [osxiconutils (`geticon`, `icns2pict`, `pict2icns`, `seticon`)](http://sveinbjorn.org/osxiconutils) & [osxutils (`cpath`, `getfcomment`, `geticon`, `google`, `hfsdata`, `lsmac`, `mkalias`, `rcmac`, `setfcomment`, `setfctypes`, `setfflags`, `seticon`, `setlabel`, `setsuffix`, `setvolume`, `trash`, `wiki`, `wsupdate`)](https://web.archive.org/web/20100831125502/http://osxutils.sourceforge.net/) by Sveinbjorn Thordarson 93 | * [`pngpaste`](https://github.com/jcsalterego/pngpaste) by Jerry Chen 94 | * [`rem`](https://github.com/kykim/rem) by Kevin Y. Kim 95 | * [`service`](https://github.com/dpk/service) by Daphne Preston-Kendal 96 | * [`stronghold`](https://github.com/alichtman/stronghold) and [`shallow-backup`](https://github.com/alichtman/shallow-backup) by Aaron Lichtman 97 | * [`tag`](https://github.com/jdberry/tag) by James Berry 98 | * [`trash`](http://www.dribin.org/dave/osx-trash/) by Dave Dribin 99 | * [`trash`](https://github.com/sindresorhus/trash) by Sindre Sorhus 100 | * [`webkit2png`](http://www.paulhammond.org/webkit2png/) by Paul Hammond 101 | 102 | SPECIAL THANKS 103 | -------------- 104 | 105 | Special thanks to: [Daphne Preston-Kendal](http://dpk.io/) for all her submissions and collaboration; [Matt Brictson](https://mattbrictson.com) for his initial relative path bug fix in `trash`; huyz for the improved determination of Finder’s PID to prevent possible false-positives in `trash`. 106 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | 3 | include FileUtils::Verbose 4 | 5 | class Dir 6 | def self.nonhidden_entries dirname 7 | self.entries(dirname).grep(/^[^.]/) 8 | end 9 | end 10 | 11 | task :default do 12 | mkdir './bin' unless File.directory? './bin' 13 | unless File.exist? './bin/dict' 14 | mrc = `which macrubyc`.strip # mrc == macruby compiler 15 | if mrc.empty? 16 | STDERR.puts <<-EOF.split("\n").map {|l| l.strip} 17 | 18 | WARNING: MacRuby is not installed; therefore, `dict`, one 19 | of the tools that depends on it, will not be installed. 20 | The rest of the tools will be installed. 21 | 22 | If you would like to use `dict`, install MacRuby from 23 | http://macruby.org/ then re-install tools-osx. 24 | 25 | EOF 26 | else 27 | mkdir './.tmp/' 28 | cp './src/dict', './.tmp/dict.rb' 29 | sh "#{mrc} -o ./bin/dict ./.tmp/dict.rb" 30 | rm_rf './.tmp/' 31 | end 32 | end 33 | 34 | scrs = Dir.nonhidden_entries('./src/'); scrs.delete 'dict' 35 | scrs.each {|f| cp "./src/#{f}", "./bin/#{f}" unless File.exist? "./bin/#{f}"} 36 | end 37 | 38 | task :install => [:default] do |t, args| 39 | unless File.exist? '/usr/local/bin' # create /usr/local/bin if it doesn't exist 40 | mkdir '/usr/local/bin', :mode => 0755 41 | chown 'root', 'wheel', '/usr/local/bin' 42 | end 43 | bins = Dir.nonhidden_entries('./bin/') 44 | bins = bins.select {|x| args.extras.include? x} unless args.extras.count == 0 45 | sh "install -b #{bins.map {|x| "./bin/#{x}"}.join ' '} /usr/local/bin/" unless bins.count == 0 46 | end 47 | 48 | task :uninstall do 49 | Dir.nonhidden_entries('./src/').each do |x| # should probably figure out a better way to do this 50 | rm "/usr/local/bin/#{x}", :force => true 51 | end 52 | end 53 | 54 | task :clean do 55 | rm_rf './bin' 56 | end 57 | -------------------------------------------------------------------------------- /src/32bitSafePerl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # 32bitSafePerl - A wrapper for Perl scripts which will force them to be run by 5 | # the 32-bit Perl binary since MacPerl is not 64-bit compatible. 6 | # It's intelligent enough to find the appropriate version 7 | # depending on the version of Mac OS X, regardless of whether 8 | # "Prefer-32-Bit" has been set, up until Mac OS X 10.7 Lion 9 | # which no longer has a 32-bit Perl binary. 10 | # 11 | # Use '#!/usr/bin/env 32bitSafePerl'. 12 | # 13 | # v0.1 2012-01-06 - Morgan Aldridge 14 | # Initial version. 15 | # 16 | 17 | # get the Mac OS X version 18 | version_major=$(sw_vers -productVersion | cut -d . -f 2) 19 | #version_minor=$(sw_vers -productVersion | cut -d . -f 3) 20 | 21 | interpreter='' 22 | # 10.7.x now has no 32-bit only version of Perl included, so we 23 | # need to set Perl's prefer-32-Bit default 24 | if [ $version_major -eq 7 ]; then 25 | if [ "$(defaults read com.apple.versioner.perl Prefer-32-Bit)" != "1" ]; then 26 | printf "Mac OS X 10.7.x Lion requires that we set Perl's 'Prefer-32-Bit' default for %s to work properly.\n" "$1" 27 | printf "Shall we do that for you (y/n)?" 28 | read confirm 29 | if [ "$confirm" = "y" ]; then 30 | defaults write com.apple.versioner.perl Prefer-32-Bit -bool true 31 | printf "Done. You can change this back at any time by running 'defaults write com.apple.versioner.perl Prefer-32-Bit -bool false'.\n" 32 | else 33 | printf "No change made. Exiting.\n" 34 | exit 1 35 | fi 36 | fi 37 | interpreter='/usr/bin/perl' 38 | # 10.6.x should use Perl 5.8.9 39 | elif [ $version_major -eq 6 ]; then 40 | interpreter='/usr/bin/perl5.8.9' 41 | # 10.5.x should use Perl 5.8.8 42 | elif [ $version_major -eq 5 ]; then 43 | interpreter='/usr/bin/perl5.8.8' 44 | # 10.4.x and earlier can use just use 'perl' 45 | elif [ $version_major -le 4 ]; then 46 | interpreter='/usr/bin/perl' 47 | fi 48 | 49 | # Perl tries to be helpful and always honors the shebang/hashbang in the script 50 | # it executes, but that causes an infinite loop due to what we're doing. So we 51 | # have to strip out the shebang/hashbang before sending to Perl. To do that, 52 | # because Perl can't accept command line arguments when running a script from 53 | # stdin, we must write the script, sans the shebang, to a tmp file. 54 | 55 | # determine the command name & shift it off the stack of arguments 56 | full_command="$1" 57 | command="${full_command##*/}" 58 | shift 59 | 60 | # create the tmp Perl script and fill it with the original script, sans the 61 | # shebang/hashbang 62 | mkdir -p "/tmp/32bitSafePerl" 63 | touch "/tmp/32bitSafePerl/${command}" 64 | chmod 755 "/tmp/32bitSafePerl/${command}" 65 | tail -n+2 "$full_command" > "/tmp/32bitSafePerl/${command}" 66 | 67 | # run the new temp Perl script through the appropriate version of Perl w/the 68 | # command line arguments 69 | "$interpreter" "/tmp/32bitSafePerl/${command}" "$@" 70 | 71 | # clean up the temp Perl script 72 | rm "/tmp/32bitSafePerl/${command}" 73 | -------------------------------------------------------------------------------- /src/clipcat: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 32bitSafePerl 2 | 3 | # 4 | # clipcat - Concatenate and print Text Clippings. 5 | # 6 | # v0.1 2010-11-18 - David Kendal 7 | # Initial version. Used with permission. 8 | # v0.2 2010-12-08 - Morgan Aldridge 9 | # Now concatenates multiple text clippings. Usage instructions. 10 | # v0.2.1 2012-01-15 - David Kendal 11 | # Fixed NUL byte added to clipcat output when there were an odd 12 | # number of characters. 13 | # 14 | 15 | use warnings; 16 | use strict; 17 | use utf8; 18 | use MacPerl; 19 | use File::Basename; 20 | 21 | my $script = basename($0); 22 | 23 | if ($ARGV[0]) { 24 | while ($ARGV[0]) { 25 | my $file = $ARGV[0]; 26 | my $fileinfo = MacPerl::GetFileInfo($file); 27 | 28 | $file = escape($file); 29 | 30 | if (!-e $file) { 31 | print STDERR "$script: no such file $file.\n"; exit 1; 32 | } elsif ($fileinfo ne 'clpt') { 33 | print STDERR "$script: file $file is not a clipping.\n"; exit 1; 34 | } 35 | 36 | my @resdata = `DeRez -only 'utf8' '$file'`; 37 | shift(@resdata); 38 | pop(@resdata); pop(@resdata); 39 | 40 | foreach (@resdata) { 41 | my $line = $_; 42 | chomp $line; 43 | $line =~ s{^\t\$"|" +/\*.+\*/$}{}g; 44 | my @blobs = split / /, $line; 45 | foreach (@blobs) { 46 | my $blob = $_; 47 | 48 | my $hex1 = substr $blob, 0, 2; 49 | my $hex2 = substr $blob, 2, 3; 50 | 51 | print chr(hex($hex1)); 52 | print chr(hex($hex2)) unless (hex($hex2) == 0); 53 | } 54 | } 55 | 56 | shift(@ARGV); 57 | } 58 | exit 0; 59 | } else { 60 | print STDERR "$script: No files were specified.\n\n"; 61 | usage(); 62 | exit 1; 63 | } 64 | 65 | sub usage { 66 | print "Usage: clipcat [options] file ...\n"; 67 | print " -h print these usage instructions\n"; 68 | } 69 | 70 | sub escape { # http://cl.ly/3JGN -- should work to escape the filename 71 | my $text = shift; # for handing to DeRez 72 | $text =~ s/\\/\\\\/g; 73 | $text =~ s/'/\\'/g; 74 | return $text; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /src/dict: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env macruby 2 | 3 | # 4 | # dict - Make a lookup in Mac OS X's included dictionary. Requires MacRuby. 5 | # 6 | # v1.0 2011-08-01 - David Kendal 7 | # Initial version. 8 | # v1.1 2012-01-08 - David Kendal 9 | # Adds multiple word lookups and a vaguely-interactive mode. 10 | # 11 | 12 | framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/DictionaryServices.framework' 13 | 14 | def definition word 15 | wlen = DCSGetTermRangeInString nil, word, 0 16 | if word.strip.length == 0 17 | # do nothing 18 | elsif wlen.location == -1 19 | STDERR.puts "#{File.basename $0}: no definition for #{word}" 20 | else 21 | puts DCSCopyTextDefinition nil, word, wlen 22 | end 23 | end 24 | 25 | if ARGV.empty? 26 | while gets 27 | definition $_ 28 | end 29 | else 30 | ARGV.each do |word| 31 | # handle options 32 | if word =~ /^-/ 33 | case word 34 | when '-h' 35 | puts <<-EOF 36 | Usage: #{File.basename $0} [options] word ... 37 | -v verbose output 38 | -h print these usage instructions 39 | EOF 40 | end 41 | else 42 | definition word 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /src/eject: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # eject - 5 | # 6 | # v0.1 2007-05-21 - Morgan Aldridge 7 | # Initial version. 8 | # v0.2 2010-10-24 - Morgan Aldridge 9 | # Minor cleanup. Automatically prepend /Volumes/ to volume name if omitted. 10 | # v0.3 2010-12-07 - Morgan Aldridge 11 | # Added "-f" option to force a disk to eject. Now supports ejecting of 12 | # network volumes. 13 | # v0.4 2014-01-30 - Lucas Morales 14 | # Added long argument forms. 15 | # 16 | 17 | # TO DO: 18 | # - Verbose doesn't currently do anything. 19 | 20 | # global variables 21 | force=false 22 | verbose=false 23 | help=false 24 | 25 | # print usage instructions (help) 26 | function usage() { 27 | printf "Usage: eject [options] volume ...\n" 28 | printf " -f --force force volume to be ejected, even if files/directories are open\n" 29 | printf " -v --verbose verbose output\n" 30 | printf " -h --help print these usage instructions\n" 31 | } 32 | 33 | # see if any arguments were passed in 34 | if [ $# -gt 0 ]; then 35 | # if so, step through them all and process them 36 | while [ $# -gt 0 ]; do 37 | # see if the user intended us to run in verbose mode 38 | if [ "$1" = "-v" ] || [ "$1" = "--verbose" ]; then 39 | shift 40 | verbose=true 41 | # see if the user intended us to force the action 42 | elif [ "$1" = "-f" ] || [ "$1" = "--force" ]; then 43 | shift 44 | force=true 45 | # see if the user requested help 46 | elif [ "$1" = "-h" ] || [ "$1" = "--help" ]; then 47 | shift 48 | help=true 49 | usage 50 | else 51 | # don't bother doing anything if the user requested help 52 | if ! $help; then 53 | if $force; then f="force"; fi 54 | # try to detect whether the user prepended "/Volumes/" to the volume name 55 | if [ "${1:0:9}" = "/Volumes/" ]; then 56 | vol="$1" 57 | else 58 | vol="/Volumes/$1" 59 | fi 60 | # determine the type of filesystem of the mount so we can unmount network volumes appropriately 61 | mnt_info=$(mount | grep "$vol") 62 | IFS=\(, read -r -d '' _ fs_type _ <<< "$mnt_info" 63 | if [[ ( $fs_type == "afpfs" ) || ( $fs_type == "smbfs" ) || ( $fs_type == "webdav" ) ]]; then 64 | diskutil unmount $f "$vol" 65 | else 66 | if $force; then 67 | diskutil unmountDisk $f "$vol" 68 | else 69 | diskutil eject "$vol" 70 | fi 71 | fi 72 | fi 73 | shift 74 | fi 75 | done 76 | else 77 | printf "No volumes were specified to be ejected.\n\n" 78 | usage 79 | fi 80 | -------------------------------------------------------------------------------- /src/launch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # launch - find & launch applications by name 5 | # 6 | # CHANGE LOG: 7 | # 8 | # v0.1 2021-03-05 - Morgan T. Aldridge 9 | # Initial version. 10 | # 11 | # LICENSE: 12 | # 13 | # Copyright (c) 2021, Morgan T. Aldridge. All rights reserved. 14 | # 15 | # Redistribution and use in source and binary forms, with or without 16 | # modification, are permitted provided that the following conditions are met: 17 | # 18 | # - Redistributions of source code must retain the above copyright notice, this 19 | # list of conditions and the following disclaimer. 20 | # - Redistributions in binary form must reproduce the above copyright notice, 21 | # this list of conditions and the following disclaimer in the documentation 22 | # and/or other materials provided with the distribution. 23 | # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 28 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 32 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | # 35 | 36 | # info 37 | tool="$(basename $0)" 38 | version="0.1" 39 | copyright="Copyright (c) 2021 Morgan Aldridge" 40 | 41 | # globals 42 | verbose=false 43 | search_paths=("/System/Applications" "/Applications" "/Network/Applications" "${HOME}/Applications") 44 | 45 | function usage() { 46 | echo "Usage: ${tool} [options] [ ...]" 47 | echo 48 | echo "Options:" 49 | echo " -h, --help : print these usage instructions" 50 | echo " -v, --verbose : verbose output" 51 | echo " -V, --version : print version information" 52 | echo 53 | } 54 | 55 | function version() { 56 | echo "${tool} v${version} ${copyright}" 57 | } 58 | 59 | function spotlight_enabled() { 60 | mdutil -s / | grep "Indexing enabled" > /dev/null && true || false 61 | } 62 | 63 | function find_apps_in_path() { 64 | local path="$1" 65 | local query="$2" 66 | 67 | spotlight_enabled \ 68 | && mdfind -onlyin "${path}" -literal "* = '${query}*'cdw && kMDItemKind == 'Application'" \ 69 | || find "${path}" -iname "*${query}*.app" -depth 1 70 | } 71 | 72 | function find_apps() { 73 | local query="$1" 74 | 75 | for path in "${search_paths[@]}" ; do 76 | [ -d "${path}" ] && find_apps_in_path "${path}" "${query}" 77 | done 78 | } 79 | 80 | function main() { 81 | # parse params 82 | local query="" 83 | while [ $# -gt 0 ] ; do 84 | case "$1" in 85 | "-h" | "--help") 86 | usage && exit 0 87 | ;; 88 | "-V" | "--version") 89 | version && exit 0 90 | ;; 91 | "-v" | "--verbose") 92 | verbose=true && shift 93 | ;; 94 | -*) 95 | echo "Unknown option '${1}'!" && echo && usage && exit 1 96 | ;; 97 | *) 98 | query="$1" && shift && break 99 | ;; 100 | esac 101 | done 102 | [ -z "${query}" ] && usage && exit 1 103 | 104 | # find matching apps 105 | local apps=() 106 | while IFS= read -r line; do 107 | apps+=("${line}") 108 | done <<< "$(find_apps "${query}")" 109 | 110 | if [ ${#apps[@]} -eq 1 ] && [ -z "${apps[0]}" ] ; then 111 | echo "Unable to find application named '${query}'!" 112 | exit 1 113 | elif [ ${#apps[@]} -eq 1 ] ; then 114 | $verbose && echo "Launching ${apps[0]}" 115 | open -a "${apps[0]}" "$@" 116 | else 117 | echo "Found ${#apps[@]} matching applications:" 118 | local i 119 | for (( i=0 ; i < ${#apps[@]} ; i++ )) ; do 120 | printf "%2i) %s\n" "$i" "${apps[$i]}" 121 | done 122 | printf " q) Quit\n" 123 | 124 | local app 125 | read -p "Selection [0]: " app 126 | case "${app}" in 127 | "") 128 | app=0 129 | ;; 130 | "q" | "Q") 131 | exit 0 132 | ;; 133 | esac 134 | [ "${app}" -ge 0 ] && [ "${app}" -lt "${#apps[@]}" ] && open -a "${apps[$app]}" "$@" || echo "Invalid selection!" && exit 1 135 | fi 136 | } 137 | 138 | main "$@" 139 | -------------------------------------------------------------------------------- /src/ql: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # ql - Trigger Quick Look on the specified file. 5 | # 6 | # Via http://www.macworld.com/article/131923/2008/02/qlterminal.html 7 | # 8 | # v0.1 2012-01-07 - Morgan Aldridge 9 | # Initial version. 10 | # v0.2 2014-01-30 - Lucas Morales 11 | # Added long argument forms. 12 | # v0.3 2020-11-15 - Benito Marcote 13 | # Added support for macOS 11 Big Sur. 14 | # 15 | 16 | # Quick Look requires 10.5.x or higher 17 | if [ $(sw_vers -productVersion | cut -d . -f 2) -lt 5 ] && [ $(sw_vers -productVersion | cut -d . -f 1) -lt 11 ]; then 18 | echo "Quick Look requires Mac OS X 10.5 Leopard or newer." 19 | exit 1 20 | fi 21 | 22 | # print usage instructions (help) 23 | function usage() { 24 | echo "Usage: ql [options] file" 25 | echo " -h --help print these usage instructions" 26 | } 27 | 28 | # see if any arguments were passed in 29 | if [ $# -gt 0 ]; then 30 | # see if the user requested help 31 | if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then 32 | shift 33 | usage 34 | else 35 | qlmanage -p "$1" >& /dev/null 36 | fi 37 | else 38 | printf "No file was specified to be viewed with Quick Look.\n\n" 39 | usage 40 | fi 41 | -------------------------------------------------------------------------------- /src/swuser: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # swuser - Switch users via Mac OS X's Fast User Switching 5 | # 6 | # Originally via Schoun Reagan, but later 7 | # http://www.macosxhints.com/article.php?story=20031102031045417 8 | # 9 | # v0.1 2006-10-18 - Morgan Aldridge 10 | # Initial release 11 | # v0.2 2010-12-27 - Morgan Aldridge 12 | # Renamed to 'swuser', added usage instructions, options 13 | # for switching to login window, user by name, or user by ID. 14 | # v0.3 2014-01-30 - Lucas Morales 15 | # Added long argument forms. 16 | # 17 | 18 | # global variables 19 | user=$(whoami) 20 | id=$(id -u $user) 21 | cgsession="/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession" 22 | 23 | # print usage instructions (help) 24 | function usage() { 25 | printf "Usage: swuser [options [username|useried]]\n" 26 | printf " -h --help print these usage instructions\n" 27 | printf " -l --login switch to the login window\n" 28 | printf " -n --name switch to a different user by user name\n" 29 | printf " -u --user switch to a different user by user id\n" 30 | } 31 | 32 | function check_id() { 33 | if (( $1 == $2 )); then 34 | printf "Sorry, you can't switch to yourself.\n" 35 | exit 1; 36 | fi 37 | } 38 | 39 | # exit with an error if being run from screen 40 | if [ "$STY" != "" ]; then 41 | printf "Sorry, swuser doesn't work from within screen. I know, I hate it too!\n" 42 | exit 1; 43 | fi 44 | 45 | # see if any arguments were passed in 46 | if [ $# -gt 0 ]; then 47 | # if so, step through them all and process them 48 | while [ $# -gt 0 ]; do 49 | # see if the user requested help 50 | if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then 51 | usage 52 | exit; 53 | # handle switch to login window 54 | elif [ "$1" = "-l" ] || [ "$1" = "--login" ]; then 55 | "$cgsession" -suspend 56 | shift 57 | # handle switching to another user by name 58 | elif [ "$1" = "-n" ] || [ "$1" = "--name" ]; then 59 | shift 60 | new_id=$(id -u "$1") 61 | check_id $id $new_id 62 | "$cgsession" -switchToUserID $(id -u "$1") 63 | shift 64 | # handle switch to another user by id 65 | elif [ "$1" = "-u" ] || [ "$1" = "--user" ]; then 66 | shift 67 | check_id $id $1 68 | "$cgsession" -switchToUserID $1 69 | shift 70 | fi 71 | done 72 | else 73 | printf "No switch user action specified.\n\n" 74 | usage 75 | fi 76 | 77 | -------------------------------------------------------------------------------- /src/trash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # trash - Move files to the appropriate .Trash file on Mac OS X. (Intended 5 | # as an alternative to 'rm' which immediately deletes the file.) 6 | # 7 | # v0.1 2007-05-21 - Morgan Aldridge 8 | # Initial version. 9 | # v0.2 2010-10-26 - Morgan Aldridge 10 | # Use appropriate .Trashes folder when trashing files 11 | # on other volumes. Create trash folder(s) if necessary. 12 | # v0.2.1 2010-10-26 - Morgan Aldridge 13 | # No longer using bash built-in regexp support in hopes 14 | # of support Mac OS X 10.4 and earlier. 15 | # v0.3 2010-12-07 - Morgan Aldridge 16 | # Correctly handle full volume path which is root volume. 17 | # Now increments filename if filename already exists in 18 | # trash folder (à la Finder). 19 | # v0.4 2011-06-02 - Morgan Aldridge 20 | # Option to list trash contents w/disk usage total. Allows 21 | # emptying of trash w/confirmation, incl. secure empty. 22 | # v0.5 2011-07-04 - Morgan Aldridge 23 | # Support for trashing/emptying using Finder via AppleScript, 24 | # when available. 25 | # v0.5.1 2012-03-29 - Matt Brictson & Morgan Aldridge 26 | # Fixed bug where cwd would get trashed instead of specified 27 | # file/path if it contained a relative path when trashing 28 | # using Finder via AppleScript. 29 | # v0.5.2 2012-11-30 - Morgan Aldridge 30 | # Merged in fix for realpath() implementation to fix 31 | # assumption that relative paths were in root directory. 32 | # Also correctly warns of missing files/directories. 33 | # v0.5.3 2013-08-23 - huyz 34 | # Fixed determination of Finder's PID when Path Finder is running 35 | # v0.6 2014-01-30 - Lucas Morales 36 | # Added long argument forms. 37 | # v0.6.1 2014-05-12 - Matt Torok 38 | # Supress `osascript` output when moving files to trash. 39 | # v0.7 2021-02-26 - Morgan Aldridge 40 | # Check for Full Disk Access on macOS >= 10.15. 41 | # v0.7.1 2021-04-09 - Morgan Aldridge 42 | # Improved Full Disk Access instructions. 43 | # 44 | 45 | # global variables 46 | verbose=false 47 | user=$(whoami) 48 | uid=$(id -u "$user") 49 | finder_pid=$(ps -u "$user" | grep CoreServices/Finder.app | grep -v grep | awk '{print $1}') 50 | v='' 51 | 52 | # print usage instructions (help) 53 | function usage() { 54 | printf "Usage: trash [options] file ...\n" 55 | printf " -v --verbose verbose output\n" 56 | printf " -h --help print these usage instructions\n" 57 | printf " -l --list list trash contents\n" 58 | printf " -e --empty empty trash contents\n" 59 | printf " -s --sempty secure empty trash contents\n" 60 | } 61 | 62 | # determine whether we have full disk access 63 | function have_full_disk_access() { 64 | if [ $(sw_vers -productVersion | cut -d . -f 1) -lt 11 ] && [ $(sw_vers -productVersion | cut -d . -f 2) -lt 15 ] ; then 65 | true 66 | else 67 | sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db 'select * from access' > /dev/null 2>&1 && true || false 68 | fi 69 | } 70 | 71 | # determine whether we can script the Finder or not 72 | function have_scriptable_finder() { 73 | # We must have a valid PID for Finder, plus we cannot be in `screen` (another thing that's broken) 74 | if [[ ($finder_pid -gt 1) && ("$STY" == "") ]]; then 75 | true 76 | else 77 | false 78 | fi 79 | } 80 | 81 | ## 82 | ## Convert a relative path to an absolute path. 83 | ## 84 | ## From http://github.com/morgant/realpath 85 | ## 86 | ## @param string the string to converted from a relative path to an absolute path 87 | ## @returns Outputs the absolute path to STDOUT, returns 0 if successful or 1 if an error (esp. path not found). 88 | ## 89 | function realpath() 90 | { 91 | local success=true 92 | local path="$1" 93 | 94 | # make sure the string isn't empty as that implies something in further logic 95 | if [ -z "$path" ]; then 96 | success=false 97 | else 98 | # start with the file name (sans the trailing slash) 99 | path="${path%/}" 100 | 101 | # if we stripped off the trailing slash and were left with nothing, that means we're in the root directory 102 | if [ -z "$path" ]; then 103 | path="/" 104 | fi 105 | 106 | # get the basename of the file (ignoring '.' & '..', because they're really part of the path) 107 | local file_basename="${path##*/}" 108 | if [[ ( "$file_basename" = "." ) || ( "$file_basename" = ".." ) ]]; then 109 | file_basename="" 110 | fi 111 | 112 | # extracts the directory component of the full path, if it's empty then assume '.' (the current working directory) 113 | local directory="${path%$file_basename}" 114 | if [ -z "$directory" ]; then 115 | directory='.' 116 | fi 117 | 118 | # attempt to change to the directory 119 | if ! cd "$directory" &>/dev/null ; then 120 | success=false 121 | fi 122 | 123 | if $success; then 124 | # does the filename exist? 125 | if [[ ( -n "$file_basename" ) && ( ! -e "$file_basename" ) ]]; then 126 | success=false 127 | fi 128 | 129 | # get the absolute path of the current directory & change back to previous directory 130 | local abs_path="$(pwd -P)" 131 | cd "-" &>/dev/null 132 | 133 | # Append base filename to absolute path 134 | if [ "${abs_path}" = "/" ]; then 135 | abs_path="${abs_path}${file_basename}" 136 | else 137 | abs_path="${abs_path}/${file_basename}" 138 | fi 139 | 140 | # output the absolute path 141 | echo "$abs_path" 142 | fi 143 | fi 144 | 145 | $success 146 | } 147 | 148 | if ! have_full_disk_access ; then 149 | printf "%s requires Full Disk Access!\n\n" "$(basename "$0")" 150 | printf "Please go to System Preferences > Security & Privacy > Privacy > Full Disk Access,\n" 151 | printf "press the '+' button, and add:\n\n" 152 | printf "1. Your terminal application (usually /Applications/Utilities/Terminal.app)\n" 153 | printf "2. /usr/libexec/sshd-keygen-wrapper (if you plan to connect via SSH)\n" 154 | exit 1 155 | fi 156 | 157 | # see if any arguments were passed in 158 | if [ $# -gt 0 ]; then 159 | # if so, step through them all and process them 160 | while [ $# -gt 0 ]; do 161 | # see if the user intended us to run in verbose mode 162 | if [ "$1" = "-v" ] || [ "$1" = "--verbose" ]; then 163 | shift 164 | verbose=true 165 | # see if the user requested help 166 | elif [ "$1" = "-h" ] || [ "$1" = "--help" ]; then 167 | shift 168 | usage 169 | exit 170 | # see if the user requested a list of trash contents 171 | elif [ "$1" = "-l" ] || [ "$1" = "--list" ]; then 172 | shift 173 | num_volumes=0 174 | total_blocks=0 175 | # list file contents & calculate size for user's .Trash folder 176 | if find "/Users/${user}/.Trash" -depth 1 ! -depth 0; then 177 | num_volumes=$(( $num_volumes + 1 )) 178 | blocks=$(du -cs "/Users/${user}/.Trash" | tail -n 1 | cut -f 1) 179 | total_blocks=$(( $total_blocks + $blocks )) 180 | fi 181 | # list file contents & calculate size for volume-specific .Trashes folders 182 | for file in /Volumes/*; do 183 | if [ -d "$file" ]; then 184 | folder="${file}/.Trashes/${uid}" 185 | if [ -d "${folder}" ]; then 186 | if find "$folder" -depth 1 ! -depth 0; then 187 | num_volumes=$(( $num_volumes + 1 )) 188 | blocks=$(du -cs "$folder" | tail -n 1 | cut -f 1) 189 | total_blocks=$(( $total_blocks + $blocks )) 190 | fi 191 | fi 192 | fi 193 | done 194 | # convert blocks to human readable size 195 | size=0 196 | if (( $total_blocks >= 2097152 )); then 197 | size=$(bc <<< "scale=2; $total_blocks / 2097152") 198 | size="${size}GB" 199 | elif (( $total_blocks >= 2048 )); then 200 | size=$(bc <<< "scale=2; $total_blocks / 2048") 201 | size="${size}MB" 202 | else 203 | size=$(bc <<< "scale=2; $total_blocks / 2") 204 | size="${size}K" 205 | fi 206 | printf "%s across %s volume(s).\n" "$size" $num_volumes 207 | exit 208 | # see if the user requested to empty the trash contents 209 | elif [ "$1" = "-e" ] || [ "$1" = "--empty" ]; then 210 | shift 211 | # determine if we can tell Finder to empty trash via AppleScript 212 | if have_scriptable_finder; then 213 | if $verbose; then printf "Telling Finder to empty trash... "; fi 214 | if /usr/bin/osascript -e "tell application \"Finder\" to empty trash" ; then 215 | if $verbose; then printf "Done.\n"; fi 216 | exit 217 | else 218 | if $verbose; then printf "ERROR!\n"; fi 219 | exit 1 220 | fi 221 | # if Finder isn't scriptable, we'll manually empty the trash ourselves 222 | else 223 | if $verbose; then v="-v"; fi 224 | # confirm that the user wants to empty the trash 225 | printf "Are you sure you want to empty the trash (this cannot be undone)? " 226 | read confirm 227 | if [ "$confirm" = "y" ]; then 228 | printf "Emptying trash...\n" 229 | # delete the contents of user's .Trash folder 230 | find "/Users/${user}/.Trash" -depth 1 ! -depth 0 -print0 | xargs -0 rm $v -r 231 | # delete the contents of the volume-specific .Trashes folders 232 | for file in /Volumes/*; do 233 | if [ -d "$file" ]; then 234 | folder="${file}/.Trashes/${uid}" 235 | if [ -d "$folder" ]; then 236 | find "$folder" -depth 1 ! -depth 0 -print0 | xargs -0 rm $v -r 237 | fi 238 | fi 239 | done 240 | printf "Done.\n" 241 | fi 242 | exit 243 | fi 244 | # see if the user requested to securely empty the trash contents 245 | elif [ "$1" = "-s" ] || [ "$1" = "--sempty" ]; then 246 | shift 247 | # determine if we can tell Finder to securely empty trash via AppleScript 248 | if have_scriptable_finder; then 249 | if $verbose; then printf "Telling Finder to securely empty trash... "; fi 250 | if /usr/bin/osascript -e "tell application \"Finder\" to empty trash with security" ; then 251 | if $verbose; then printf "Done.\n"; fi 252 | exit 253 | else 254 | if $verbose; then printf "ERROR!\n"; fi 255 | exit 1 256 | fi 257 | # if Finder isn't scriptable, we'll manually empty the trash ourselves 258 | else 259 | if $verbose; then v="-v"; fi 260 | # confirm that the user wants to securely empty the trash 261 | printf "Are you sure you want to securely empty the trash (this REALLY cannot be undone)? " 262 | read confirm 263 | if [ "$confirm" = "y" ]; then 264 | printf "Securely emptying trash...\n" 265 | # securely delete the contents of user's .Trash folder 266 | find "/Users/${user}/.Trash" -depth 1 ! -depth 0 -print0 | xargs -0 srm $v -r 267 | # securely delete the contents of the volume-specific .Trashes folders 268 | for file in /Volumes/*; do 269 | if [ -d "$file" ]; then 270 | folder="${file}/.Trashes/${uid}" 271 | if [ -d "$folder" ]; then 272 | find "$folder" -depth 1 ! -depth 0 -print0 | xargs -0 srm $v -r 273 | fi 274 | fi 275 | done 276 | printf "Done.\n" 277 | fi 278 | exit 279 | fi 280 | # handle remaining arguments as if they were files 281 | else 282 | #printf "argument: '%s'\n" $1 283 | #printf "destination: '%s'\n" $TRASH 284 | if $verbose; then v="-v"; fi 285 | 286 | # does the file we're trashing exist? 287 | if [ ! -e "$1" ]; then 288 | printf "trash: '%s': No such file or directory\n" "$1" 289 | else 290 | # determine if we'll tell Finder to trash the file via AppleScript (very easy, plus free undo 291 | # support, but Finder must be running for the user and is DOES NOT work from within `screen`) 292 | if have_scriptable_finder; then 293 | # determine whether we have an absolute path name to the file or not 294 | if [ "${1:0:1}" = "/" ]; then 295 | file="$1" 296 | else 297 | # expand relative to absolute path 298 | if $verbose; then printf "Determining absolute path for '%s'... " "$1"; fi 299 | file="$(realpath "$1")" 300 | if [ $? -eq 0 ]; then 301 | if $verbose; then printf "Done.\n"; fi 302 | else 303 | if $verbose; then printf "ERROR!\n"; fi 304 | fi 305 | fi 306 | if $verbose; then printf "Telling Finder to trash '%s'... " "$file"; fi 307 | if /usr/bin/osascript -e "tell application \"Finder\" to delete POSIX file \"$file\"" >/dev/null ; then 308 | if $verbose; then printf "Done.\n"; fi 309 | else 310 | if $verbose; then printf "ERROR!\n"; fi 311 | fi 312 | # Finder isn't available for this user, so don't rely on it (we'll do all the dirty work ourselves) 313 | else 314 | # determine whether we should be putting this in a volume-specific .Trashes or user's .Trash 315 | IFS=/ read -r -d '' _ _ vol _ <<< "$1" 316 | if [[ ("${1:0:9}" == "/Volumes/") && (-n "$vol") && ($(readlink "/Volumes/$vol") != "/") ]]; then 317 | trash="/Volumes/${vol}/.Trashes/${uid}/" 318 | else 319 | trash="/Users/${user}/.Trash/" 320 | fi 321 | # create the trash folder if necessary 322 | if [ ! -d "$trash" ]; then 323 | mkdir $v "$trash" 324 | fi 325 | # move the file to the trash 326 | if [ ! -e "${trash}$1" ]; then 327 | mv $v "$1" "$trash" 328 | else 329 | # determine if the filename has an extension 330 | ext=false 331 | case "$1" in 332 | *.*) ext=true ;; 333 | esac 334 | 335 | # keep incrementing a number to append to the filename to mimic Finder 336 | i=1 337 | if $ext; then 338 | new="${trash}${1%%.*} ${i}.${1##*.}" 339 | else 340 | new="${trash}$1 $i" 341 | fi 342 | while [ -e "$new" ]; do 343 | ((i=$i + 1)) 344 | if $ext; then 345 | new="${trash}${1%%.*} ${i}.${1##*.}" 346 | else 347 | new="${trash}$1 $i" 348 | fi 349 | done 350 | 351 | #move the file to the trash with the new name 352 | mv $v "$1" "$new" 353 | fi 354 | fi 355 | fi 356 | shift 357 | fi 358 | done 359 | else 360 | printf "No files were specified to be moved to the trash.\n\n" 361 | usage 362 | fi 363 | -------------------------------------------------------------------------------- /src/urlmnt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # urlmnt - Mount an afp/smb/ftp/webdav url as a volume in Mac OS X 5 | # 6 | 7 | # TO DO: 8 | # - 9 | 10 | # Reference: 11 | # http://www.macosxhints.com/article.php?story=2001120201020569 12 | 13 | # global variables 14 | VERBOSE=0 15 | HELP=0 16 | 17 | # print usage instructions (help) 18 | function usage() { 19 | printf "Usage: urlmnt [options] url ...\n" 20 | printf " -v verbose output\n" 21 | printf " -h print these usage instructions\n" 22 | } 23 | 24 | # see if any arguments were passed in 25 | if test $# -gt 0 ; then 26 | # if so, step through them all and process them 27 | print "test\n" 28 | else 29 | printf "You did not specify the URL of the volume to mount.\n\n" 30 | usage 31 | fi -------------------------------------------------------------------------------- /src/with: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 32bitSafePerl 2 | 3 | # 4 | # with - Set the application which will be used to open files 5 | # by Launch Services and the Finder. 6 | # 7 | # v1.0 2011-06-08 - David Kendal 8 | # Initial version. 9 | # 10 | 11 | use warnings; 12 | use strict; 13 | use Getopt::Long; 14 | use File::Basename; 15 | use Cwd qw(realpath); 16 | use Mac::Processes; 17 | 18 | my $script = basename($0); 19 | 20 | my $creator = ''; 21 | my $path = ''; 22 | my $bundle = ''; 23 | my $app = ''; 24 | my $help; 25 | my $open; 26 | 27 | if (!$ARGV[0]) { 28 | $help = 1; 29 | } 30 | 31 | GetOptions("creator|c=s" => \$creator, 32 | "path|p=s" => \$path, 33 | "bundle|i=s" => \$bundle, 34 | "app|a=s" => \$app, 35 | "help|h" => \$help, 36 | "open|o" => \$open); 37 | 38 | if ($help) { 39 | print <', $ENV{"HOME"}.'/._tmp_res' or die "Problem opening resource fork file: $!"; 94 | print RES $resource; 95 | close RES; 96 | 97 | foreach (@ARGV) { 98 | my $file = realpath($_); 99 | if (!-e $file) { 100 | print STDERR $script.": file not found: $file\n"; 101 | } 102 | 103 | system 'Rez', $ENV{"HOME"}.'/._tmp_res', '-a', '-o', $file; 104 | system 'open', $file if $open; 105 | } 106 | 107 | system 'rm', $ENV{"HOME"}.'/._tmp_res'; 108 | --------------------------------------------------------------------------------