Tuesday, October 10, 2006

Trying out wordpress

I recently started a new blog at http://spugbrap.wordpress.com, which may replace both of my blogger.com blogs soon. I'm trying out wordpress, comparing it with the new blogger beta, and will decide from there.

At this point, I will probably be posting all new entries over there, so you may want to go ahead and subscribe to it via the Entries feed, and possibly the Comments feed. I may cross-post a little bit, for the sake of comparing blogger beta vs. wordpress, but the wordpress one will be my primary outlet for all things geeky/personal/both (sometimes it's hard to draw the line).

Wednesday, August 23, 2006

Batch file: Trillian global disconnect and reconnect

I use Trillian for most of my instant messaging, because it allows me to connect to AIM and Yahoo through one common user interface (it also supports ICQ, MSN, and IRC, but I don't use [it for] those). The features I like about it are numerous, but one of my major complaints is the way it sometimes has trouble reconnecting after being disconnected from one of the networks.

Normally, it tries a few times to reconnect, automatically. But sometimes it seems like it gives up too soon. When this happens, you have to right click the system tray icon, go to the "Connections" submenu, and choose "Global Disconnect". Then, you have to do the exact same thing, but choose "Global Reconnect". The initial "Global Disconnect" is usually required, because Trillian is stuck in a state where it thinks you are connected in some way, such that just doing "Global Reconnect" wouldn't do anything.

As anyone that knows me could tell you, I hate doing extra steps, particularly involving the mouse, when a simple keyboard shortcut could suffice. So, several years ago, I made this simple batch file, which doesn't do anything magical, but I use it almost every day.

The batch file, included in the box below, uses TrillKey to send a "Global Disconnect" command to the currently-running instance of Trillian. Then, it waits 1 second, to give Trillian time to deal with the first request. Then, it uses TrillKey to send a "Global Reconnect" command to Trillian. For the brief delay in between commends, I use the 'sleep' command from cygwin, but a couple other ways to put delays into batch files can be found here: http://malektips.com/xp_dos_0002.html.
To call this batch file, I have a shortcut to it on my desktop, with a shortcut key assigned to it (Ctrl-Alt-T). Works like a charm!

@echo off
c:\programs\trillkey.exe disconnect
sleep 1
c:\programs\trillkey.exe reconnect

While writing this post, I noticed that I'm using a really old version of TrillKey (from 2002). The latest version includes additional features, so my batch file can be simplified to a single command:

@c:\programs\trillkey.exe disconnect delay 1 reconnect

TrillKey can do a LOT more than just disconnect and reconnect, though. I highly recommend checking it out.

Monday, August 21, 2006

How to send an SMS from cell phone to an AIM user

I'm not sure if this is common knowledge or not, but I only learned about it recently. Some cell phones let you sign on to instant messenger clients (usually AIM), but that can be less than ideal. When I did that a couple years ago, every single communication my phone did with AIM counted as a text message. That includes messages you send or receive, as well as sign-on related messages, and even disconnection messages. I'd get disconnected fairly frequently, and every time that happened, it would cost me 2-3 text messages to sign back on. At 10 cents a pop (unless you pay for a plan with a bucket of monthly text messages), that got annoying pretty fast.

Well, I recently found out that you can send a message to an AIM user, using your cell phone, without logging on with any actual AIM client. Just send an SMS text message with the following format:

Recipient: 265010
Message: AIM_screen_name: message text

I know there are all sorts of "mobile AIM" features, but this is one simple one that has come in handy several times for me, already.

Thursday, August 10, 2006

How to list just directories in bash

This morning, I was trying to find a way to list just the subdirectories in the current directory, in a bash shell script I was writing. I thought it would be simple, but everything I tried seemed to either take an extraordinarily long time, or felt like an ugly hack.

The first thing I tried was:
find . -type d

But this was extremely slow, because it was recursively searching inside every subdirectory as well. I just wanted a list of subdirectories inside the current directory. I won't bore you/clutter this post up with any more of my less-than-ideal methods.

What follows are a couple of ways of doing what I was trying to do, which I found in a post (and its comments) on the Ubuntu Blog, "List only the directories":

ls -l | grep “^d”

This works, but gives a 'long' directory listing, when all I wanted was a list of directory names.


find . -type d -maxdepth 1 -mindepth 1

This one was my favorite, since it used the method I originally tried, but it fixed the slowness by using parameters to avoid recursion. It gave me a couple warnings about the order of the parameters, though, so I changed it to this:
find . -maxdepth 1 -mindepth 1 -type d

ls -d */

This gave me the same output as the 'find' method did, but some timing tests showed me that the 'find' method was about 2 times faster.

Monday, August 07, 2006

A Handy CSS Debugging Snippet

From A Handy CSS Debugging Snippet:

* { outline: 2px dotted red }
* * { outline: 2px dotted green }
* * * { outline: 2px dotted orange }
* * * * { outline: 2px dotted blue }
* * * * * { outline: 1px solid red }
* * * * * * { outline: 1px solid green }
* * * * * * * { outline: 1px solid orange }
* * * * * * * * { outline: 1px solid blue }


This code, if placed in your stylesheet, outlines every element on the page with colored boxes. I like this because it's simple, and I learned something from it. I had no idea that you could use an asterisk for a css selector. Then, the idea of stringing them together to represent the elements that are most deeply nested with different colored outlines. Pretty slick!

I will probably still use the many functions in the Outline menu of the Web Developer Extension for Firefox, and the Internet Explorer Developer Toolbar, for most of my everyday outlining-for-debugging needs. There are also bookmarklets out there to do this type of thing. But, this snippet deserves a spot in my toolbox, because it seems like it could could come in handy someday.

Friday, August 04, 2006

Disabling Message Center in RealPlayer via a simple registry edit

Random tidbit of potentially useful information...

From a comment by Wulf on the Google Operating System blog:

While playing around in my Registry, I found an interesting entry from RealPlayer.

If you change
HKEY_CURRENT_USER\Software\RealNetworks\Msg\
Preferences\Frequency > (default)
from 1 to 0, the 'Message Center' (read: ads) won't come up anymore...

I haven't tried this, as I so rarely use RealPlayer that I don't even remember the annoyance that this tip claims to prevent. But, I did go ahead and make this registry value change, in the hopes that someday, something might less annoying. :)

I actually took this opportunity to try out REG.EXE, the "Console Registry Tool for Windows" (it came with Windows XP Pro), which I've never gotten around to messing with... So this is how I changed this particular registry key:
reg add
HKCU\Software\RealNetworks\Msg\Preferences\Frequency
/ve /d 0

Wednesday, August 02, 2006

screen in cygwin needs System attribute on SockDir files

This will not be of use to many, if any, but I expended effort trying to figure out how to solve this today, so I'm posting it here for future reference, if nothing else.

Today, at work, I ssh'd to my home computer, and tried to run 'screen -r -d', to reattach to an existing session of gnu screen at home. Here's what happened:

$ screen -r -d
There is no screen to be detached.


I knew this was not true, so I tried this:
$ screen -list
No Sockets found in /tmp/uscreens/S-myusername.


I didn't believe it, because I knew I had an existing session open, so I looked for myself:
$ ls -l /tmp/uscreens/S-myusername/
total 2
-rw------- 1 myusername None 54 Jul 24 14:35 1696.tty0.spugbrap-home
-rw------- 1 myusername None 54 Aug 2 14:19 3500.tty0.spugbrap-home


I saw it right there, so I looked to see if one of the processes that I had running in my existing screen session was still running:
$ ps | grep perl
3204 368 3204 1760 13 1003 14:20:05 /usr/bin/perl


Sure enough, there it was... So, I took a look at my SockDir on my laptop, at work, to see if permissions might be involved in some way:
$ ls -l /tmp/uscreens/S-myusername/
total 3
srwx------ 1 myusername None 53 Aug 2 14:18 2568.tty1.dave-laptop
srw------- 1 myusername None 53 Jul 4 01:05 3600.tty1.dave-laptop
srw------- 1 myusername None 53 May 15 15:42 960.tty1.dave-laptop


Ah hah! There was a difference! The 's' at the beginning of the permissions list on my laptop's SockDir contents, but not on my home machine's.

So, I went searching for what the heck that 's' stands for, since usually, if anything, I either see an 'l' (L) or a 'd'. I checked the help, info, and man pages for 'ls' and 'chmod', but didn't find anything that actually matched this flag. The closet thing was 'suid + executable', but when I tried to chmod that onto one of my files, the permissions showed '-rws------', which is not what I was looking for.

A google search or two, for things like 'srwx------', 'srw-------', 'srwx srw', 'cygwin srw', etc. didn't turn up anything useful - at least not in the first pages of results.

I tried looking at my laptop's SockDir in windows explorer, and looking at the advanced security properties of one of the files. Nothing looked interesting. Then I looked at it from a command prompt (4nt), and saw this:
[c:\]dir c:\cygwin\tmp\uscreens\S-myusername
[...]
0 bytes in 0 files and 2 dirs


Oops, let's try with 'attrib' instead of 'dir':
[c:\]attrib c:\cygwin\tmp\uscreens\S-myusername
__SA_ C:\cygwin\tmp\uscreens\S-myusername\2568.tty1.dave-laptop
__SA_ C:\cygwin\tmp\uscreens\S-myusername\3600.tty1.dave-laptop
__SA_ C:\cygwin\tmp\uscreens\S-myusername\960.tty1.dave-laptop


Ah hah! The 'system' and 'archive' attributes were set on these files. So, I verified that these flags were NOT set on the files on my home machine:
$ attrib "c:\\cygwin\\tmp\\uscreens\\S-myusername\*"
C:\cygwin\tmp\uscreens\S-myusername\1696.tty0.spugbrap-home
C:\cygwin\tmp\uscreens\S-myusername\3500.tty0.spugbrap-home


Sure enough, there's the difference. So, I set the 'system' attribute on those files (didn't bother with 'archive' attribute, though I'm not sure what causes it to be there on my laptop but not on my home):
$ attrib +"c:\\cygwin\\tmp\\uscreens\\S-myusername\*"

Verified that it worked:
srw------- 1 myusername None 54 Jul 24 14:35 1696.tty0.spugbrap-home
srw------- 1 myusername None 54 Aug 2 14:19 3500.tty0.spugbrap-home


Then, I tried connecting to my existing session, and it succeeded.

While preparing this post, I experimented a little bit more, and noticed that, for some reason, my home pc is not creating these screen socket files with the required 'system' attribute at all, anymore. I'm not sure why this is happening, now, because I can't think of anything I've done, recently, that might have caused any different behavior as far as permissions and such.

I will post again if I figure this out, but for now I am content with adjusting the attribute manually. In theory, I can keep each screen session alive until my next reboot (which is rare), so I shouldn't have to do too many manual adjustments like this. I also welcome any comments on how to solve this problem, or any other useful tips for effectively using gnu screen in cygwin.

Sunday, July 16, 2006

finding unique ips in access log that have something in common

search for a string in access log, extract only ip address from matching line, sort the list of ip's and remove duplicates, output the [shortened] list...

for ip in `grep -i firefox /cygdrive/w/resin/log/access.log \
  | grep -o "^[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" \
  | sort -u \
  | wc -l`; \
  do echo $ip; \
done

perl urlencode without libs

$url = "Foo bar com.dx?q=23";
$url =~ s/([^\w\-\.\@])/$1 eq " "?"+":
  sprintf("%%%2.2x",ord($1))/eg;

print $url;

I don't remember where I got this, but it must have come in handy for me at some point. Never hurts to have this kind of stuff laying around, just in case.

Thursday, July 13, 2006

Generating a random fake name from the commandline

Today I needed to come up with a list of lots of fake names for test data. In the past, I either manually entered well-known fictional character names (e.g. Homer J. Simpson), or used strings of characters (like 'asdf g. hjkl' or 'aaaaaaaaaaaaaa').

I remembered seeing some sort of test data generator, somewhere, recently, so I googled for "fake name generator test data".

What I found was http://www.fakenamegenerator.com, which generates realistic test data based on country, name origin, and gender specifications. More info on the site at the end of this post. Here's a set of commands that I put together to retrieve one fake name from that site:

curl -s -b agreement=Yes "http://www.fakenamegenerator.com"
    | grep -o 'on Google">\([^<]\+\)'
    | sed -e "s/[^>]*>\([^<]\+\)<.*/\1/g"

When you use the site like a normal human being, the fake data that is generated includes full name, address, email address (usable, provided by an anonymous email service), phone number, mother's maiden name, date of birth, and credit card number (+ expiration date). Very cool! For a very small fee, you can also order a bulk batch of data, which also includes fake Social Security Numbers).

However, being the penny-pinching and geeky type, I wanted to be able to generate my own list of fake names (without all the other info), for free. The set of commands listed above work right now, from a cygwin bash shell, but will probably break sometime in the future, when the HTML structure of the page changes. Oh well.

Oh yeah, don't forget to read their terms of service* before using the service... Right now, I could not find anything prohibiting the use of automated tools to generate and retrieve names, but use the above set of commands at your own risk!

* The terms of service page only displays one time, unless you clear/disable your cookies.

Using ImageMagick and 4NT to find jpegs by quality level

Using ImageMagick and 4NT to find and modify all jpegs in the current dir who have quality level 94:

for %f in (*.jpg) do (echo %f
    & identify -verbose %f | grep Quality
    | grep -o "[0-9]*")


iff "%@execstr[identify -verbose %f
    | grep Quality | grep -o "[0-9][0-9]"]" == "94"
    then & echo %f & endiff


for %f in (*.jpg) do
    (iff "%@execstr[identify -verbose %f
    | grep Quality | grep -o "[0-9][0-9]"]" == "94"
    then & echo %f &
    mogrify -strip -quality 84 %f & endiff)




Unrelated side note:
This is one of several posts that I will be making in the near future, which come directly from my ever-growing toblog.txt file... No code cleanup or lengthly explanations, just commands I've run at some point that I thought were worth saving.

Wednesday, July 05, 2006

Bash history substitution

Anyone who has had an introductory unix course should know about the bash shell's "history" command, which gives you a numbered list of commands that you've run previously.

You can execute one of those commands again by doing:
$ ![number] (for a particular command you've seen on the history list)
or
$ !! (for the previous command/last command in the history list)


One thing that I didn't learn in any class, but did find out about from a co-worker, several years ago, was history substitution. It's easy to run the previous command with minor changes, by using a caret-delimited substitution expression. Here's a very simple example:

view a file:
$ cat spugbrap.txt

then, edit that same file:
$ ^cat^vim^

that gets expanded to (and executed as):
$ vim spugbrap.txt


I've used this feature countless times since learning about it, but it always suffered from a limitation: If the pattern you're trying to match occurs multiple times in the previous commandline, this subtitution method only replaces the first occurrance of it. So, I recently decided to find out how to substitute multiple occurances, since I was sure there had to be an easy way. Here's one way I found:

watch a couple of tomcat log files continuously:
$ tail -F ~/tomcat/logs/stdout_20060704.log ~/tomcat/logs/stderr_20060704.log

The next day, the log file names are different, because they're date-based. so I want to change 20060704 to 20060705, and it needs to happen twice because the date occurs twice in that commandline. No problem! Assuming the previously executed command was the "tail" commandline, above, simply enter this:
$ !!:gs/20060704/20060705/

that gets expanded to (and executed as):
$ tail -F ~/tomcat/logs/stdout_20060705.log ~/tomcat/logs/stderr_20060705.log


What if my previous command was long, and I need to make multiple substitutions with multiple strings?

previous command:
$ tail -F stdout_20060704.log stderr_20060704.log host-manager.2006-07-04.log catalina.2006-07-04.log admin.2006-07-04.log localhost.2006-07-04.log manager.2006-07-04.log jakarta_service_20060704.log

to change the dates, which occur in two formats:
$ !!:gs/20060704/20060705/:gs/2006-07-04/2006-07-05/


If the command I want to run (with substitutions) was not the previous command, but some other command that appears in the numbered list from running 'history', put the history line number between the exclamation point and the colon:
$ !123:gs/oldstr/replacementstr/


For more information about this, and other bash history manipulation capabilities, check out:
Bash Features - Using History Interactively

Wednesday, June 28, 2006

Making System.out.println() return a String

At some point, recently, I needed to do both of these things in one little Java expression:
- output a String to stdout
- return the same String

System.out.println() returns 'void', and you can't cast void to String, so I struggled with this for a little while. Here's what I came up with (download here if it looks ugly in the blog):

public class VoidToString
{
  public static void main(String[] args)
  {
    System.out.println("returned value: [" +
      new Object()
      {
        public String q(String p)
        {
          System.out.println("stdout: [" + p + "]");
          return p;
        }
      }.q("blah") + "]");
  }
}


It ended up not working in the context I was trying to use it in, because the 3rd-party API I was using did not appreciate the inner class, but I still thought it was a neat little trick.

Tuesday, June 20, 2006

getting the current date in a particular format in Windows XP/2003

I needed to get the current date, in the format 'yyyy-MM-dd', and use that value multiple times within a batch file.

The command 'DATE /T' returns a date like this:
Tue 06/20/2006

But I needed:
2006-06-20

Now, this would have been extraordinarily easy, if I could use 4nt, cygwin, nclip, or any number of other tools. However, my options were limited on the machine that I needed to run this on, as it is a production web application server. So, I had to come up with a way to do this using the tools I had available to me:
- Windows 2003 Server (assumed to be a default installation, or more minimal than that)
- Sybase ASE 12.5.x
- Java

I probably could have just written a simple java application to do this, but where's the fun in THAT? :)

So, I consulted with my good friend, Jeff Scanlon, as I usually do when I have an interesting tech challenge, and together we came up with this batch file [NOTE: you can download it here, if the code gets too munged with the blogger html/css]:

------ BEGIN BATCH FILE -------

@echo off
REM generate SQL script to get date in correct format:
echo select 'set my_date=' + str_replace(convert(char(20),getdate(),102),'.','-') >%TEMP%\getDate_yyyy-MM-dd.sql
echo go >>%TEMP%\getDate_yyyy-MM-dd.sql

REM run the SQL script to generate a simple, temporary batch file:
isql -U username -P password -i %TEMP%\getDate_yyyy-MM-dd.sql | findstr set >%TEMP%\set_my_date.bat

REM run the temporary batch file, which will set an environment variable, 'MY_DATE', to contain the date:
call %TEMP%\set_my_date.bat

REM clean up
del /q %TEMP%\set_my_date.bat
del /q %TEMP%\getDate_yyyy-MM-dd.sql

REM this is where you'd actually put code that uses the date, like:
REM md c:\backup\%MY_DATE%
REM copy c:\mystuff\*.* c:\backup\%MY_DATE%

------ END BATCH FILE -------

Thursday, May 04, 2006

How to list duplicate lines in a text file, with counts next to each unique line

At some point, last year (it's been in my 'toblog' file all this time), I needed to analyze the lines in a text file, removing duplicate lines, while counting how many times each duplicated line occurred within the file, and sorting from most common to least common.

For example, using a text file called 'dupetest.txt', containing:

foo bar baz
foo qux corge
spugbrap likes bacon
foo qux corge
spugbrap likes bacon
foo bar baz
oatmeal cookies are good
oatmeal cookies are good
foo bar baz
foo qux corge
foo bar baz

The output I want is:
4 foo bar baz
3 foo qux corge
2 spugbrap likes bacon
2 oatmeal cookies are good


I knew there had to be a simple way of doing this by just stringing together a few unix commands (in cygwin), but finding the right combination of commands took me some effort. Here's what I came up with:

sort dupetest.txt | uniq -c -d | sort -n -r

Friday, April 28, 2006

Talking over SSL from Java

For the first time in this blog, I'm pleased to present to you a
guest post from my good friend, Jeff Scanlon:


Talking over SSL from Java
--------------------------

When using SSLFactory to talk SSL with a server, or using
the URL class to open an https connection, an error you
might encounter is:

sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target

One solution that worked for me is to add the server certificate
to the cacerts file in the appropriate Java runtime.

Steps:
  1. You must have the server certificate. If you don't have this
    handy, navigate to the secure website, click on the lock icon
    (in Internet Explorer). Click on "Details" tab and then click
    the "Copy to File..." button. Export the certificate to a DER
    encoded binary X.509 (.CER) file. Let's say you chose the
    filename example.cer

  2. Copy example.cer to the jre/lib/security directory in the Java
    runtime you are using. If you're on Windows and the JDK/JRE
    is in C:\jdk1.5.0_04, then the full path is:
    C:\jdk1.5.0_04\jre\lib\security

  3. Import the certificate into the cacerts file by using the
    following command [editor's note: this is all one command, broken
    into multiple lines for blogging
    ]:

    keytool -import -storepass changeit -file
      example.cer -keystore cacerts
      -alias example

    'changeit' is the default password to the cacerts keystore
    'example.cer' is the filename you used in step #1
    'example' is an arbitrary alias you choose for the certificate

  4. Now try your code and see if it can establish the SSL
    connection. If you get an error like 'HTTPS hostname
    wrong: should be ____', then add this code:

    import javax.net.ssl.HostnameVerifier;
    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.SSLSession;

    // -------------------------------------------------

    HostnameVerifier hv = new HostnameVerifier() {
        public boolean verify(String hostName,
                              SSLSession session)
        {
            System.out.println(
              "hostName is [" + hostName +
              "] and session peer host is [" +
              session.getPeerHost() + "]");
            return(true);
        }
    };

    HttpsURLConnection.setDefaultHostnameVerifier(hv);

    The above code provides a hostname verifier replacement that
    will cause hostname verification to succeed (by always
    returning true). Be aware that using this code does have
    security implications. However, this simplistic verify
    replacement should solve SSL communication problems and
    provide a starting point for implementing a custom verify
    that maintains security.

Jeff has been a good friend of mine for about 16 years, now.
He has been published several times (such as here,
here, and here), and is usually the first person I turn
to for ideas/advice on any programming/computer-related topic. He will definitely be contributing to this blog again in the future.

Wednesday, March 01, 2006

html form tag without the top/bottom padding

This seems like such a simple thing, but I'm really happy to be able to add it to my collection of useful code snippets:

The important part (HTML code snippet):
<form style="display: inline; margin: 0px;">

Explanation/history/context:
For years, now, I've just accepted (but been annoyed by) the fact that the HTML FORM tag causes some whitespace to display above and below the form contents:

htmlscreenshot
Form outside table:

Text above table
<form>
<table border="1">
  <tr>
    <td>table cell</td>
  </tr>
</table>
</form>
Text below table


One common way of avoiding that whitespace padding is to stick the FORM tag inside a TABLE tag, floating freely without being inside any particular row/cell, like this:
htmlscreenshot
Form inside table:

Text above table
<table border="1">
<form>
  <tr>
    <td>table cell</td>
  </tr>
</form>
</table>
Text below table


I've never bothered to find a better way, until recently, when I decided that I didn't like HTML validators complaining about that. So I searched and found a method of avoiding that whitespace that's simple, seems to work fine in multiple browsers, and passes HTML validation:
html
Form outside table with style:

Text above table
<form style="display: inline; margin: 0px;">
<table border="1">
  <tr>
    <td>table cell</td>
  </tr>
</table>
</form>
Text below table
screenshot

Sunday, February 05, 2006

Removing empty macros from Excel, to avoid popup security dialog

Anyone that's ever dealt with Excel has probably seen this security warning dialog about macros:


It's one thing if the spreadsheet actually has macros in it, especially if it actually *uses* the macros in it. But even if you create a macro, and subsequently delete it, you still get this warning.

I found an Excel FAQ today that included instructions for fixing this. Worked for me!

Friday, February 03, 2006

Finding and un-removing files that I did a 'cvs remove' on yesterday

Last July, I posted a bash shell script called 'cvsrecent.bsh', the purpose of which was "to get a list of CVS revisions and log messages for all checkins within the past (some number) of days."

I've continued to use this script, to this day, but I found a shortcoming this morning that I hadn't noticed before. It doesn't find files that have been removed from your working directory or the repository. I removed some files from my working directory and did 'cvs remove' on them yesterday, and today I decided I need to undo that.

So, I wrote a set of commands to find and check out the last version of all files that were deleted some number of days ago. Here are the components I used:

First, a bash function to get previous revision of a file:
function cvsprev()
{
  cvs -q log $@ |     grep "^revision " |     head -2 |     tail -1 |     cut -f2 -d" "
}


Next, a bash function to get the date for some number of days ago, and format it the way cvs likes it:
function getdaysagodate()
{
  date --date="$1 days ago" +"%Y-%m-%d"
}


Finally, here's the set of commands I used to:

  1. find all the files that CVS put in the "Attic" some number of days ago (in my case, I used 1 (one) since I removed the files yesterday).
  2. for each file:
    1. get the full working-directory path
    2. find the last revision number, before it was removed
    3. do a 'cvs update -r' to check out the last version of the file
for f in  `cvs -Q log -SNd ">=$(getdaysagodate 1)" |  grep -A1 Attic |  sed -e "s/^.*Attic.*$//"    -e "s/^\-\-$//"    -e "s/Working file: //"`; 
do cvs update -r `cvsprev $f` $f; 
done


Another command could be added to check those files back into CVS automatically, but I wasn't 100% certain that I wanted to check all of them back in. I mainly just wanted to find them, and look at them. Maybe sometime I'll update the cvsrecent.bsh script, so it can handle files that have been removed. At least I'm half-way there after having written these commands today.