Weekly News Summary for Admins – 2017-05-06

We had vacation last week, so summary is a bit late!

On Scripting OS X

To support Scripting OS X, consider buying one (or both) of my books. Thank you!

If you have already bought and read the books, please leave a review on the iBooks Store. Reviews are important to help new potential readers make the purchase decision. Thank you (again)!

News and Opinion

Conferences

Support and HowTos

To Listen

MacSysAdmin 2017 Conference Open for Registration

You can now register for the MacSysAdmin Conference in Göteborg (Sweden) which will go from October 3-6.

It is a great conference and I have wanted to go for years, but never managed to make it. The list of speaker has been and is really very impressive.

So I am very proud to announce that not only will I be attending but also presenting a session on macOS bash scripting this year! (so literally a session on ‘Scripting OS X… macOS’)

Looking forward to seeing you all there!

Configuring bash with aliases and functions

In the previous posts we talked about which files you could use to customize your bash environment.

There are (mainly) three customizations you can perform:

This post will look at some useful aliases and functions.

bash aliases

Note: bash aliases are something completely different from Finder aliases. The closest shell equivalent for Finder alias files are symbolic links.

bash aliases are basically text substitutions. For example a common alias is to define:

alias ll="ls -l"

You can type this alias definition directly into bash but then the definition will only exist for that particular shell. When you open a new terminal window it will know nothing of this alias. If you want an alias to exist in all your shells (and usually you do), you need to add the above line to your .bash_profile or .bashrc. (Learn about the difference here.)

Whenever you modify the .bash_profile or .bashrc it will not automatically be loaded into any shell that is already open, you either have to close the Terminal window and open a new one or write

$ source ~/.bash_profile

(or ~/.bashrc) to load the new settings.

Once you have set the alias, anytime you type ll at the start of a command, bash will replace it with ls -l before executing. Since subsequent arguments are left alone they will just be picked up by the substituted ls command, so if you type ll -a bash will substitute that to ls -l -a and it will work just your would expect.

You can make the alias more complex if you want:

alias lll="ls -alhTOe@"

If you always want to use the long format of ls, you can alias the ls command itself:

alias ls="ls -l"

Then, when ever you type ls it will be replaced with ls -l. Note the lack of spaces around the ‘=’, as usual when assigning values in bash.

Uncovering the alias

You can unset or delete an alias with the unalias command:

$ unalias ls

This will return to the default ls command (for this bash instance only).

Since alias substitution only happens at the beginning of the command, you can also bypass it once by starting the command line with a different character:

$ "ls" -S
$ \ls -S

Either way of typing this will use the original ls command, avoiding substitution, just this once.

If you are unsure if a command has an alias or want to know what gets substituted, then you can have the alias definition printed with

$ alias ls
alias ls='ls -l'
$ alias lll
alias lll='ls -alhTOe@'

You can also list all defined aliases by running alias without any arguments:

$ alias
alias ll='ls -l'
alias lll='ls -alhTOe@'
alias ls='ls -l'

Some Alias examples

Some users like to alias the potentially dangerous commands rm, mv and cp with the -i option, which forces the user to confirm when a file is going to be deleted or overwritten:

alias rm="rm -i"
alias mv="mv -i"
alias cp="cp -i"

Then if you do something that could destroy an existing file you will be prompted to confirm:

$ rm my_important_file 
remove my_important_file? 

You can still use \rm to bypass the alias, if you believe you know what you are doing and want to avoid being prompted several times.

You can add short cuts to cd to parent folders:

alias ..="cd .."
alias ...="cd ../.."
alias cd..="cd .."

Since alias substitution only takes place at the beginning of the command, you can still use .. normally as an argument.

Note that the last alias cd.. is a substitution of a common typo. Since the output of the alias is not checked for further alias substitutions you cannot use alias cd..=.. to define it using the previous alias. Each alias must stand for itself.

You can also use aliases to make complex or hard to memorize commands easier to remember:

alias badge="tput bel"

The first command will play a system alert and set a ‘badge’ on the terminal application if it is in the background. This is useful to get notified of long running commands:

$ hdiutil convert image.sparseimage -format UDZO image.dmg; badge;

The substitution can be long and multiple commands can be separated by ;

alias dockspace="defaults write com.apple.dock persistent-apps -array-add '{\"tile-type\"=\"spacer-tile\";}'; killall Dock;"

The dockspace alias uses this hack to add a spacer item to the dock.

We have seen earlier with the ll alias that any text or arguments after the alias will just be added to the substituted command. We can use this to our advantage:

alias reveal='open -R'
alias xcode='open -a Xcode'
alias pacifist='open -a Pacifist'

All of these commands will require a file path as an ‘argument’ to work properly.

Functions: beyond the alias

While bash aliases are useful and easy to define, they have limitations. Aliases will only be replaced at the beginning of the command prompt. Also additional arguments will be appended after the replacement. If you need to insert additional arguments somewhere in the replacement, you cannot achieve that with aliases.

For example, to display the man page for ls in the Preview application you need the following commands:

$ man -t ls | open -f -a "Preview"

If you wanted to alias this you would have to insert the arguments after man -t and before the pipe. Since aliases, only substitute the beginning of the command, this will not work. we need to define a bash function:

function preman() {
    man -t $@ | open -f -a "Preview"
}

Sometimes you will see bash functions defined without the function keyword, but I prefer to use it since it makes the code more readable.

The $@ will be replaced with all the arguments passed into the function, so when use the function

$ preman ls

the argument ls will be substituted where the $@ is.

You can also use the positional argument variables $1, $2, etc:

function recipe-open() {
    open "$(autopkg info '$1' | grep 'Recipe file path' | cut -c 22-)"
}

Within functions the full power of bash scripting is at your service. You can even call other scripting languages.

# prints the path of the front Finder window. Desktop if no window open
function pwdf () {
    osascript <<EOS
        tell application "Finder"
            if (count of Finder windows) is 0 then
                set dir to (desktop as alias)
            else
                set dir to ((target of Finder window 1) as alias)
            end if
            return POSIX path of dir
        end tell
EOS
}

# changes directory to frontmost 
alias cdf='pwdf; cd "$(pwdf)"'

The last alias cdf has to be defined as an alias. Since a function or script could not change the directory for the current shell, you have to use alias substitution to get the cd.

In general functions (and scripts) are more powerful and versatile than aliases, but aliases provide and easy and comparatively safe way to customize the your shell environment.

Putting it all together

Here is a sample .bash_profile with many of the examples given here and in the previous article. You can use this as a starting point for your own bash configuration.

Weekly News Summary for Admins – 2017-04-29

It is vacation time here on Scripting OS X. Updates will be sporadic…

On Scripting OS X

To support Scripting OS X, consider buying one (or both) of my books. Thank you!

If you have already bought and read the books, please leave a review on the iBooks Store. Reviews are important to help new potential readers make the purchase decision. Thank you (again)!

Posts and Opinion

Support and HowTos

To Listen

On bash Environment Variables

This is vacation time with a holiday in the Netherlands. Posting will be more sporadic than usual.

In the previous post we talked about which files you could use to customize your bash environment.

There are (mainly) three customizations you can perform:

This post will look at some useful environment variables.

Choose your own PATH

The PATH environment variable contains a list of directories that bash will search through for commands. You can show your current path by echoing the PATH variable:

$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:

This is the default PATH for a ‘clean’ macOS. Depending on what you have installed, there may be more elements in your PATH. The individual directories are separated by a colon :. When you enter a command, like for example systemsetup at the prompt, bash will search through the directories in PATH in the order until it finds a match.

Note: if there is ever any question on which command is actually executed, you can use the which command to get the effective path to a given command from the shell:

$ which python
/usr/bin/python
$ which systemsetup
/usr/sbin/systemsetup
$ which bash
/bin/bash

You might have installed other (newer versions) of certain tools (such as python or bash itself). Depending on how you install them they may be installed in a way that overrides the tool that is part of macOS. This may lead to problems when scripts assume the system version of a tool.

You can add your own directories by either appending (safe) or prepending (risky) your own directories to the PATH variable:

PATH=$PATH:~/bin
export PATH

This will add the ~/bin directory (which does not exist by default, you have to create and fill your own) to your PATH. The export command tells bash it should export this variable to child processes.

You can add more than one directory to the PATH, either at once, or one per line:

export PATH=$PATH:~/bin:~/Projects/scripts

or

PATH=$PATH:~/bin
PATH=$PATH:~/Projects/scripts
export PATH

Since bash stops looking when it finds a match, the order of the directories in the PATH is important. When you place your directories before the default directories you can override some of the system commands. This can be powerful, but also very dangerous. There are safer methods for overriding system commands. You should add your directory to the end of the PATH to be safe.

Note: the PATH (and other environment variables) are exported to child-shells and scripts executed from the shell. However, not every tool executes directly from a shell, or from a shell with your configuration file. So you should never assume PATH (or any other environment variable) is set to anything specific (or even set) when you write a script. You can either set the PATH at the beginning of a script, or use absolute paths for commands in scripts such as /usr/bin/defaults or /usr/bin/pkgbuild. You can determine the path to a command with the which command.

Prompt Me!

You can also configure the command prompt, i.e. the text that bash displays before you enter a command. By default the macOS prompt looks like this:

hostname:currentDir user$ 

The configuration for the prompt is stored in the PS1 environment variable. You can see the default value by echoing it:

$ echo $PS1
\h:\W \u\$

The letters with the backslash \ in the prompt variable are placeholders that will be replaced with a specific value when the prompt is printed out.

  • \h will be replaced with the hostname of the computer up to the first . (\H would be the complete hostname)
  • \W will be the basename of the current working directory
  • \u is the current user
  • \$ will be $ unless the current user is root or effectively elevated to root privileges, in which case it will be #. This is very useful as a warning when working with elevated privileges.

Since I am the only user on my Mac, and I also know which Mac I am sitting at, I have changed my prompt to something simpler:

export PS1="\W \$ "

Note the space after the \$. If you forget that the cursor will stick right on the $.

(When I log in to a different Mac with ssh the different prompt also reminds me that this shell is on a different Mac.)

You can find a list of escape sequences here.

You can also add color to the prompt data, but the escape sequences will look very messy very quickly. To have the directory name in gray, you use:

export PS1="\[\e[1;30m\]\W\[\e[m\] \\$ "

List in Color

You can also tell bash to color for some other commands, such as ls. To do that you just have to set the CLICOLOR variable:

export CLICOLOR=1

Change the Editor

Some terminal commands will open a text editor directly. For example git may open a text editor so you can enter a commit message. By default the system will open vi but you can change the editor that will be used. There are even some graphical editors that can be used this way. The environment variable for this behavior is EDITOR.

vi or vim:

export EDITOR=vi
export EDITOR=vim

emacs:

export  EDITOR=emacs

nano or pico:

export EDITOR=nano
export EDITOR=pico

TextEdit:

export EDITOR="open -f"

BBEdit:

export EDITOR="bbedit -w --resume"

TextMate:

export EDITOR="mate -w"

SublimeText:

export EDITOR="subl -n -w"

Atom:

export EDITOR="atom --wait"

Note: the -w or --wait argument tells the UI application that there is another process waiting for their output.

Conclusion

Putting all of this together, you can add these lines to your `.bash_profile` or `.bashrc`:

Up Next?

In the next post we will look at bash aliases and functions.

Weekly News Summary for Admins – 2017-04-21

MacAdmins Slack passed 10k users! Congratulations to everyone who has made this possible. This is such a wonderful community. (If you are not yet a member, go join now!)

On Scripting OS X

To support Scripting OS X, consider buying one (or both) of my books. Thank you!

If you have already bought and read the books, please leave a review on the iBooks Store. Reviews are important to help new potential readers make the purchase decision. Thank you (again)!

Updates and Releases

Posts and Opinion

Support and HowTos

To Listen

To Watch

Some Useful AutoPkg Processors

I have updated some of the shared processors in my AutoPkg repository. I find them useful for my AutoPkg workflow and testing recipes. I hope they may be useful for you, too.

You can find more detailed information on the processors in the scriptingosx-recipes repository. To add the repository to your AutoPkg use this command

$ autopkg repo-add scriptingosx-recipes

FileTemplate

This processor can be used to read a file with placeholder variables and write the result to a new file (presumably in the package being built). I use the FileTemplate processor in the FirefoxPrefs.pkg recipe to insert a javascript settings file with the proper values.

String sequences in the template file which are enclosed with % symbols such as %version% or %homepage_url% will be replaced with the value from the autopkg variable. You can use variables defined or obtained in previous recipe steps (e.g. %version% or %NAME%) or add additional values as input variables for the FileTemplate processor (see homepage_url in FirefoxPrefs.pkg)

See the firefox_AA.cfg.template file for an example.

Note: this is a very basic way of setting Firefox preferences. It works great for my use case (supressing update dialogs and setting home page url in student labs). If you need more control over Firefox behavior in your packaging process, look at CCK and use Greg’s FirefoxAutoconfig recipes.

Post Processors

The other processors are designed to be run as post-processors. You can add them to your AutoPkg workflow like this:

$ autopkg run Recipe1.pkg Recipe2.pkg --post com.scriptingosx.processors/Notification

Or with a plist format recipe list.

These are fairly simple post-processors. They can serve as a useful example for how to write and use post-processors.

Notification

This will show a user notification when the processor detects a new download or that a new package was built. (May act strangely when run with no user logged in.)

RevealInFinder

This processor will open a new Finder window and reveal (select) the new file. It will either use a newly archived file, a newly built package or a new download (in that order). (May not work when no user is logged in.)

Archive

When this processor detects a new download or that a new package was built it will copy it to a the directory given in archive_path. archive_path can be on a file server, but it is your responsibility that the share is mounted and available at that path. When the processor determines that a package was built it will copy that, otherwise it will look for a downloaded file. Existing files with the same name will be overwritten.

Even when you don’t copy to a server this can be useful to create an archive of packages outside of the ~/Library/AutoPkg/Caches folder so you can delete cache folders to remove problems with downloads or package building without losing your ‘history’ of packages.

To provide the required archive_path variable you can use the -k/--key argument or a plist format recipe list.

$ autopkg run Recipe1.pkg Recipe2.pkg --post com.scriptingosx.processors/Archive -k archive_path=~/Library/AutoPkg/Archive/ -k archive_subdir=%NAME%

Weekly News Summary for Admins – 2017-04-14

Happy Easter Week-end, everyone!

On Scripting OS X

Updates and Releases

Posts and Opinion

Other News

To Listen