Cygwin, Part Two: Linux-like Shells

Your favorite Linux shell — bash, zsh, ksh, and more — is probably part of the Cygwin package.

Microsoft’s shell, CMD, is essentially the original MS-DOS shell with some extensions that make it more usable. (The January 2004 “Power Tools” column, Cross-Platform Command Lines, tells more.) As you saw last month, though, installing the freely-available Cygwin package on your Windows system gives you a much more powerful command-line. In fact, Cygwin comes with a number of major Linux shells — if you install them, that is. Here’s a list of available shells, generated from within Cygwin:

$ cd /bin; ls *sh.exe
ash.exe      ksh.exe    tclsh.exe
autossh.exe  pdksh.exe  tcsh.exe
bash.exe     sh.exe     wish.exe
csh.exe      ssh.exe    zsh.exe

These shells are virtually identical to the ones on your Linux system.

Let’s look at some of their features and settings that will help you get more work done more quickly (and with more fun) on a Windows system. We’ll also see a script for finding programs by entering part of their name. (A lot of this is also useful under Linux.)

Handling File Types

Unix and Linux generally don’t require filename extensions (filenames ending with a dot and three or four characters that tell the file type). For instance, text filenames don’t need to end with .txt. (The January 2005 “Power Tools” column, “It’s (Not) Magic,” has details.)

Under Cygwin, it’s easy to use Linux-type filenames without extensions: simply create and use files as you normally would on your Linux box. For example, if you create a shell script in a file named foo, there’s no need to name it foo.sh (although you might want to for easy recognition); simply use the normal name, make the file executable with chmod+x, and run the script by typing foo (or (./foo) at the prompt in a Cygwin shell window.

If your shell’s PATH includes Windows folders like C:\windows\system32 — and it probably does, because the Windows PATH is copied into Cygwin shells — you’ll be able to run Windows executables from your shell prompt. If the Windows executable folders are listed before the Cygwin directories in your Cygwin PATH, you’ll get (for instance) the Windows version of find instead of GNU find. (You don’t need to type the .exe extension for an executable program; simply type (say) find.)

If you’ll be working with files and folders created by Windows applications, Windows extensions like .txt will appear in your Cygwin shell. (As we saw last month, an exception is Windows shortcut files, or filenames ending with .lnk. Cygwin hides .lnk extensions as it treats those files as symbolic links.)

Your shell may let you “hide” some types of files — that is, files with certain extensions — during globbing (wildcard expansion) and filename completion. For instance, the bash shell variable GLOBIGNORE is a colon-separated list of patterns that tells the shell which types of files not to include in wildcard matches. Here’s a demonstration:

$ cd /cygdrive/c/windows
$ ls v*
vb.ini  vbaddin.ini  vmmreg32.dll
$ GLOBIGNORE='*.ini:*.dll'
$ ls v*
ls: v*: No such file or directory
$ unset GLOBIGNORE

The bash variable FIGNORE controls filename completion. It’s a colon-separated list of file suffixes (not complete filename glob patterns, but just suffixes) that won’t be considered when you complete a filename, by pressing TAB, for instance. As an example, if you use Emacs, you might not want to match backup files (filenames ending with tilde, ~) from the shell’s command line. You’d set FIGNORE like this:

FIGNORE='~'

(Although the ls utility isn’t part of the shells, you probably use it often from a command prompt. GNU ls has its own options to control which files are listed. We’ll see more in next month’s column.)

In tcsh, spelling correction of command names ignores filename extensions. For instance, if you’re trying to run the Windows findstr command (which works something like grep) and you mistype its name, tcsh will ask if you want to run findstr.exe but it won’t mention the .exe:

[jpeek@nt /d/tmp]$ findst "error" *.txt

CORRECT>findstr “error” *.txt (y|n|e|a)?

In general, if you use the Cygwin shell the way you’d use a shell under Linux, you’ll be okay in most cases.

Slashes vs. Backslashes

One area where Linux and Windows shells can’t easily be compatible is the difference between Linux and Windows pathname separators. A Linux filesystem uses forward slashes (/directory/file), while Windows uses backslashes (\folder\file.ext). Configuring Cygwin at install time to use forward slashes in pathnames will probably make things easiest for Linux fans.

Beware of places where you need to type a Windows-like pathname, though. You may need to use quoting or pairs of backslashes on a command line — for instance, '\windows\afile.txt' or \\windows\\afile.txt.

There’s a related problem in the bash and zsh built-in command read, which reads a line from the terminal and saves it to a shell variable. It treats a backslash as an escape character. So, in the following shell script fragment:

echo -n "Enter the Windows pathname: "
read winpath

If you run the script and enter C:\windows\xyz, then $winpath contains C:windowsxyz. You can work around this problem by using read -r winpath instead of just read winpath. The option -r disables backslash escapes.

Cygwin comes with a program named cygpath that translates pathnames from Windows style (with \) to POSIX style (with /) and vice versa. For example:

$ cygpath -w /cygdrive/c/windows/foo.exe
c:\windows\foo.exe
$ cd $(cygpath -p 'c:\windows')
$ pwd
/cygdrive/c/windows

To find out more, use man cygpath.

Starting Shells

Cygwin’s installation process can install a shortcut on the Windows desktop and the Start Menu. The shortcut points to a Windows batch file (CMD script file) named cygwin.bat. The batch file starts bash after first setting the Windows drive. The shortcut looks like this:

@echo off
C:
chdir C:\p\cygwin\bin
bash --login -i

The batch file sets the options --login (start a“ login” shell that will read the .bash_profile setup file) and -i (be sure the shell is interactive). That batch file isn’t as straightforward as starting the shell executable directly from a Windows shortcut icon on the desktop or the Start Menu.

Figure One shows the Properties dialog for a desktop Z shell icon. Setting the shortcut key combination (here, to Ctrl-Alt-Z) gives another way to open a shell quickly.

Figure One: Z Shell shortcut icon properties

Once you’ve made a shortcut icon that points directly to an executable file, you can also drag the icon to the Quick Launch area next to the Start icon in the toolbar; this copies the shell icon and lets you launch it with one click. To put the shell in the list of applications at the Start Menu’s top left corner, right-click the shortcut icon and choose Pin to start menu, as shown in Figure Two. (Windows apparently doesn’t let you pin a .bat file shortcut to the Start Menu, but a .exe shortcut works fine.)

Figure Two: Adding Z Shell to Start menu

Using Shell Features that GUIs Don’t Have

One of the advantages of a shell — versus a graphical interface like Windows Explorer — is the shells’ programmable features. There’s no need to use those features from a script; you can type them on the command line — edit them interactively, if needed, with the shells’ built-in editors — and let the shell do the work while you do do something else.

Want to copy a lot of big folders from several separate filesystems? It’s easy to give their pathnames to a for loop and let the copying proceed — instead of clicking, waiting, clicking, and waiting more. Have a series of time-consuming unrelated commands to run? Put semicolons (;) or AND-operators (&&) between the commands to execute them in sequence. Test/branch commands can make decisions for you, so you don’t need to watch a display and wait.

Let’s see three examples. (The > prompts are secondary shell prompts, presented whenever the shell is waiting for you to finish typing a command.)

$ for dir in /a/b /c/d /e/f /g/h
> do cp -rp “$dir” /i/backup
> done
$ find /j .; sort /k.; lpr /m.
$ while sleep 60; do
> test ! something && continue
> do something else.
> done

Where’s that program?

The names of some utilities can be hard to remember. Is that Netpbm utility named tiftopnm or tifftopnm? What versions of grep are installed? This is especially nice to know if you use multiple systems with different configurations.

Standard system tools like apropos (man-k) can help, but the answers they give can be out of sync with the programs that are actually installed and available in your shell’s search path. A bash script named findcmd does a better job. It searches each directory in the PATH environment variable for a filename that matches patterns you enter. Figure Three shows an example of findcmd matching three patterns.

Figure Three: Running the findcmd script
$ findcmd

>>> findcmd: Enter command name pattern (example: *cmd*).   
Empty line exits:
> *cmd
/home/jpeek/bin/findcmd
/cygdrive/c/WINDOWS/system32/login.cmd
/cygdrive/c/WINDOWS/system32/usrlogon.cmd

>>> findcmd: Enter command name pattern (example: *cmd*).   
Empty line exits:
> *cmd*
/home/jpeek/bin/findcmd
/cygdrive/c/WINDOWS/system32/cmd.exe
/cygdrive/c/WINDOWS/system32/cmdl32.exe
/cygdrive/c/WINDOWS/system32/login.cmd
/cygdrive/c/WINDOWS/system32/usrlogon.cmd

>>> findcmd: Enter command name pattern (example: *cmd*).   
Empty line exits:
> cmdial32.dll
/cygdrive/c/WINDOWS/system32/cmdial32.dll

The first search shows filenames ending with cmd. The second shows names containing cmd. And the third finds the exact name cmdial32.dll. Listing One shows the important part of the script.

Listing One: The most important part of the findcmd script
while .
  read pattern
  for dir in “${pathdirs[@]}”
  do
    cd “$dir”
    set $pattern
    if [ "$1" != "$pattern" ]
    then
      for match
      do
        test -x “$match” && echo “$dir/$match”
      done
    fi
  done
done

The script prints a prompt and reads a filename pattern. (Entering patterns with read — instead of on the command line — avoids “No match” errors and too-early filename expansion.) A for loop steps through each directory in the PATH (pre-processed to convert null or “.” entries into the current directory, and to remove missing and duplicate directories).

The command set $pattern “puts the pattern onto the command line,” doing filename expansion upon the pattern as if it had been typed on a command line in this PATH directory. If there are no matches, bash will store the unmatched pattern in $1; the if / test statement doesn’t print any output. Otherwise, one or more matches are saved in the positional parameters $1, $2, and so on; a inner for loop tests each match to be sure it’s executable, then outputs it as a full pathname.

The script also handles explicit filename patterns (with no *, ?, or [] wildcards in the pattern) by using test -x. To make Listing One less complex, that part of the script isn’t shown. You can download the complete findcmd script.

To Be Continued...

Next month, we’ll finish this series with a look at the Cygwin utilities.

Jerry Peek is a freelance writer and instructor who has used Unix and Linux for more than 25 years. He's happy to hear from readers; see https://www.jpeek.com/contact.html.

[Read previous article] [Read next article]
[Read Jerry’s other Linux Magazine articles]