TweetFollow Us on Twitter

The Shell: Automation

Volume Number: 21 (2005)
Issue Number: 4
Column Tag: Programming

Mac In The Shell

by Edward Marczak

The Shell: Automation

Doing Something Useful

Automation: it's everything. There's little other reason to have computers in a business. While that may be a slight oversimplification, let's agree that it's a compelling reason to have them around. Sometimes, you automate smaller tasks so you can free up your time to automate the bigger tasks.

Introduction

Last month, we touched on some basic shell commands. This month, we'll take those, and a new one or two, and create a script that helps us automate some system maintenance.

Processing...

The March "Mac In The Shell" talked about how the bash shell works with files, interprets pattern matching characters, and can get you help. As you've been practicing your shell-fu, hopefully you've found some new commands that are simply too useful to give up. For now, though, they're just commands. By chaining commands together, or letting them interact in any way, they become greater than the sum of their parts. Unix is built on this philosophy. You'll find that most commands are small, single-purpose programs. Each program can concentrate on being great at one thing. Then, we can write a script or function, or use i/o redirection to have the output of one program act as input to another.

Here's an example: 'wc' is the "word, line, character, and byte count" program. You can feed it a file, and it will happily spit back these statistics:

$ wc smallscript.sh 
      93     299    2350 smallscript.sh

'smallscript.sh' has 93 lines, 299 words and 2350 bytes. Sometimes, you just have to know these things. Additionally, 'wc' accepts switches that tailor the output to your needs. If you just need to know how many lines, use the '-l' (ell) switch:

$ wc -l smallscript.sh 
      93 smallscript.sh

But what if we need to know how many lines in a directory listing? The 'ls' command doesn't have an option for that. That's why we let each program do what it does best! We'll let the 'ls' command list the directory, and have 'wc' use the output from 'ls' as its input. An example from my home directory:

$ ls -l | wc -l
      44

(Yes, that's about 20-or-so too many, OK?) The "|" character is the pipe, or more properly, "pipeline." I'll give a quick overview of the going-ons here, but for more information and usage examples, see my article in the December 2004 MacTech.

Most of us take for granted the fact that we have a keyboard in front of us, and when we type, characters show up on the screen. Additionally, few people run Terminal.app and remember that this is supposed to be emulating a real terminal. Well, in some ways, the underlying system does still treat each terminal session as if it were a real, physical terminal hooked into it. It does this through the use of virtual terminals (or, 'pseudo-terminals'). I suppose "Virtual Terminal.app" just didn't roll off the tongue.

Whether you're using a real text-terminal connected via wire to an AIX server, or Terminal.app locally on your OS X box, some things are constant. We all have learned (or should have, at some point), about "Input, Process, Output" in computer information handling. When you're running a shell, there are defaults for 'input' and 'output', called 'standard input' and 'standard output'. Standard input is taken from your typing at the keyboard. Standard output is the screen. There are several ways to redirect this input and output, and the pipe is one of the ways to do that.

In the previous example, we could certainly have done this:

$ ls -l > filelist.txt
$ wc -l filelist.txt

The '>' character lets us redirect standard output. Instead of the output from 'ls' printing to the screen as it usually does, we redirect it to a file. Then we can have 'wc' operate on the file. However, it's a lot easier and neater to use the pipe redirector to do it all in one step, isn't it?

The Automator

Apple apparently agrees with my stance on automation, as evidenced by the 'Automator' feature of Tiger. However, in reality, you are the automator. We're not quite up to the stage where operating systems and applications really watch what you do over time, and then modify behavior purely on your actions. So, you have to automate actions. Automator in Tiger is a great step in this direction, and it will even call shell scripts for you!

A fair amount of automation is already built into OS X for you. Shell scripts that clean up log files, rebuild databases, and more run from cron. 'cron' is a system utility that runs shell scripts ("jobs," more precisely, as it can really kick off any program, such as perl, PHP or python) on a set schedule. We learned how to massage cron and have it run our own jobs from Josh Wisenbaker in the March issue of MacTech. Despite the maintenance that already exists in OS X, there's always more work to do. Here's an example of a script that I like to run on OS X Server, but there are parts that will be useful on OS X as well (remember that OS X makes a great server, too, as it will run pretty much anything that OS X Server will). I'll also introduce one of my favorite commands.

What We Need To Know

An OS X Server box that is tasked with just about everything that it can do has a lot of responsibilities: DNS, e-mail, file serving, web serving, and more. There are certain critical parameters that we should be kept abreast of, such as free disk space, if there's anything stuck in the mail queue, if any binaries have been altered, etc. I like knowing this summary every morning. So, how can we get this in a simple, single source? A shell script that e-mails its output to us, of course!

First, running out of disk space pretty universally bad. OS X, with its "I advocate a single partition" disk scheme makes watching disk space even more critical. 'df' is the tool we use to find out how much disk space is used and free on local, mounted volumes. I like using the '-h' 'human readable' switch:

$ df -h
Filesystem                  Size     Used     Avail     Capacity     Mounted on
/dev/disk4                  173G      73G      100G          42%      /
devfs                       110K     110K        0B         100%      /dev
fdesc                       1.0K     1.0K        0B         100%      /dev
<volfs>               512K     512K        0B         100%      /.vol
/dev/disk5                  234G     221G       13G          95%      /Volumes/Data2
automount -nsl [436]          0B       0B        0B         100%      /Network
automount -fstab [449]        0B       0B        0B         100%      /automount/Servers
automount -static [449]       0B       0B        0B         100%      /automount/static
/dev/disk10                 932G     444G      487G          48%      /Volumes/Data1

It's good to know about all of our volumes, but the super-important one is our system volume. The system volume is represented by the "/" on the first line of output above. We can see that of the three real volumes on this system, one is getting pretty close to being full (Data2 at 95% capacity = 'only' 13GB left!).

We can find out about what's in the mail queue with the 'mailq' command. This will tell us what has been deferred in a sendmail or postfix queue. I just happen to have an example: (see bottom figure.) Here, we see this server has three messages that are still waiting to be delivered for one reason or another. Sometimes, you'll find that messages to a certain destination are backed up because the mail host is genuinely down or unreachable. Other times, you'll find that routing is borked on your end, and the queued messages have a bit more of a random pattern.

There are several systems that will take a hash of your files, and then later tell you if anything has changed. This has become known as a tripwire function, named so after the application "Tripwire" that basically fathered this genre of utility. Naturally, the goal of running a system like this is security. If someone/something replaces your 'ls' binary with one that hides certain files, you want to know about it. I run tripwire on most of my Mac OS X boxes, more as a notification of things that have been installed than for the paranoid security reasons, so that's what I'll discuss here. Of course, there are others, such as radmind (which also can do a lot more than simply compare files), and Checkmate. We'll be discussing tripwire in a future column, but for now, I'm just going to go though the motions.

First, I go dump all of the old reports from the reports directory:

rm -f /usr/local/tripwire/report/*

Then, we want to run the tripwire and generate our new report:

/usr/local/tripwire/bin/tripwire -m c

Finally, we can update the database with the new changes, so we're reset for the next run:

/usr/local/tripwire/bin/tripwire -m u -a -r 
   /usr/local/tripwire/report/`ls /usr/local/tripwire/report` 
   -P "My really long passphrase" -v

What's a system without backup? Even if I have a tape backup running, I like to a) copy certain files locally on the same

$ mailq
-Queue ID-          -Size-           -Arrival Time-                -Sender/Recipient-
0C4125B83A8         2615             Fri Apr  1 02:59:56           MAILER-DAEMON
 (Name service error for name=example.com type=MX: Host not found, try again)
                                         
                                                                   Clarence.Smith@example.com

B8E2A4EF0EF         2628             Thu Mar 31 01:28:52           MAILER-DAEMON
 (Name service error for name=example.com type=MX: Host not found, try again)
                                         
                                                                   Eileen.Doe@example.com

D7ACD4439EE         3204             Wed Mar 30 07:59:25           MAILER-DAEMON
 (connect to mail.example.com[10.0.0.112]: Operation timed out)
                                         
                                                                   bob@example.com

-- 9 Kbytes in 3 Requests.

disk, simply for ease of access, and b) get files off of the server to a remote location. I use rsync for both of these tasks. If you plan on implementing this as outlined here, please note: you must replace the Apple-supplied rsync with RsyncX if you want to sync files with resource forks (http://archive.macosxlabs.org/rsyncx/rsyncx.html). So, what do we want to copy here? I like to get /etc locally:

rsync -a -q --delete /private/etc/ /backup/etc/

On a server running mail, I like to grab the mail store:

rsync -a -q --delete /var/imap/ /backup/var/imap/
rsync -a -q --delete /var/spool/imap/ /backup/var/spool/imap/

Lastly, I like to move certain files off-site. rsync will do that, too:

rsync --delete -l -a -v /Shares/shared/ store.radiotope.com:
   /Volumes/Backup/rsync-bu/shared/ --eahfs -stats

Lastly, I make sure I clean up any directories that may be building up files. Some of my servers run OpenBase databases (for the excellent DayLite by MarketCircle). One of the options for OpenBase is an automatic backup. Great! The only problem with this is that, while it's happy to make the backup, it doesn't know when to get rid of it. Files can pile up pretty quickly, which means disk space can disappear quickly, too. Here's where my favorite command comes in. The 'find' command is one of those commands that certainly fits the philosophy that we discussed earlier. However, it's also a bit of a Swiss Army knife in that it will handle certain tasks even though other utilities would do the job just fine. Yes, this will be talked about in more depth next month. The find command lets us find files based on several different criteria. One parameter is the age of the file. I want to delete any OpenBase backup files that haven't been modified for 7 days:

find /Library/OpenBase/Backup/ -mtime +7 -delete

This will rid us of files that have not been modified in 7 days in the /Library/OpenBase/Backup directory. find is very powerful, and can be a system admin's best friend on many occasions. However, while you're learning, please note: find is also one of those commands that you have to be a little careful with, and it will descend recursively into all of the directories below the path you specify. Used with the "-delete" switch, it can do damage! As a last measure in my old-file-removal routine, I like to also dump old quarantined mail caught by amavis:

find /var/quarantine/ -mtime +7 -delete

Put It All Together

Now that we know exactly what we want to do, let's turn it into a shell script. To do this, we need a text editor. I typically reach for vi. Apple tends to lead people through these examples with pico. While there are some GUI tools that will also do the trick (TextWrangler and SubEthaEdit come to mind), this is a column on using the terminal, and that's what we'll do! Remember: vi, emacs and pico can easily be used over ssh, with no access to a GUI at all - TextWrangler and SubEthaEdit can't!

Creating a shell script involves a few steps. We'll call this one 'nightlymaint.sh'. Get into vi to edit this file by typing "vi nightlymaint.sh". Press the 'i' key to go into insert mode. You should now see the word 'INSERT' at the bottom of your screen. First thing we need to do is to declare what kind of script this is:

#!/bin/bash

Alternatively, this could have been a perl script (#!/usr/bin/perl), PHP, Python, a different shell such as tcsh, or other variety. When you invoke this as an executable, the shell looks at this first line to determine which interpreter to use to run your script. This is a nice shorthand way to execute a script, rather than have to specify everything on the command-line. Then, we'll add each of our commands, one by one, with some comments. So, the whole thing looks like this:

#!/bin/bash

# nightlymaint.sh
# Our nightly maintenance routine
# Problems?  Contact Joe Admin
# version 1.0 - 2005-04-04

# Let the logs know that we've begun
logger -p local0.notice -i -t Nightly Starting nightly routine

# Get disk stats
/usr/bin/df -h

# See what's in the mail queue
/usr/bin/mailq

# Run tripwire reports
logger -p local0.notice -i -t Running tripwire reports
rm -f /usr/local/tripwire/report/*
/usr/local/tripwire/bin/tripwire -m c
/usr/local/tripwire/bin/tripwire -m u -a -r /usr/local/tripwire/report/`ls 
   /usr/local/tripwire/report` -P "My really long passphrase" -v

# Backup files
logger -p local0.notice -i -t Backing up files
/usr/bin/rsync -a -q --delete /private/etc/ /backup/etc/
/usr/bin/rsync -a -q --delete /var/imap/ /backup/var/imap/
/usr/bin/rsync -a -q --delete /var/spool/imap/ /backup/var/spool/imap/
/usr/bin/rsync --delete -l -a -v /Shares/shared/ store.radiotope.com:
   /Volumes/Backup/rsync-bu/shared/ --eahfs -stats

# Clean up old files
find /Library/OpenBase/Backup/ -mtime +7 -delete
find /var/quarantine/ -mtime +7 -delete

logger -p local0.notice -i -t Nightly maintenance complete

Lines that start with a hash mark ('number sign') are comments and are ignored by the bash interpreter. This script will execute, one line at a time from the beginning to the end. It sends its output to standard output. However, we're going to run this from cron, which, by default, will take any standard output and e-mail it to the owner of the crontab.

Let's save this script. If you're still with me in vi, press the escape key once, and then type ":" (colon, not semi-colon - so hit the shift key, too). The colon character puts you into command mode. When you see the colon character at the bottom of your screen, type 'wq' and press enter. That's short for 'w'rite the file and then 'q'uit. You'll be back at the command line. So, just one more thing: we need to mark this script executable. Type "chmod u+x nightlymaint.sh" and press return. This adds the executable bit to the permissions of our script. Without it, the shell won't (easily) execute our file.

I want to test this script, but a word of warning first: Do NOT run this script as-is. It needs to be adjusted for your environment. The 'logger', 'du' and 'mailq' lines are harmless. Everything else needs to be changed to some sane value. You may or may not have tripwire installed. If you do not, simply remove that section. What directories are you concerned with backing up? Naturally, the 'Clean up old files' section will remove files! Make sure you either remove these lines, or point them at a directory that makes sense for you!

Did you read all that? Really? OK. Now we'll....wait. Go read the preceding paragraph again....I'll wait.......OK, thanks. If you're really ready, let's run this sucker. At the command line, type "./nightlymaint.sh" (don't type the quotes), press enter, and you'll see a good deal of info come spewing back at you. You can also check /var/log/system.log and you'll see lines like the following produced by our script:

Apr  4 18:28:13 localhost Nightly[668]: maintenance complete

The output you see on your screen is the same information that will get sent to you via e-mail.

Last comments on this script: you may have noticed that I prefixed each command with its full path in the script, but usually don't bother when I simply call it on the command-line. Two reasons: First, we want to be sure we get the right binary, if there are multiple on our system. Secondly, it's a bit of a (good) habit: depending on how your script gets called, it may not have access to the environment, which means no path variable. So, absolute paths should go everywhere in a script like this.

Additionally, if you ever need to put a password or passphrase into a shell script (rare, but it happens), you should mark the file only readable by root ("chown root nightlymaint.sh", followed by "chmod 700 nightlymaint.sh").

Schedule It

We don't want to have to run this manually every day, do we? (Shouts of, "No!" heard nearby) So, let's get this into cron.

A script like this will need to run privileged as root. There are a few ways to do this. We could drop it in root's crontab. We could drop a line in the system crontab. Or, as a variant, we could ignore messing with cron directly at all. They're all good methods, and which you choose really depends on your style. However, OS X already schedules maintenance tasks that run on daily, weekly and monthly schedules. We're going to take advantage of that.

We need our script to run on a daily basis, so let's move our script to the appropriate place:

sudo mv nightlymaint.sh /etc/periodic/daily/700.nightlymaint.sh
sudo chmod 550 /etc/periodic/daily/700.nightlymaint.sh
sudo chown root:wheel /etc/periodic/daily/700.nightlymaint.sh

That's all! Now, the system will run our script as part of its regular 3:15am daily maintenance.

Closing time

This month we learned how to create a simple shell script. This script runs through its commands in order, with no conditions or loops. I will say that this exercise glossed over a few small details, and completely ignored the fact that OS X Server already has a decent way to monitor free disk space (called "diskspacemonitor"), and do some of what we talked about (see /var/log/daily.out). This example script certainly isn't perfect, nor was it meant to be all-inclusive of every system administration ill. It introduced you to a few new commands. More importantly, it should give you ideas. It should allow you to create something that works for you.

You can now start to automate some tasks that are repetitive in your computing environment. Perhaps you need stats on processes chewing up too much CPU. Perhaps you need to monitor open file count. Whatever it is, now you have a way. Ask questions - here if you'd like. You can send in questions to emarczak@mactech.com Once you start automating, you start getting some time back. Time to read that new book on OS X, time to spend with friends and family, time to watch that new Battlestar Galactica episode, or time to rest. All fine goals, no?


Ed Marczak owns and operates Radiotope, a technology consulting company that asks, "how can we help you automate that?" Automate at http://www.radiotope.com

 
AAPL
$467.36
Apple Inc.
+0.00
MSFT
$32.87
Microsoft Corpora
+0.00
GOOG
$885.51
Google Inc.
+0.00

MacTech Search:
Community Search:

Software Updates via MacUpdate

Acorn 4.1 - Bitmap image editor. (Demo)
Acorn is a new image editor built with one goal in mind - simplicity. Fast, easy, and fluid, Acorn provides the options you'll need without any overhead. Acorn feels right, and won't drain your bank... Read more
Mellel 3.2.3 - Powerful word processor w...
Mellel is the leading word processor for OS X, and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
Iridient Developer 2.2 - Powerful image...
Iridient Developer (was RAW Developer) is a powerful image conversion application designed specifically for OS X. Iridient Developer gives advanced photographers total control over every aspect of... Read more
Delicious Library 3.1.2 - Import, browse...
Delicious Library allows you to import, browse, and share all your books, movies, music, and video games with Delicious Library. Run your very own library from your home or office using our... Read more
Epson Printer Drivers for OS X 2.15 - Fo...
Epson Printer Drivers includes the latest printing and scanning software for OS X 10.6, 10.7, and 10.8. Click here for a list of supported Epson printers and scanners.OS X 10.6 or laterDownload Now Read more
Freeway Pro 6.1.0 - Drag-and-drop Web de...
Freeway Pro lets you build websites with speed and precision... without writing a line of code! With it's user-oriented drag-and-drop interface, Freeway Pro helps you piece together the website of... Read more
Transmission 2.82 - Popular BitTorrent c...
Transmission is a fast, easy and free multi-platform BitTorrent client. Transmission sets initial preferences so things "Just Work", while advanced features like watch directories, bad peer blocking... Read more
Google Earth Web Plug-in 7.1.1.1888 - Em...
Google Earth Plug-in and its JavaScript API let you embed Google Earth, a true 3D digital globe, into your Web pages. Using the API you can draw markers and lines, drape images over the terrain, add... Read more
Google Earth 7.1.1.1888 - View and contr...
Google Earth gives you a wealth of imagery and geographic information. Explore destinations like Maui and Paris, or browse content from Wikipedia, National Geographic, and more. Google Earth... Read more
SMARTReporter 3.1.1 - Hard drive pre-fai...
SMARTReporter is an application that can warn you of some hard disk drive failures before they actually happen! It does so by periodically polling the S.M.A.R.T. status of your hard disk drive. S.M.... Read more

Strategy & Tactics: World War II Upd...
Strategy & Tactics: World War II Update Adds Two New Scenarios Posted by Andrew Stevens on August 12th, 2013 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Expenses Planner Review
Expenses Planner Review By Angela LaFollette on August 12th, 2013 Our Rating: :: PLAIN AND SIMPLEUniversal App - Designed for iPhone and iPad Expenses Planner keeps track of future bills through due date reminders, and it also... | Read more »
Kinesis: Strategy in Motion Brings An Ad...
Kinesis: Strategy in Motion Brings An Adaptation Of The Classic Strategic Board Game To iOS Posted by Andrew Stevens on August 12th, 2013 [ | Read more »
Z-Man Games Creates New Studio, Will Bri...
Z-Man Games Creates New Studio, Will Bring A Digital Version of Pandemic! | Read more »
Minutely Review
Minutely Review By Jennifer Allen on August 12th, 2013 Our Rating: :: CROWDSOURCING WEATHERiPhone App - Designed for the iPhone, compatible with the iPad Work together to track proper weather conditions no matter what area of the... | Read more »
10tons Discuss Publishing Fantasy Hack n...
Recently announced, Trouserheart looks like quite the quirky, DeathSpank-style fantasy action game. Notably, it’s a game that is being published by established Finnish games studio, 10tons and developed by similarly established and Finnish firm,... | Read more »
Boat Watch Lets You Track Ships From Por...
Boat Watch Lets You Track Ships From Port To Port Posted by Andrew Stevens on August 12th, 2013 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Expenses Review
Expenses Review By Ruairi O'Gallchoir on August 12th, 2013 Our Rating: :: STUNNINGiPhone App - Designed for the iPhone, compatible with the iPad Although focussing primarily on expenses, Expenses still manages to make tracking... | Read more »
teggle is Gameplay Made Simple, has Play...
teggle is Gameplay Made Simple, has Players Swiping for High Scores Posted by Andrew Stevens on August 12th, 2013 [ permalink ] | Read more »
How To: Manage iCloud Settings
iCloud, much like life, is a scary and often unknowable thing that doesn’t always work the way it should. But much like life, if you know the little things and tweaks, you can make it work much better for you. I think that’s how life works, anyway.... | Read more »

Price Scanner via MacPrices.net

13″ 2.5GHz MacBook Pro on sale for $150 off M...
B&H Photo has the 13″ 2.5GHz MacBook Pro on sale for $1049.95 including free shipping. Their price is $150 off MSRP plus NY sales tax only. B&H will include free copies of Parallels Desktop... Read more
iPod touch (refurbished) available for up to...
The Apple Store is now offering a full line of Apple Certified Refurbished 2012 iPod touches for up to $70 off MSRP. Apple’s one-year warranty is included with each model, and shipping is free: -... Read more
27″ Apple Display (refurbished) available for...
The Apple Store has Apple Certified Refurbished 27″ Thunderbolt Displays available for $799 including free shipping. That’s $200 off the cost of new models. Read more
Apple TV (refurbished) now available for only...
The Apple Store has Apple Certified Refurbished 2012 Apple TVs now available for $75 including free shipping. That’s $24 off the cost of new models. Apple’s one-year warranty is standard. Read more
AnandTech Reviews 2013 MacBook Air (11-inch)...
AnandTech is never the first out with Apple new product reviews, but I’m always interested in reading their detailed, in-depth analyses of Macs and iDevices. AnandTech’s Vivek Gowri bought and tried... Read more
iPad, Tab, Nexus, Surface, And Kindle Fire: W...
VentureBeat’s John Koetsier says: The iPad may have lost the tablet wars to an army of Android tabs, but its still first in peoples hearts. Second place, however, belongs to a somewhat unlikely... Read more
Should You Buy An iPad mini Or An iPad 4?
Macworld UK’s David Price addresses the conundrum of which iPAd to buy? Apple iPad 4, iPad 2, iPad mini? Or hold out for the iPad mini 2 or the iPad 5? Price notes that potential Apple iPad... Read more
iDraw 2.3 A More Economical Alternative To Ad...
If you’re a working graphics pro, you can probably justify paying the stiff monthly rental fee to use Adobe’s Creative Cloud, including the paradigm-setting vector drawing app. Adobe Illustrator. If... Read more
New Documentary By Director Werner Herzog Sho...
Injuring or even killing someone because you were texting while driving is a life-changing experience. There are countless stories of people who took their eyes off the road for a second and ended up... Read more
AppleCare Protection Plans on sale for up to...
B&H Photo has 3-Year AppleCare Warranties on sale for up to $105 off MSRP including free shipping plus NY sales tax only: - Mac Laptops 15″ and Above: $244 $105 off MSRP - Mac Laptops 13″ and... Read more

Jobs Board

Sales Representative - *Apple* Honda - Appl...
APPLE HONDA AUTOMOTIVE CAREER FAIR! NOW HIRING AUTO SALES REPS, AUTO SERVICE BDC REPS & AUTOMOTIVE BILLER! NO EXPERIENCE NEEDED! Apple Honda is offering YOU a Read more
*Apple* Developer Support Advisor - Portugue...
Changing the world is all in a day's work at Apple . If you love innovation, here's your chance to make a career of it. You'll work hard. But the job comes with more than Read more
RBB - *Apple* OS X Platform Engineer - Barc...
RBB - Apple OS X Platform Engineer Ref 63198 Country USA…protected by law. Main Function | The engineering of Apple OS X based solutions, in line with customer and Read more
RBB - Core Software Engineer - Mac Platform (...
RBB - Core Software Engineer - Mac Platform ( Apple OS X) Ref 63199 Country USA City Dallas Business Area Global Technology Contract Type Permanent Estimated publish end Read more
*Apple* Desktop Analyst - Infinity Consultin...
Job Title: Apple Desktop Analyst Location: Yonkers, NY Job Type: Contract to hire Ref No: 13-02843 Date: 2013-07-30 Find other jobs in Yonkers Desktop Analyst The Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.