Great article on some best practices for writing shell scripts:
Source: Shell Scripts Matter
Great article on some best practices for writing shell scripts:
Source: Shell Scripts Matter
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
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.
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.
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.)
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.)
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%
Happy Easter Week-end, everyone!
Note: bash_profile
is completely different from configuration profiles. Learn more about Configuration Profiles in my book: ‘Property Lists, Preferences and Profiles for Apple Administrators’
You can learn more about using Terminal and the shell on macOS in my my book: “macOS Terminal and Shell” — Thank you!
In this spontaneous series on the macOS Terminal I have often mentioned adding something as an alias
or function
to your bash_profile
or bashrc
. Obviously, you may wonder: how do I do that? And which file should I use?
When you work with the command line and the bash
shell frequently, you will want to customize the environment. This can mean changing environment variables, such as where the shell looks for commands or how the prompt looks, or adding customized commands.
For example, macOS sets the PATH
environment variable to /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
by default. This is a list of directories (separated by a colon ‘:
’) that the system searches through in order for commands. I like to add a folder in my home directory ~/bin
to that list, so that I can execute certain tools without needing to type out the full path. (e.g. munkipkg, quickpkg and ssh-installer).
In bash
you append to existing PATH
do this with:
export PATH="$PATH:~/bin"
You could type this command every time you open a new Terminal window (i.e. shell), or you can configure your shell to do this automatically.
Depending on which shell you use and how you start the shell, then certain script files will be executed which allow you to set up these customizations.
This article will talk about customizing bash
on macOS. Other shells and other operating systems may have other files or rules.
Thanks to the rich and long history of bash
the answer to which file you should put your configuration in, is surprisingly confusing.
There are (mainly) two user level files which bash
may run when a bash
shell starts. ~/.bash_profile
or ~/.bashrc
.
Both these files are on the first level of your home directory ~/
. Since the file names start with a .
Finder and normal ls
will not show them. You need to use ls -a
to see if they are present. Read more about invisible and hidden files here.
The usual convention is that .bash_profile
will be executed at login shells, i.e. interactive shells where you login with your user name and password at the beginning. When you ssh
into a remote host, it will ask you for user name and password (or some other authentication) to log in, so it is a login shell.
When you open a terminal application, it does not ask for login. You will just get a command prompt. In other versions of Unix or Linux, this will not run the .bash_profile
but a different file .bashrc
. The underlying idea is that the .bash_profile
should be run only once when you login, and the .bashrc
for every new interactive shell.
However, Terminal.app on macOS, does not follow this convention. When Terminal.app opens a new window, it will run .bash_profile
. Not, as users familiar with other Unix systems would expect, .bashrc
.
Note: The Xterm application installed as part of Xquartz runs .bashrc
when a new window opens, not .bash_profile
. Other third-party terminal applications on macOS may follow the precedent set by Terminal.app or not.
This is all very confusing.
There are two main approaches:
.bash_profile
, ignore all the special cases and be happy..bashrc
and source .bashrc
from .bash_profile
with the following code in .bash_profile
:if [ -r ~/.bashrc ]; then
source ~/.bashrc
fi
The if [ -r ... ]
tests wether a file exists and is readable and the source
command reads and evaluates a file in place. Sometimes you see
[ -r ~/.bashrc ] && . ~/.bashrc
(mind the spaces) Which is a shorter way to do the same thing.
Since either file can drastically change your environment, you want to restrict access to just you:
$ chmod 700 ~/.bash_profile
$ chmod 700 ~/.bashrc
No. There are more files which may be executed when a shell is created.
When bash
cannot find .bash_profile
it will look for .bash_login
and if that does not exist either .profile
. If .bash_profile
is present the succeeding files will be ignored. (though you can source
them in your .bash_profile
)
There is also a file /etc/profile
that is run for interactive login shells (and Terminal.app). This provides a central location to configure the shells for all users on a system. On macOS /etc/profile
sets the default PATH
with the path_helper
tool and then source
s /etc/bashrc
which (you guessed) would be the central file for all users that is executed for non-login interactive shells. For macOS Terminal.app /etc/bashrc
sets the default prompt and then itself sources /etc/bashrc_Apple_Terminal
which sets up the session persistence across logins.
So in macOS Terminal.app, before you even see a prompt, these scripts will be run:
/etc/profile
/etc/bashrc
/etc/bashrc_Apple_Terminal
~/.bash_profile
~/.bash_profile
does not exists, ~/.bash_login
~/.bash_profile
nor ~/.bash_login
exist, ~/.profile
~/bash_profile
can optionally source ~/.bashrc
There is also a file ~/.inputrc
, where you can setup certain command line input options. One common example for this is to enable case-insensitive tab-completion. You can find a list of more options here.
Finally, there is ~/.bash_logout
which is run when a shell exits or closes.
Whichever file you choose, (I went with option one and have everything in .bash_profile
) now you want to put stuff in it.
Technically this is a script, so you can do anything you can code in bash
. However, usually the contents of a .bash_profile
or .bashrc
fall into one of three categories:
PATH
) or look and feel (PS1
) or set configuration for other commands or programs (CLICOLOR
)I will show some examples for each in the next post!
There have been many posts on Terminal on macOS recently. To catch up, here is a list of all the posts, so far:
open
Commandman
PagesAnd here are a few older posts that also fit well in this topic:
bash
case-insensitivefind
and git
I have a few more ideas and will continue this series as long as I can think of topics. If you have suggestions, please let me know on Twitter, the MacAdmins Slack or in the comments!
And for reading through all of this you get a reward! Download a two-page PDF with all the most important Terminal Keys and Commands! Print it and put them up next to your screen!
You can also download another two-page PDF reference for my book ‘Property Lists, Preferences and Profiles for Apple Administrators’.
man
PagesThis week’s big surprise is that Apple has let out some early news that they are working on a new Mac Pro. Even more interesting than new hardware is (for me) that Apple is trying to re-assure the pro and prosumer market that Apple cares about them.
Micheal Tsai has a great summary post.
When you frequently use Terminal, you will use man
pages. They contain tons of useful information on most of the tools and commands you use on the shell.
Note: update for macOS Ventura
However, the man
command’s user interface was designed for terminal output decades ago and does not really integrate well with the modern macOS UI. When you run the man
command the output will take over your current Terminal window and scrolling through long man pages can be tedious.
However, on macOS you do not have to man
like it’s 1989.
First solution is to use
$ open x-man-page://ls
instead of man
. This will open the man page in a new yellow Terminal window, so you can still see what you are actually doing, while reading the man
page. If the yellow is just too annoying for you, you can change the look of the window by changing the ‘Man Page’ window profile in the Terminal Preferences. Since this window shows the entire man page, you can scroll and even use ‘Find’ (Command-F) in this window.
Since typing this open command is a bit awkward, you can add a function to your bash profile or bashrc file:
function xmanpage() { open x-man-page://$@ ; }
Note: You could use xman
here. However, that will conflict with another command when you have X11/XQuartz installed.
You can also put ‘x-man-page:’ URLs in other applications, such as emails or chat applications. However, not all applications will recognize URLs starting with x-man-page
: as URLs, so your results may be mixed. It does work in Slack, even though Slack is skeptic of the links:
In Terminal, you can open a man
page from the context menu. Simply do a secondary (ctrl/right/two-finger) click on a word in a Terminal window and choose ‘Open man Page’ from the context menu. This will open the man
page in a separate window, like opening x-man-page
URLs.
Back in the early days of computing you could (or had to) convert man
pages into postscript output, so they would look nicer when printing. These options are still present and we can (ab)use them to show a man
page in Preview. (Please don’t waste paper printing man
pages.) The command for this is:
$ man -t ls | open -f -a "Preview"
The -t
options pipes the man
page into another tool (groff
) to reformat it into pdf which we then pipe into open
and send to the Preview application. (More on the open
command.) If you use this more frequently, you want to create a function for this in your bash profile or .bashrc
:
function preman() { man -t "$@" | open -f -a "Preview" ;}
You can also send a man page to a tex editor. Before you pipe the output into a text editor, you have to clean it up a bit:
$ MANWIDTH=80 MANPAGER='col -bx' man $@ | open -f
This will open in TextEdit. If your favored text editor can receive data from stdin, then replace the open -f
with its command. For BBEdit, this will work great:
$ MANWIDTH=80 MANPAGER='col -bx' man $@ | bbedit --clean --view-top -t "man $@"
And again, if want to use this method frequently, create a bash function for it.
The ManOpen application has been around for a long, long time, but amazingly, it still works on macOS Sierra. It will also display man
pages in a separate window. The main advantage MacOpen has over the other solutions here is that it will automatically detect other commands and highlight them as hyperlinks to their man
pages. There is also a command line tool, confusingly called openman
to open the app directly from the Terminal.
You can also create an Automator Service for this. Then you can open man pages from (nearly) any application with a keystroke or from the context menu. I described this in an older post on man pages.
The updates for macOS 10.12.4, iOS 10.3 watchOS and tvOS(!) dropped this week. Lot’s of Apple support pages to catch up with:
I have talked a lot about the Terminal recently. One the most basic elements of a command shell is the prompt line, where you enter the command. Sometimes you have to move the cursor around the prompt line and there are more efficient ways of doing this than hitting the left arrow multiple times.
These key commands are for the default Terminal configuration with the bash
shell.
Often you want to retry or re-use a previous command. Hitting the up arrow will recall the previous command, leaving the cursor at the end of the line, so you can either hit return to execute the command or edit it. You can hit the up arrow multiple times to go back further in your command history. You can also hit the down arrow to move forward again.
Instead of hitting the up arrow several times, you can also hit ctrl-R
and start typing a command you used before. This will search through the history backwards and recall the latest command you used starting with what you typed. So ctrl-R
and then typing cd
will recall the last cd
command you typed. When you get ‘stuck’ in the search mode you can leave it with ctrl-G
.
Once you have recalled (or typed) a command and want to edit it, you will have to move the cursor.
The left and right arrows move the cursor left and right. You can use (option) ⌥-left (or right) to move word by word. You can use ctrl-A
to jump to the beginning of the line and ctrl-E
to jump to the end.
ctrl-XX
(hold ctrl and press ‘x’ twice) will jump to the beginning of the line and then back to the current position the second time you use it.
If you recalled a wrong command, then you can clear the entire part left of the cursor with ctrl-U
and the part right of the cursor with ctrl-K
. There does not seem to be a keystroke that just clears the entire current line, but most of the time your cursor will be at the end of the line, so ctrl-U
will do the trick.
And finally, you can option-click with the mouse pointer on a character in the command line to move the cursor there.
When you hold the option key, the mouse pointer will turn from the text insertion pointer to a cross. This is because the option key also switches text selection to rectangular. This can be useful when copying text from the table like output like ls -l
or df
.
As in any Mac application, you can double-click to select a word in the Terminal. However, for macOS a ‘word’ ends at special characters, including the /
. This is annoying when you want to select a file path. If you double-click while holding command-shift
Terminal will select the entire file path. This works for URLs, too.
Usually, you select some text to copy and paste it into the command prompt. You can take a short cut here, too. Terminal has a ‘Paste Selection’ command in the ‘Edit’ menu (command-shift-V). This way you save the ‘copy’ step.
And (as a recap from the post on marks) command-shift-A will select the output of the previous command.
When you are typing commands, file names or paths, then you can use the tab key ⇥ to save keystrokes and typos.
For example you can type cd ~/Doc⇥
and it will complete to cd ~/Documents/
. You can hit tab over and over at different parts of the command:
$ cd ~/Li⇥
$ cd ~/Library/Appl⇥
$ cd ~/Library/Application\ Su⇥
$ cd ~/Library/Application\ Support/
When there are multiple options to complete, bash
will play an alarm sound, if you then press tab for the second time, it will list all options:
$ cd ~/D⇥<beep>⇥
Desktop/ Documents/ Downloads/
Using tab-completion not only saves keystrokes, and time, but also reduces the potential for typos and errors. Tab-completion will also automatically escape problematic characters such as spaces:
$ cd /Library/Appl⇥
$ cd /Library/Application\ Support/
Note: usually bash
is case-sensitive. However, since the macOS filesystem (HFS+) is (for now) case-insensitive, you may want to switch tab-completion to be case-insensitive, too. The case-sensitivity of the file system may change with the upcoming APFS file system.
There is another form of completion which you invoke with pressing the esc(ape) key twice. This will replace bash ‘globbing’ characters such as ?
, *
and ~
. For example:
$ cd ~/Doc*[esc][esc]
$ cd /Users/armin/Documents
Note that this replaces the ~
and the *
. When there are multiple possible expansions, there will be an alert sound. You can press esc twice again to see all possible expansions:
$ cd ~/D*[esc][esc]<beep>[esc][esc]
Desktop/ Documents/ Downloads/