I have in the past written about my Cygwin setup, but several weeks ago, while I was in Scotts Valley and having dinner with Adam Markowitz and Steve Trefethen, Steve mentioned that I should write a bit more about my setup.
While the defaults for a new Cygwin install today are better than they have ever been, there are still a lot of things to be desired. Using, as I do, a bash shell as my main command line, yet still being a Windows programmer running on Windows, means that I need to integrate with Windows command-line programs. Herein lies a problem: Cygwin uses Unix-like paths with '/' and no drive letter or colon (which is a path separator on Unix systems), while Windows inherits the usual CP/M/DOS traditions. Incidentally, I mount those drives to root letters to make converting between Windows and Cygwin paths easy:
$ mkdir /c $ mount 'c:\' /c
Mounting removable drives like floppy disks and DVD-drives in the same way is more problematic, as the 'ls --color=auto' command (which wants to colour in files and directories corresponding to thier types) will try to read the contents of these directories, which of course will be mounted to removable drives. This would normally cause delays when doing a listing of the root directory, as the removable drives in the system spin up etc. Consequently, for removable drives I use a different technique. For example, my DVD-drive is 'O:' (because it's round, and because I frequently add and remove drives and I don't like drive letters changing because it breaks things), so this is how I integrate the DVD-drive into the Cygwin file system:
$ ln -s '/cygdrive/o' /o
This creates a symbolic link which will just be a broken link when there is no DVD in the drive. I do similar things for my iPods, pen drives, floppy drive (I still keep one around, just in case :), etc.
Anyway, back to Cygwin/Windows path interaction. Cygwin does provide a command to convert between paths, called 'cygpath'. It can be used fairly easily in an ad-hoc way on the command-line:
$ notepad /etc/bash.bashrc # (this will fail, as notepad can't cope) $ notepad $(cygpath -w /etc/bash.bashrc) # (this will work here but fails when the Windows path has spaces) $ notepad "$(cygpath -w /etc/bash.bashrc)" # (this is more resilient)
winexec
Using cygpath manually is a bit of a pain, so I wrote a little bash script I call winexec to capture the pattern:#!/bin/bash function usage { echo "usage: $(basename $0) [options][ ...]" echo "Executes an executable with arguments, converting non-options into Win32 paths." echo "Options:" echo " -f Only convert paths to files or directories which actually exist." echo " -s Use cygstart to execute detached from console." echo " -k Skip converting paths until '**' found in arguments (and remove the '**')." echo " -- Terminate $(basename $0) options processing." exit 1 } # Process options to winexec itself. while [ "$1" ]; do case "$1" in -f) ONLY_FILES=1 ;; -s) USE_CYGSTART=1 ;; -k) SKIP_TO_STAR=1 ;; --) shift break ;; -*) # Give an error on unknown switches for future compat. usage ;; *) break ;; esac shift done EXECUTABLE="$1" shift test -z "$EXECUTABLE" && usage # Options conversion and caching. declare -a OPTS function add_opt { OPTS[${#OPTS[@]}]="$1" } function add_file_opt { if [ -n "$SKIP_TO_STAR" ]; then if [ "$1" = "**" ]; then SKIP_TO_STAR= # Eat '**' but don't add. else # Haven't seen star yet, so add unconverted. add_opt "$1" fi else if [ -n "$ONLY_FILES" ]; then if [ -f "$1" -o -d "$1" ]; then add_opt "$(cygpath -w "$1")" else add_opt "$1" fi else add_opt "$(cygpath -w "$1")" fi fi } # Process arguments to executable. while [ "$1" ]; do case "$1" in -*) add_opt "$1" ;; *) add_file_opt "$1" ;; esac shift done # Actually start the executable. if [ "$USE_CYGSTART" ]; then cygstart -- "$EXECUTABLE" "${OPTS[@]}" else "$EXECUTABLE" "${OPTS[@]}" fi
For an example of how I use that, I have another script called 'dir', for when I feel like I need classic 'dir' options:
#!/bin/bash winexec -f -k cmd /c dir '**' "$@"
All these scripts, BTW, go in my ~/bin directory and are chmod'd 0755 to make them executable:
$ mkdir ~/bin $ chmod -R 0755 ~/bin/*
My system's PATH (i.e. the Windows PATH, from System Properties | Advanced | Environment Variables) includes my home directory's bin directory before the Cygwin bin directories, but it also includes those. There can be some knots here though, which I won't get into today. The scripts also need to use Unix line-endings, though Cygwin was less strict about this in the past. It's easily enough done, though: the dos2unix command will normalize to Unix any text files given as arguments.
n
Notepad is a classic programmer's tool - as in "all I need is Notepad and the compiler" (or maybe just Notepad ;), etc. Since Notepad doesn't react so well to multiple file arguments, it isn't completely suitable to the winexec trick. I have a customized script for Notepad:
#!/bin/bash if [ -z "$1" ]; then echo "usage: $(basename $0)..." echo "Starts notepad on the file(s)." echo "If is -, then standard input is redirected to a temp file and opened." exit 1 fi for file in "$@"; do if [ "$file" = "-" ]; then file=$(mktemp) cat '-' > $file ( notepad "$(cygpath -w "$file")" rm $file ) & else cygstart -- notepad "$(cygpath -w "$file")" fi done
Having created this little utility, I can open multiple files in notepad just using the bash wildcards:
$ n /c/windows/*.txt # (there aren't too many of these guys)
Similarly, I can capture a program's output into Notepad for reference in a separate window and possible printing:
$ dir /c | n - # (opens a notepad window containing the directory listing for C:\)
Copy and Paste
Finally (for now), good Windows integration requires good clipboard integration. The native-Windows rxvt terminal which ships with Cygwin already support automatic copy on selection and paste with middle-cilck or Shift+Ins, familiar to Unix console and X users. However, I often want to copy the output of a command to the clipboard, or get a copied piece of text into a file, or transform the contents of the clipboard (perhaps to do a search and replace on it), etc. Thus, I wrote two little utilities in Delphi, copy-clipboard.dpr and paste-clipboard.dpr:
Copy
{$APPTYPE CONSOLE} uses SysUtils, Classes, Clipbrd; var list: TStringList; line: string; begin try list := TStringList.Create; while not Eof(Input) do begin Readln(line); list.Add(line); end; Clipboard.AsText := list.Text; except on e: Exception do Writeln(ErrOutput, e.Message); end; end.
(Freeing objects that have no external effect when freed before you're about to exit the program is the height of pointlessness, in case you were wondering.)
Paste
{$APPTYPE CONSOLE} uses SysUtils, Clipbrd; begin Write(Clipboard.AsText); end.
These two utilities, having been compiled, renamed to c.exe and p.exe, and moved to my ~/bin directory, come in very handy. For example, should I myself have wanted to copy one of the above scripts, I normally just select and copy script text, and:
$ p > ~/bin/winexec $ chmod 0755 ~/bin/winexec
Similarly, I sometimes want to search and replace on text on the clipboard:
$ p Similarly, I sometimes want to search and replace on text on the clipboard: # (showing what's on the clipboard) $ p | sed 's| |_|g' | c # (replace all spaces with underscores) $ p Similarly,_I_sometimes_want_to_search_and_replace_on_text_on_the_clipboard:
A not usually unwelcome side-effect of my clipboard commands is that they normalize line endings and add a newline sequence at the end of the text, if there isn't one already.
I hope I've given a few folk some ideas about optimizing their environment, particularly if they're command-line junkies like me.