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
$103.30
Apple Inc.
+0.80
MSFT
$45.09
Microsoft Corpora
-0.34
GOOG
$577.33
Google Inc.
+5.73

MacTech Search:
Community Search:

Software Updates via MacUpdate

TextSoap 7.4.0 - Flexible text editing u...
TextSoap is for people who work with text. TextSoap effortlessly cleans up text from endlessly different formats. Wash away unwanted characters, spaces, tabs. Fix paragraphs with hard returns at the... Read more
NetShade 6.0.2 - Browse privately using...
NetShade is an Internet security tool that conceals your IP address on the web. NetShade routes your Web connection through either a public anonymous proxy server, or one of NetShade's own dedicated... Read more
Mac DVDRipper Pro 5.0 - Copy, backup, an...
Mac DVDRipper Pro is the DVD backup solution that lets you protect your DVDs from scratches, save your batteries by reading your movies from your hard disk, manage your collection with just a few... Read more
pwSafe 3.1 - Secure password management...
pwSafe provides simple and secure password management across devices and computers. pwSafe uses iCloud to keep your password databases backed-up and synced between Macs and iOS devices. It is... Read more
StatsBar 1.8 - Monitor system processes...
StatsBar gives you a comprehensive and detailed analysis of the following areas of your Mac: CPU usage Memory usage Disk usage Network and bandwidth usage Battery power and health (MacBooks only)... Read more
Path Finder 6.5.5 - Powerful, award-winn...
Path Finder is a file browser that combines the familiar Finder interface with the powerful utilities and innovative features. Just a small selection of the Path Finder 6 feature set: Dual pane... Read more
QuarkXPress 10.2.1 - Desktop publishing...
With QuarkXPress, you can communicate in all the ways you need to -- and always look professional -- in print and digital media, all in a single tool. Features include: Easy to Use -- QuarkXPress is... Read more
Skype 6.19.0.450 - Voice-over-internet p...
Skype allows you to talk to friends, family and co-workers across the Internet without the inconvenience of long distance telephone charges. Using peer-to-peer data transmission technology, Skype... Read more
VueScan 9.4.41 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
Cloud 3.0.0 - File sharing from your men...
Cloud is simple file sharing for the Mac. Drag a file from your Mac to the CloudApp icon in the menubar and we take care of the rest. A link to the file will automatically be copied to your clipboard... Read more

Latest Forum Discussions

See All

Modern Combat 5 Gets a Major Multiplayer...
Modern Combat 5 Gets a Major Multiplayer Update Posted by Jessica Fisher on September 2nd, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Alien Creeps TD Review
Alien Creeps TD Review By Jennifer Allen on September 2nd, 2014 Our Rating: :: EXPENSIVE DEFENSESUniversal App - Designed for iPhone and iPad Alien Creeps TD would be a fun if unremarkable Tower Defense game, but its heavy focus on... | Read more »
The Journey Down: Chapter Two Review
The Journey Down: Chapter Two Review By Jennifer Allen on September 2nd, 2014 Our Rating: :: DARK YET ENTICINGUniversal App - Designed for iPhone and iPad It’s a little dark, in every sense of the word, but The Journey Down:... | Read more »
Function Space, a Social Network App for...
Function Space, a Social Network App for Science, Launches on iOS Posted by Ellis Spice on September 2nd, 2014 [ permalink ] | Read more »
Stupidfast – How Taylor Martinez Switche...
How do you make an Endless Running game more than just another Endless Running game? By adding real life prizes to it, of course! That’s the thinking behind StupidFast: a game designed for football enthusiasts, and the brainchild of former college... | Read more »
Little Raiders: Robin’s Revenge Review
Little Raiders: Robin’s Revenge Review By Jennifer Allen on September 2nd, 2014 Our Rating: :: CASUAL RAIDINGUniversal App - Designed for iPhone and iPad Combining simple combat with village building is a potent combination for... | Read more »
Treasure Tombs: Ra Deal Coming from Bulk...
Treasure Tombs: Ra Deal Coming from Bulkypix and Dark Tonic This Fall Posted by Jessica Fisher on September 2nd, 2014 [ permalink ] Dark Tonic and | Read more »
Pirate Bash Review
Pirate Bash Review By Nadia Oxford on September 2nd, 2014 Our Rating: :: BAD PIRATES, GOOD TIMESUniversal App - Designed for iPhone and iPad Pirate Bash’s turn-based battles add an intriguing twist to a typical physics game.   | Read more »
Tiny Tower Vegas Review
Tiny Tower Vegas Review By Jennifer Allen on September 2nd, 2014 Our Rating: :: STEADY DEVELOPMENTUniversal App - Designed for iPhone and iPad Build a huge tower again but Vegas-style in Tiny Tower Vegas.   | Read more »
The Manhattan Project Review
The Manhattan Project Review By Andrew Fisher on September 2nd, 2014 Our Rating: :: ROCKET SCIENCEUniversal App - Designed for iPhone and iPad The Manhattan Project offers a great Euro-style gameplay experience, but it is totally... | Read more »

Price Scanner via MacPrices.net

Apple refurbished iPads available for up to $...
Apple is offering Certified Refurbished iPad Airs for up to $140 off MSRP. Apple’s one-year warranty is included with each model, and shipping is free. Stock tends to come and go with some of these... Read more
Are We Now In The Post-Post-PC Era?
A longtime and thoroughgoing laptop aficionado, I was more than a little dismayed by Steve Jobs’s declaration back in 2010 when he sprang the iPad on an unsuspecting world. that we’d entered a “post-... Read more
PC Outlook Improves, But 2014 Shipments Still...
According to the International Data Corporation (IDC) Worldwide Quarterly PC Tracker, worldwide PC shipments are expected to fall by -3.7 percent in 2014. To hat’s actually an improvement from the... Read more
IDC Lowers Tablet Sales Projections for 2014...
Following a second consecutive quarter of softer than expected demand, International Data Corporation (IDC) has lowered its worldwide tablet plus 2-in-1 forecast for 2014 to 233.1 million units. The... Read more
Apple now offering refurbished 21-inch 1.4GHz...
The Apple Store is now offering Apple Certified Refurbished 21″ 1.4GHz iMacs for $929 including free shipping plus Apple’s standard one-year warranty. Their price is $170 off the cost of new models,... Read more
Save $50 on the 2.5GHz Mac mini, on sale for...
B&H Photo has the 2.5GHz Mac mini on sale for $549.99 including free shipping. That’s $50 off MSRP, and B&H will also include a free copy of Parallels Desktop software. NY sales tax only. Read more
Save up to $300 on an iMac with Apple refurbi...
The Apple Store has Apple Certified Refurbished iMacs available for up to $300 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free. These are the best prices on... Read more
The Rise of Phablets
Carlisle & Gallagher Consulting Group, a businesses and technology consulting firm focused solely on the financial services industry, has released an infographic depicting the convergence of... Read more
Bad Driver Database App Allows Good Drivers t...
Bad Driver Database 1.4 by Facile Group is a new iOS and Android app that lets users instantly input and see how many times a careless, reckless or just plain stupid driver has been added to the... Read more
Eddy – Cloud Music Player for iPhone/iPad Fre...
Ukraine based CapableBits announces the release of Eddy, its tiny, but smart and powerful cloud music player for iPhone and iPad that allows users to stream or download music directly from cloud... Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.