Shell scripting: Iterate over a list line-by-line instead of word-by-word

Let’s say a directory contains these files:

My First File.txt
My Second File.txt

If you wanted to take an action on each distinct file, you might try something like this:

for file in `ls ~`; do echo $file; done

The output will look like below, because the spaces and line breaks are both treated delineators of a new item in the list (ie. a new assignment to the “file” variable.)


If your intent was to act on the complete filename (using only line breaks as indicators of a new item) then you can instead pipe your output through a “while” loop like this:

ls ~ | while read item; do echo $item; done

Which will yield the desired output:

My First File.txt
My Second File.txt

Automatically Update WordPress, Themes, and Plugins using WP-CLI

WordPress is a hugely popular blog/CMS platform, but with widespread adoption comes risk: It is a common target for hackers, exploits, etc. Accordingly, you should make sure it gets regular updates.

WordPress has a built-in update mechanism but this also requires that its PHP files be writable by the web server, introducing a new set of security risks.

Luckily there is another option. Instead you can use a command-line tool called WP-CLI, which enables us to script WordPress updates.

These instructions will outline the steps necessary to install WP-CLI, create a script to update multiple sites at once, and install that script as a cron job to ensure updates happen on a regular schedule.

Before You Begin

As with any WordPress maintenance tasks, I recommend making regular backups of your database and files.

For this process to succeed, you’ll need to run your script as a user who has permission to modify the WordPress files. This could be your regular user account, but you might also want to create a dedicated user such with a name like ‘scripts’, and give it write permissions to your WordPress files. It is not recommended to run this as root.

  1. Install WP-CLI
    Install WP-CLI (adapted from

    curl -O
    chmod +x wp-cli.phar
    sudo mv wp-cli.phar /usr/local/bin/wp
  2. Test WP-CLI
    Run as a user who has write privileges to your WordPress site. If everything works you should get a series of “Success” messages, and/or a list of updated items.

    cd /var/www/html   # replace with path to your site 
    /usr/local/bin/wp core update
    /usr/local/bin/wp core update-db
    /usr/local/bin/wp theme update --all
    /usr/local/bin/wp plugin update --all
  3. Create an Update Script
    Use your favorite text editor to create a new shell script. In that script, put the following code:

    # Absolute paths of WordPress sites. Space-separated.
    sites="/var/www/html/site1 /var/www/html/site2 /var/www/html/site3"
    for site in $sites; do
    echo $site
    /usr/local/bin/wp core update --path=$site --quiet
    /usr/local/bin/wp core update-db --path=$site --quiet
    /usr/local/bin/wp theme update --all --path=$site --quiet
    /usr/local/bin/wp plugin update --all --path=$site --quiet
  4. Make The Script Executable
    chmod 700 wp-update
  5. Test The Update Script

    If everything works you’ll see a series of “Success” messages, and/or a list of updated items. If you see errors, double-check that your current user has permission to write to the WordPress site directories.

  6. Install Cron Job
    Make sure you’re still logged in as a user who has write permissions for the WordPress site directories.

    crontab -e

    Now create a new cron entry like this one, including the correct path to your update script. In this example it will run every day at 2:30am.

    30 2 * * * /home/scripts/bin/wp-update

    Close and save your crontab file.

  7. If everything worked correctly, your WordPress sites will now auto-update every night.

Desktop Picture Profile Creator

DesktopPictureProfileCreator is a simple bash script that generates mobileconfig profiles for managing the desktop picture on macOS computers.

Run the script followed by the path to the image you want to set as the desktop picture. For example “sh /Library/Desktop\ Pictures/Aqua\ Blue.jpg”. Whatever path you specify must exist in the same location on the computer(s) you intend to deploy it to.

The profile will be created on the desktop of whichever user runs the script. In the example above, the profile would appear at ~/Desktop/Desktop-Aqua-Blue-jpg.mobileconfig

For more info and to download, check out my mobileconfigs repository on GitHub.

File ownership considerations with Nginx and php-fpm

I recently switched my CentOS 7 web server over to Nginx and php-fpm.

From my experience with Apache I assumed that PHP scripts would be executed by the same user the web server is running as — ‘nginx’ in this case. But this could no longer be taken for granted since php-fpm is a separate process from the web server.

In my configuration php-fpm was actually running as the ‘apache’ user. This meant any files that need to be writable by PHP scripts should still be owned by that user or group rather than ‘nginx’.

A common scenario where this matters is if your users need to be able to install WordPress updates, Plugins, or Themes via the browser without entering additional credentials. In order for this to work, the web server (or in this case, php-fpm) must be able to write to the files in question.

If you are wrestling with file permissions, or are unsure of the correct permissions to set in this scenario, be sure to confirm which user and group are specified in /etc/php-fpm.d/www.conf

# grep "^user\|^group" /etc/php-fpm.d/www.conf 
user = apache
group = apache

Or you can check the actual running process with pstree:

$ pstree -ua | grep "nginx\|php"
  |   |-nginx,nginx
  |   |-nginx,nginx
  |   |-nginx,nginx
  |   |-nginx,nginx
  |   |-nginx,nginx
  |   |-nginx,nginx
  |   |-nginx,nginx
  |   `-nginx,nginx
  |   |-php-fpm,apache              
  |   |-php-fpm,apache              
  |   |-php-fpm,apache              
  |   |-php-fpm,apache              
  |   |-php-fpm,apache              
  |   |-php-fpm,apache              
  |   `-php-fpm,apache

Scheduling Reboot of Motorola Cable Modem

UPDATE: This no longer works for me because a recent firmware update removed the Restart command from the web interface.

Periodically my Motorola SURFboard SB6141 cable modem stops talking to the outside world. I don’t know why, but restarting the modem fixes it every time. Rather than having to regularly power-cycle it by hand – or, heaven forbid, buy new hardware – I decided to schedule an automatic reboot.

The modem itself doesn’t have a facility for automatic reboots, but it has a web page with a button to restart it! <> So all we have to do is automate the clicking of that button.

If you have this model, or a similar one, this command should work. Just paste it into your terminal/console program and hit Enter, and see if you temporarily lose connectivity to the Internet. If you do, then it worked:

/usr/bin/curl -G -d reset_modem=Restart%20Cable%20Modem > /dev/null

Once you verify it works for your modem, add it as a cron job. Mine looks like this, running daily at 4:30am:

30 4 * * * /usr/bin/curl -G -d reset_modem=Restart%20Cable%20Modem > /dev/null


  • The only real dependency is that you have curl installed. On Mac OS X, CentOS, and Debian it lives at /usr/bin/curl but if you’re using a different distro/OS you’ll want to make sure the path is correct.
  • Verify the IP of your cable modem is correct. For most/all Motorola models it’s but try Googling yours if that doesn’t bring up a status page in a browser.
  • If the IP is correct, make sure the values passed by the restart button are the same as my example. (Go to the page that has the button, make sure that URL matches my example, then view source to make sure the name and value of the button match.)

Yosemite Captive Portal Fix

A number of Yosemite Macs are having problems with our organization’s Wi-Fi captive portal.

If you don’t first sign in via the window presented by Captive Network Assistant, the system appears to have zero network connectivity. You can’t ping anything, you can’t perform DNS lookups, etc. It’s as if the OS is actively blocking all attempted connectivity until it knows you have succeeded in authenticating.

Consequently, Browsers throw errors instead of presenting our captive portal page. This is a big problem, since in many cases the Captive Network Assistant app was previously disabled or deleted!

I found I could whitelist our network so that it lets applications try connecting even if the Captive Network Assistant has not yet been placated.

Just copy the command below, replacing NAMEOFNETWORK with the SSID of your captive portal network. A reboot may be necessary, but it may also be enough to simply switch Wi-Fi off and on again.

sudo /usr/libexec/PlistBuddy -c "Add ScrapingParameters:DisabledRealms:0 string @NAMEOFNETWORK" /Library/Preferences/SystemConfiguration/CaptiveNetworkSupport/Settings.plist


AirPrintProfileCreator is a simple bash script which aids you in creating mobileconfig profiles that enable iOS devices to print to a CUPS server.

Download and run the script on your server (“sudo sh”) and follow the prompts on screen. When the profile has been created, you can install it on your iOS device via Configurator, MDM, or just post it online or email it to yourself.

The script should work on any recent Mac or other *nix systems with CUPS installed, but I have only tested on OS X v 10.9. Your CUPS server must have a static/reserved IP.

For more info and to download, check out my mobileconfigs repository page on GitHub.

Using Configuration Profiles to Enable iOS Printing to CUPS Servers

Thanks to Configuration Profiles, it is now possible to print to any printer that’s shared using CUPS, including both Mac OS X and Linux servers, without help from third-party software.

Aside from the obvious benefit of being free, the great thing about this approach is that the print server does not have to broadcast its presence on the same network as the iOS device. It works across routable subnets or even VPN connections.

Before you begin, please make sure you have all of the following:

  • iPhone, iPad, or iPod touch running iOS 7 or higher.
  • Mac or Linux system running CUPS, with a static/reserved address.
  • Mac running Apple Configurator to create the profile.

Enable Printer Sharing

  • OS X:
    1. Open System Preferences.
    2. Click on Sharing.
    3. Enable Printer Sharing
    4. Verify that the desired printers are enabled for everyone.
  • Linux or other *NIX:
    1. Visit the CUPS web interface <http://localhost:631>
    2. Click on the Administration tab.
    3. Enable the option to Share printers connected to this system.
      Tip: You may also need to click on the Printers tab and modify individual printers to enable sharing for them individually.

Create Profile

  1. Open Apple Configurator on your Mac.
  2. Select the Supervise tab.
  3. Click the + (plus) button.
  4. Click Create New Profile…
  5. In the Name field, give your profile a title.
  6. Click on AirPrint.
  7. Click the Configure button.
  8. Click the + (plus) button.
  9. Select the Undiscoverable radio button.
  10. Enter the IP Address of your CUPS server.
  11. Enter the Resource path, which is the path to the print queue. It will be similar to /printers/queue-name
    Tip: Run lpstat -p in the Terminal on your CUPS server to get a list of available queue names. A printer titled My_Laser_Printer would have a path of /printers/My_Laser_Printer
  12. Click the Save button.
  13. Repeat steps 9 through 13 for any additional printers you wish to configure.
  14. Click Save to save your new profile.

Install Profile

There are many ways you can send the profile to your iOS device(s). Here are a few ideas:

  • Use the Prepare feature in Configurator.
  • Export the profile, email it to yourself, and open the attachment on your device.
  • Post it on a web server and visit the URL in Safari.
  • Push it to the device via an MDM service.


Once your profile has been installed, try printing from any app. Your printer should appear automatically. If it doesn’t, here are a few things you can try:

  • Double-check the queue name and the IP address of the CUPS server.
  • Verify that the cups server is reachable from the network your iOS device is on.
    Tip: Visit in a browser. You may receive a message that the CUPS web interface is not enabled, but that’s OK as long as the page comes up.
  • Make sure print sharing is enabled in general, as well as for the specific printer(s) you added to your profile.