# -*- mode: shell-script -*- # Script to make the Mac more Linux-like # Version 1.61 (c) Silas S. Brown 2012-24. # Tested in zsh on macOS 11.4 through 14.4, # and in bash on Mac OS X 10.7 through 10.14. # This script can go in your ~/.zshrc (10.15+) # or ~/.bashrc and/or ~/.bash_profile (older Macs) # for example: # curl http://ssb22.user.srcf.net/setup/maclinux.txt >> ~/.bash_profile # or if you prefer you can keep it in a separate file # and source it from your startup files # (put "source ~/.maclinux" into .zshrc or .bashrc) # You might wish to also put it in BASH_ENV to ensure all # commands are copied to subshells, although commands without # hyphens and dots should be copied over anyway. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # If you want to compare this code to old versions, the old # versions are being kept in the E-GuideDog SVN repository at # http://svn.code.sf.net/p/e-guidedog/code/ssb22/setup # and on GitHub at https://github.com/ssb22/web-imap-etc # and on GitLab at https://gitlab.com/ssb22/web-imap-etc # and on Bitbucket https://bitbucket.org/ssb22/web-imap-etc # and at https://gitlab.developers.cam.ac.uk/ssb22/web-imap-etc # and in China: https://gitee.com/ssb22/web-imap-etc # although some early ones are missing. if [ -d /Volumes ] && [ "$(uname -s)" = Darwin ]; then # we're on a Mac # if in bash, avoid recursion: export __OldBashEnv="$BASH_ENV" ; unset BASH_ENV # Check SMART status of primary hard disk: diskutil info disk0|grep SMART|grep -v 'Status:[^A-z]*Verified$' && echo "Hard disk failing: backup NOW" && echo # Support installations of HomeBrew/MacTex/MacPorts/Fink: export PATH=/usr/texbin:/opt/local/bin:/opt/local/sbin:/usr/local/bin:/usr/local/sbin:$PATH:/usr/local/opt/coreutils/libexec/gnubin # (gnubin (from coreutils package) comes last so Mac versions take priority, just in case; coreutils has some commands not otherwise available e.g. factor, md5sum) export MANPATH=$MANPATH:/usr/local/opt/coreutils/libexec/gnuman [ ! "$ZSH_NAME" ] && [ -e /sw/bin/init.sh ] && . /sw/bin/init.sh for N in "$HOME"/Library/Python/*/bin; do [ -e "$N" ] && export PATH="$PATH:$N"; done # for pip install --user (without --user it's /usr/local/bin) if [ ! "$ZSH_NAME" ] && /usr/bin/which -s brew; then # don't need any of our aliases when running HomeBrew scripts & it's quicker to bypass this file when doing so brew () { BASH_ENV= $(/usr/bin/which brew) "$@" ; } ; export -f brew fi if ! /usr/bin/which -s wget; then # Set wget to curl with appropriate options. Useful # if you're in the habit of typing "wget". Note that # it is not real wget however and it won't recognise # wget options like -c. wget () { curl -L --remote-name-all --compressed "$@" ; } # for older curl: alias wget="curl -OL" export -f wget >/dev/null fi if ! /usr/bin/which -s watch; then # primitive emulation of the "watch" command watch () (while true; do clear; date; echo "$@"; echo; "$@"; sleep 2; done) export -f watch >/dev/null fi if ! /usr/bin/which -s pidof; then pidof () (pgrep "^-*$@"'$') export -f pidof >/dev/null fi top () { /usr/bin/top -stats $(if /usr/bin/top -l 1 -s 0 -stats mem >/dev/null 2>/dev/null; then echo pid,cpu,mem,uid,command; else echo pid,vsize,cpu,rsize,uid,command; fi) -o cpu $@ ; } # 11.x: -r with 'vsize' works but takes more CPU # 10.7: 'vsize' works w/out -r # 'state' works but takes more columns export -f top >/dev/null alias netstat-tl="lsof -iTCP -sTCP:LISTEN" alias netstat-t="lsof -iTCP -sTCP:ESTABLISHED" if ! /usr/bin/which -s airport && [ -e System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport ]; then airport () { /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport "$@" ; } export -f airport >/dev/null fi if ! /usr/bin/which -s free; then free () { top -l 1 -s 0 | grep -m 1 PhysMem ; } export -f free >/dev/null fi alias do-i-need-more-ram='if [ $((vm_stat|grep "^Page[io]"|sed -e "s/.*://" -e "s/\.$//";echo 1;echo +;echo /;echo p)|(dc 2>/dev/null || echo 11)) -lt 10 ]; then echo Maybe; else echo Probably not; fi' # 'maybe' if page outs are over 10% of page ins; +1 to avoid any possible division by zero if there have been no pageouts alias battery-status='pmset -g batt' # for MacBook laptops # Note: Anything with a hyphen in it must be an alias, not a # function (or at least not exported), or /bin/sh won't like # it and commands like "man" will fail. Hence the above # comment about BASH_ENV. # Define the /Applications as "open -a" commands so you # can type for example "libreoffice", "gimp", # "audacity" etc with a relative pathname and Mac open # will take care of translating it into an absolute # pathname and opening the appropriate Mac app with it. # Spaces are turned into hyphens, e.g. for google-chrome. # If a command already exists, we will append -app to it # (unless command-app already exists as well), for example # Emacs.app will become emacs-app if there's already emacs. # The version below unfortunately requires a temp file, # but it's usually faster than repeated calls to sed as in # previous versions of this script (1.38 and below). # (Bash can't source a /dev/fd from <(...), piping to a # source /dev/stdin invokes a subshell so it loses the # functions; no /dev/shm on Mac, so we have to use /tmp) # (see also comments around make-ramdisk function below). # TODO: if we must write to /tmp, can we keep it cached # so that new tabs in the Terminal window don't need to do # a disk write? (e.g. by putting whole script in an 'if' # clause which also does echo commands to declare the # functions, or cache the o/p of "declare" and somehow # remove what we didn't add), but how to check the age of # the cache? -newer /Applications? with timeout? # just make an 'update-functions' cmd for use when wanted? if [ -e /usr/bin/python3 ] ; then export DefineAppsPython=/usr/bin/python3 elif [ -e /usr/bin/python2 ] ; then export DefineAppsPython=/usr/bin/python2 else echo "maclinux cannot find system Python: most of our application shortcuts will NOT be defined" >&2; fi # TODO: search path for a user-installed Python? if ! /usr/bin/which -s python2 && /usr/bin/which -s python2.7; then python2 () { python2.7 "$@" ; } ; export -f python2 >/dev/null; fi # e.g. MacPorts on macOS 12.3 (no system python2) if [ "$DefineAppsPython" ]; then if ! /usr/bin/which -s python; then python () { "$DefineAppsPython" "$@" ; } ; export -f python >/dev/null; fi if ! /usr/bin/which -s jq && [ $(python --version 2>&1|sed -e 's/.* //' -e 's/\.//' -e 's/\..*//') -ge 26 ]; then jq () { python -m json.tool "$@" ; } ; export -f jq >/dev/null; fi DefineApps () { export T=$(mktemp /tmp/$PPID''XXXXXX) find "$@" '(' -type d -or -type l ')' -name '*.app' -prune -print0 2>/dev/null | xargs -0 "$DefineAppsPython" -c ' import os,sys,re from distutils.spawn import find_executable os.chdir("/usr/bin") # because at least some versions of distutils.spawn assume that PATH contains . def orApp(c): if not find_executable(c): return c elif not find_executable(c+"-app"): return c+"-app" def getF(Command,App): if not Command: return "" elif "." in Command or "-" in Command: return "alias \""+Command+"\"=\"open -W -a \\\""+App+"\\\"\"" else: return Command+" () { open -W -a \""+App+"\" \"$@\" ; } ; export -f "+Command+" >/dev/null" for App in sys.argv[1:]: Commands=[re.sub("[^a-z0-9._-]","",re.sub(".*/","",App)[:-4].replace(" ","-").lower())] if Commands[0].startswith("microsoft-"): Commands.append(Commands[0].replace("microsoft-","")) elif Commands[0]=="chmox" and not find_executable("kchmviewer"): Commands.append("kchmviewer") # probably easier to auto-complete, as chmox will suggest chmod also elif Commands[0] in ["adobe-reader","adobe-acrobat-reader-dc","adobe-acrobat-reader"] and not find_executable("acroread"): Commands.append("acroread") for Command in Commands: print (getF(orApp(Command),App))' >> $T . $T ; rm $T ; unset T; } else DefineApps () { false ; }; fi DefineApps /Applications /System/Applications /usr/local/Cellar/emacs if declare -f xcode >/dev/null; then DefineApps "$(declare -f xcode|grep -i '^\s*open.*/Xcode.app'|head -1|sed -e 's/[^"]*"//' -e 's/".*//')/Contents/Applications"; DefineApps "$(declare -f xcode|grep -i '^\s*open.*/Xcode.app'|head -1|sed -e 's/[^"]*"//' -e 's/".*//')/Contents/Developer/Applications"; fi #'# (might find iphone-simulator etc there) unset DefineApps if [ -e /Applications/MacPorts/VLC.app/Contents/MacOS/VLC ]; then # override the above "open": might need its command line # (but not everything listed by -H really works) vlc () { /Applications/MacPorts/VLC.app/Contents/MacOS/VLC "$@" ; } ; export -f vlc >/dev/null fi if [ -e /Applications/LibreOffice.app/Contents/MacOS/soffice ]; then # for command-line conversions soffice () { /Applications/LibreOffice.app/Contents/MacOS/soffice "$@" ; } ; export -f soffice >/dev/null fi # One slight annoyance is if you use some Macs that # have bbedit installed but others that just have # TextWrangler. Let's function-ify those edit commands: if /usr/bin/which -s bbedit; then edit () { bbedit "$@" ; } export -f edit >/dev/null elif /usr/bin/which -s edit; then bbedit () { edit "$@" ; } export -f bbedit >/dev/null else edit () { open -e "$@" ; } export -f edit >/dev/null # and leave bbedit undefined fi if [ -e /usr/libexec/java_home ] && ! [ "$JAVA_HOME" ] ; then export JAVA_HOME=$(/usr/libexec/java_home); fi if [ -e /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/*/jsc ] ; then # command-line Javascript interpreter not in default PATH: might as well make this available jsc () { /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/*/jsc "$@" ; } if ! /usr/bin/which -s node; then # might as well alias it, in case you're in the # habit of typing "node" rather than "jsc" (it's # not the same, but near enough for simple tests) node () { jsc "$@" ; } export -f node >/dev/null fi fi # Ditto for mixed Mac/Linux environments where you # can type "jmacs" on the Linux terminal to get a # quick emacs-like editor: if ! /usr/bin/which -s jmacs; then if /usr/bin/which -s emacs; then jmacs () { emacs -q "$@" ; } ; export -f jmacs >/dev/null else # emacs not present in macOS 11, but we might have it as an application: if declare -f emacs >/dev/null; then jmacs () { Emacs=$(declare -f emacs|grep -i '^\s*open.*/Emacs.app'|head -1|sed -e 's/[^"]*"//' -e 's/".*//')/Contents/MacOS/Emacs; if [ ! "$1" ]; then $Emacs -q -nw --eval '(switch-to-buffer "*scratch*")'; else $Emacs -q -nw "$@"; fi ; } ; export -f jmacs >/dev/null elif declare -f aquamacs >/dev/null; then # TODO: not tested jmacs () { $(declare -f emacs|grep -i '^\s*open.*/Aquamacs.app'|head -1|sed -e 's/[^"]*"//' -e 's/".*//')/Contents/MacOS/Aquamacs -q -nw "$@" ; } ; export -f jmacs >/dev/null fi fi fi # and make sure you can type "play" to play sound files if ! /usr/bin/which -s play; then play () { afplay "$@" ; } ; export -f play >/dev/null ; fi # and "umount" to eject auto-mounted USB sticks etc: umount () { diskutil umount "$@" ; } ; export -f umount >/dev/null eject () { drutil eject ; } ; export -f eject >/dev/null # for CDs # and the general "web browser" command used by some # scripts in Debian etc alias x-www-browser=open # should recognise a URL alias gnome-open=open # If you have installed Macfusion (which requires MacFUSE # or the MacFUSE compatibility layer of OSXFUSE) then # make its curlftpfs and sshfs commands available. Note # however that curlftpfs can be unreliable, e.g. some # versions do not properly truncate files when you try to # overwrite with less data, plus large transfers can break, # so some situations might be better using .netrc or TRAMP. # If you do use ftpfs and the server is running it via # inetd then you might need to increase the connections/min # allowed in inetd.conf, e.g. "ftp stream tcp nowait.32767" if declare -f macfusion >/dev/null; then export MF_Path="$(declare -f macfusion|grep -i '^\s*open .*/Macfusion.app'|head -1|sed -e 's/[^"]*"//' -e 's/".*//')" #' # (comment for some versions of Emacs syntax highlighting) export sshfs_Path="$MF_Path/Contents/PlugIns/sshfs.mfplugin/Contents/Resources/sshfs-static" export curlftpfs_Path="$MF_Path/Contents/PlugIns/ftpfs.mfplugin/Contents/Resources/curlftpfs_static_mg" unset MF_Path if ! /usr/bin/which -s curlftpfs && [ -e "$curlftpfs_Path" ]; then curlftpfs () { "$curlftpfs_Path" -o uid="$(id -u)" "$@" ; } ftpfs () { curlftpfs "$@" ; } # might as well export -f curlftpfs ftpfs >/dev/null else unset curlftpfs_Path fi if ! /usr/bin/which -s sshfs && [ -e "$sshfs_Path" ]; then sshfs () { "$sshfs_Path" "$@" ; } export -f sshfs >/dev/null else unset sshfs_Path fi fi # If you have installed fuse-ext2 then might as well set # its mke2fs (although usually you'd use fuse-ext2 or # mount -t fuse-ext2) if ! /usr/bin/which -s mke2fs && /usr/bin/which -s fuse-ext2.mke2fs; then mke2fs () { fuse-ext2.mke2fs "$@" ; } e2fsmount () { fuse-ext2 "$@" ; } export -f mke2fs e2fsmount >/dev/null fi ulimit -n 1024 # allow programs to have more open files # Setting the volume from the command line (this # doesn't exactly emulate the Linux commands on various # distributions but at least it's a start) - volume () { if [ ! "$1" ]; then # if no argument, print current percentage V=$(osascript -e "output volume of (get volume settings)") if [ "$V" = "missing value" ]; then echo "Cannot read current volume" echo "This can happen if a multi-output device has been set in audio-midi-setup" echo "(e.g. to copy computer's sound to QuickTime's recorder via BlackHole)" # (or SoundFlower on older Macs?) echo " - audio-midi-setup must be used to read volume" # Can still set it from command line IF it's a multi-output device, NOT if it's an aggregate device (splitting stereo channels to separate Bluetooth feeds etc) else echo "$V"; fi else osascript -e "set volume output volume $1"; fi } # older Macs just had "set volume" with a number 0 to 7 export -f volume >/dev/null # might as well have some iTunes command-line access too - # here's a function that plays a track whose name contains # whatever string you pass to the function: play-track () { osascript -e "tell application \"iTunes\" to play (get item 1 of (get every track of current playlist where name contains \"$*\"))"; } alias list-track-names="osascript -e \"set AppleScript's text item delimiters to \\\"@@@\\\"\" -e \"tell application \\\"iTunes\\\" to get name of (every track of current playlist) as string\"|sed -e $'s/@@@/\\\\\\n/g'|less -S" alias next-track='osascript -e "tell application \"iTunes\" to next track"' alias previous-track='osascript -e "tell application \"iTunes\" to previous track"' pause () { osascript -e "tell application \"iTunes\" to pause" ; } # resume from GUI for now (right-click on the dock) stop () { osascript -e "tell application \"iTunes\" to stop" ; } export -f pause stop >/dev/null # we can also do the following without sudo, as long as a desktop session is running: halt () { if [ ! "$SSH_CLIENT" ]; then [ ! "$(jobs -s)" ] && ( ( sleep 0.5 && osascript -e "tell application \"Finder\" to shut down") & ) ; exit ; else sudo shutdown -h now; fi ; } # (if the "exit" warns about stopped jobs, we won't do the shutdown) poweroff () { halt ; } reboot () { [ ! "$(jobs -s)" ] && ( ( sleep 0.5 && osascript -e "tell application \"Finder\" to restart") & ) ; exit ; } logout () { [ ! "$(jobs -s)" ] && ( ( sleep 0.5 && osascript -e "tell application \"System Events\" to log out") & ) ; exit ; } sleepnow () { if ! pmset -g assertions|grep PreventSystemSleep|grep -v '^\s*PreventSystemSleep\s*0$'; then (sleep 0.5; pmset sleepnow) & exit; else false; fi ; } alias suspend-mac=sleepnow export -f halt poweroff reboot logout sleepnow >/dev/null # (the "exit"s at the end of the above are so the terminal doesn't stop it with "are you sure", unless there are other terminal sessions) # Spotlight is required by app-store, e.g. for XCode updates (otherwise can get misleading error messages about account sign-in). But you might want Spotlight disabled at other times (especially when accessing removable media). alias spotlight-disable="sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.metadata.mds.plist" alias spotlight-enable="sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.metadata.mds.plist" updatedb () { sudo /usr/libexec/locate.updatedb ; } export -f updatedb >/dev/null # note that updatedb does not look in any user directories # that are chmod'd to 700 (user only) - be sure to chmod # a+rx any dirs you want it to scan # Here's a function to install arbitrary commands to run at desktop login # Notes: (1) PATH might be incomplete, (2) this maclinux won't have been executed (unless you source it in the script), # (3) if the script starts any background processes, it MUST do a "wait" before finishing if you don't want launchctl to terminate its background processes _install_login_command () { if ! [ -e "/$1" ]; then echo "Syntax: install-login-command executable-full-path"; echo "(see comments in maclinux for caveats)"; return; fi mkdir -p "$HOME/Library/LaunchAgents" L="$(echo "$1"|sed -e 's,.*/,,')" if [ -e "$HOME/Library/LaunchAgents/$L.plist" ] || [ ! "$L" ] || ! touch "$HOME/Library/LaunchAgents/$L.plist" 2>/dev/null; then # can't use executable name as plist name (exists or can't create), so make a generic one C=0; while [ -e "$HOME/Library/LaunchAgents/loginscript$C.plist" ]; do C=$[$C+1]; done; L=loginscript$C fi F="$HOME/Library/LaunchAgents/$L.plist"; (echo "";echo "Label$LProgram$1RunAtLoad") > "$F" launchctl load "$F" echo "Installed to $F" # (use "launchctl unload" and "rm" to uninstall) echo "To test now, do: launchctl start $L"; echo "(errors to /var/log/system.log)"; } # (change $HOME/Library/LaunchAgents to /Library/LaunchAgents to run on boot instead of login, but crontab @reboot can do that) alias install-login-command=_install_login_command _make_ramdisk () { case "$1" in "") echo "Syntax: make-ramdisk mountpoint size" # (or size mountpoint : will auto-reverse) echo "(512-byte blocks, min 1024 for 512k," echo "can specify as M or G instead)" false ;; [1-9]*) case "$2" in [1-9]*) echo "Mountpoint must not start with a number"; false ;; *) _make_ramdisk "$2" "$1" ;; esac ;; *) case "$2" in [1-9]*[gG]) _make_ramdisk "$1" "$[${2%[gG]}*2097152]" ;; [1-9]*[mM]) _make_ramdisk "$1" "$[${2%[mM]}*2048]" ;; *) mkdir "$1" && mount -t hfs "$(echo "$(newfs_hfs $(hdid -nomount "ram://$2"))"|sed -e 's,[^/]*/,/,' -e 's/ .*//' -e 's,/rdisk,/disk,')" "$1" ;; esac ;; esac ; } # (10.5+ can also do e.g. diskutil erasevolume HFS+ "maclinux-RAMdisk" $(hdiutil attach -nomount ram://1024) for /Volumes/maclinux-RAMdisk) alias make-ramdisk=_make_ramdisk _drop_ramdisk () { D="$(mount|grep "$(echo "$1"|sed -e 's,/$,,') "|sed -e 's/ .*//')" ; /sbin/umount "$1" ; hdiutil detach "$D" ; rmdir "$1" ; } alias drop-ramdisk=_drop_ramdisk export -f _make_ramdisk _drop_ramdisk >/dev/null # so can use from scripts (useful especially if have upgraded HDD to SSD and worried about excessive writes by script temp files) # NOTE: for making HFS+ disk images that will interoperate # with Linux, it's really best to do this IN LINUX (use dd # and then use mkfs.hfs from hfsprogs), rather than on the # Mac. That way it's less likely you'll end up with a dmg # that the Linux kernel refuses to mount for some reason. function xmanpage() { open x-man-page://$@ ; echo "To change appearance: Terminal / Preferences / Settings / Man page / (Text,Window)" ; } export -f xmanpage >/dev/null if ! /usr/bin/which -s xman; then xman () { xmanpage "$@" ; } ; export -f xman >/dev/null; fi # (if X11 is present then xman should be installed, but it may fail to read all manpage sections and UTF-8 won't display) function psmanpage() { man -t "$@" | open -f -a Preview ; } # will need Invert to change the colours of that export -f psmanpage >/dev/null alias man-terminal=xmanpage # for auto-complete "man" alias man-ps=psmanpage # Mac's multiline editing doesn't always work well with ANSI codes, even when \[..\] delimiters are used if [ ! "$SSH_CLIENT" ]; then # simpler prompt for a local Mac (hostname can be a bit long) [ "$ZSH_NAME" ] && PS1='mac:%~%# ' || PS1='mac:\w\$ ' elif [ "$(hostname -s)" = mac ] || [ "$(hostname -s)" = unknown ]; then # if hostname has been SET to mac, better make it different # to distinguish between a local one and one over SSH [ "$ZSH_NAME" ] && PS1='mac.ssh:%~%# ' || PS1='mac.ssh:\w\$ ' case "$TERM" in xterm*) echo -n $'\033]0;mac ssh\007' ;; esac else [ "$ZSH_NAME" ] && PS1='%m:%~%# ' || PS1='\h:\w\$ ' case "$TERM" in xterm*) echo -n $'\033]0;'"$(hostname -s)"$'\007' ;; esac fi case "$TERM" in xterm*) [ "$ZSH_NAME" ] && PS1=$'%{\e[1;37;40m%}'"$PS1"$'%{\e[1;33;40m%}' || PS1='\[\e[1;37;40m\]'"$PS1"'\[\e[1;33;40m\]' ;; esac export BASH_ENV="$__OldBashEnv" if [ "$ZSH_NAME" ]; then unsetopt hup check_running_jobs bad_pattern no_match # as bash setopt -k # allow comments even in interactive (as bash does) fi else # not on a Mac if [ "$COLUMNS" ] && [ $COLUMNS -le 50 ] && wget --version 2>/dev/null|head -1|grep "Wget 1.17" >/dev/null; then alias wget="echo 'Doing wget -q to work around Debian bug #823891';wget -q" # non-Mac, but that bug affects the version of wget shipped with Ubuntu 16.04 LTS so I put this in for the Cambridge MCS remote GNU/Linux machines at the time fi if [ -e /usr/bin/amixer ] ; then # for consistency, re-define our "volume" command for amixer on non-Mac volume () { if [ ! "$1" ]; then amixer sget Master else amixer sset Master "$1%"; fi } export -f volume >/dev/null if [ -n "$SSH_CLIENT" ]; then export DBUS_SESSION_BUS_ADDRESS=$(cat /proc/*/environ 2>/dev/null | tr '\0' '\n' | grep -m 1 DBUS_SESSION_BUS_ADDRESS | cut -d = -f2-); if ! [ -n "$DBUS_SESSION_BUS_ADDRESS" ]; then unset DBUS_SESSION_BUS_ADDRESS ; fi; fi fi fi # end of "not on a Mac" block # (so you can put all this into a .bashrc that will # get sourced from either Mac or Linux machines) which unix2dos >/dev/null 2>/dev/null || alias unix2dos="perl -i -p -e 's|[\r\n]+|\r\n|g'" # (outside Mac section because might also be useful on Linux if unix2dos is missing and you need to send a .txt file to someone who has only Windows Notepad) # "Bonus" command (not just Mac but may be useful on some # Linux systems) - a simple 'catdocx' (like catdoc if it's # installed, but for docx files - just extracts text # between the XML markup). catdocx_utf8 outputs UTF-8; # catdocx_cp1252 outputs "Windows 1252" as used on old # Psion PDAs etc. catdocx_utf8 () { while [ "$1" ]; do unzip -Cp "$1" word/[dfe]*[ts].xml; shift; done|perl -p -e 's/]*)?>/\n/g;s/<[^>]*>//g;s/ */ /g;s/<//g'|grep -v '^\s*$' ; } # (want to unzip document.xml, endnotes.xml and footnotes.xml but not emit warnings if missing and not match fontTable etc, hence the awkward file specification; perl rather than sed so as to avoid BSD sed / gsed differences re \n in replacement etc) catdocx_cp1252 () { catdocx_utf8 "$@" | iconv -c -f UTF8 -t CP1252//TRANSLIT ; } # --unicode-subst="[U+%04X]" instead of -c would be nice, but it's not available on all versions of iconv # Bonus command 2: cat a load of text files into a # self-extracting shell script (for email or whatever) # (must o/p to a file; i/p cannot incl dirs) shellarc () { Out="$1"; if [ -e "$Out" ]; then echo "Output file $Out exists; I'd rather not overwrite it" ; return; fi; echo "Writing $Out"; (echo '#!/bin/bash' ; while [ "$2" ]; do if echo "$2"|grep / >/dev/null; then echo mkdir -p '"'"$(echo "$2"|sed -e 's,/[^/]*$,,')"'"'; fi; C=0; while grep EOF$C "$2" >/dev/null; do C=$[C+1]; done ; echo "cat > \"$2\" <<\"EOF$C\"" ; cat $2 ; echo EOF$C ; shift; done) > "$Out" ; chmod +x "$Out" ; } # Bonus command 3: pdfmerge, on any machine with qpdf installed (to help with double-sided printing) which qpdf >/dev/null 2>/dev/null && pdfmerge () { if [ -e merged.pdf ]; then rm -i merged.pdf; fi && qpdf --empty --pages $(for i in $@; do echo $i 1-z; done) -- merged.pdf && du -h merged.pdf ; }