Photo autocopy from SD card

Copying photos from the SD card of my camera is a very common task. And always the same pattern. Take the card out of the camera, put it into the sd card reader, wait for the automount popup, browse to the picture directory, select all images, cut all images, browse to my photo directory, create a new folder, name the folder, move to the folder, paste the pictures into it, wait for the finished job.

Sound boring? Sure, it is!

Now, how to make this one a little smarter? Maybe scripting some of those steps is a cool idea. So how would this work in a perfect world?

The idea

Taking the SD card out of the camera and putting it into the SD card reader does not seem to be easily scripted, that’s why I focused on the other steps ;-). Okay, I’d like to put the card into the reader, just wait for all the images to be moved and then put the card back into the camera.

But how to reach this goal? Maybe you’re interested in my solution…

One thing we need to know: When hotplugging devices to my Ubuntu system the kernel tells the udev daemon about that new device. The kernel informs the udev daemon about a lot of different events, like adding and removing hardware, volumes and so on. But how to get closer to these events?

Digging into udev events

There is an interesting tool named udevadm which can be used to monitor the incoming events. Let’s see which events are fired when I put the SD card into the card reader:

> udevadm monitor
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent
KERNEL[1316365297.577602] add /devices/pci0000:00/0000:00:1e.0/0000:15:00.2/mmc_host/mmc0/mmc0:b368 (mmc)
KERNEL[1316365297.578091] add /devices/virtual/bdi/179:0 (bdi)
UDEV [1316365297.579008] add /devices/virtual/bdi/179:0 (bdi)
UDEV [1316365297.579058] add /devices/pci0000:00/0000:00:1e.0/0000:15:00.2/mmc_host/mmc0/mmc0:b368 (mmc)
KERNEL[1316365297.580552] add /devices/pci0000:00/0000:00:1e.0/0000:15:00.2/mmc_host/mmc0/mmc0:b368/block/mmcblk0 (block)
KERNEL[1316365297.580644] add /devices/pci0000:00/0000:00:1e.0/0000:15:00.2/mmc_host/mmc0/mmc0:b368/block/mmcblk0/mmcblk0p1 (block)
UDEV [1316365297.617556] add /devices/pci0000:00/0000:00:1e.0/0000:15:00.2/mmc_host/mmc0/mmc0:b368/block/mmcblk0 (block)
UDEV [1316365298.314850] add /devices/pci0000:00/0000:00:1e.0/0000:15:00.2/mmc_host/mmc0/mmc0:b368/block/mmcblk0/mmcblk0p1 (block)

These are a lot of of events, but there is only one needed, so it is needed to choose the correct one. One way would be to try one event after each other, but there is a better way: Now one need to know that it is possible to react on specific events and run some script every time such an event is received. The script is executed and has access to the whole environment which is populated with some useful information.

To get a little more information create a matching udev rule and a small script which appends the whole environment to a temporary dump file:

Store the udev rule in the rules directory /etc/udev/rules.d/99-sd-autocopy.rules:

SUBSYSTEM=="block", ACTION=="add", RUN="/usr/local/bin/sd-autocopy"

The udev daemon should recognize that rule immediately. If a restart of the udev daemon might help.

Now create the script to dump the environment at the path specified in the udev rule /usr/local/bin/sd-autocopy:

echo ======================== >> /tmp/test
env >> /tmp/test

Then make the script executable:

chmod +x /usr/local/bin/sd-autocopy

Now simply plug the SD card of your choice into your SD card reader and have a look at the contents of /tmp/test afterwards. The file should contain information of several events where the single events are separated by the lines of equal sings.

Having these events recorded it is now possible to grab the important information for the script. When looking at the single events we see that there is one ADD event for the added disk and one ADD event for each partition. We are only interested in the partition events and need the first partition of the device, so add the following conditions to the udev rule:


which should result in this rule:

SUBSYSTEM=="block", ACTION=="add", ENV{DEVTYPE}=="partition", ENV{UDISKS_PARTITION_NUMBER}=="1", RUN="/usr/local/bin/sd-autocopy"

After changing the rule, deleting the /tmp/test file and an additional test we can see that the test sd-autocopy script gets only called with the correct event. This event comes with the following environment variables:

DEVLINKS=/dev/disk/by-id/mmc-SDC_0x000006d4-part1 /dev/disk/by-path/pci-0000:15:00.2-part1 /dev/disk/by-uuid/F84E-1690

The sd-autocopy script

With these information the SD card can be identified. It will use the ID_SERIAL and DEVNAME environment variables in the script. Now I identified the tasks which need to be performed by the sd-autocopy script:

  1. Verify the sd card to be a handled one
  2. Mount the partition
  3. Find all images on the partition
  4. Create a folder for each date in a target base path
  5. Move the files to the date specific folder
  6. Unmount the partition

The script is placed in /usr/local/bin/sd-autocopy (just remove the testing code from that file). And here is the finished script:

# sd-autocopy - Copies/Moves images automatically from inserted media
# Copyright (c) 2011 Lars Michelsen <lm@larsmichelsen.com>
# License:
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# But this in curly braces and detach the whole block to background
# for not blocking udev during copying.
# END OF CONFIGURATION ########################################################
# To enable debugging without udev events uncomment the lines below
# Catch unset but needed vars - terminate script
if [ -z "$ID_SERIAL" ] || [ -z "$DEVNAME" ]; then
    exit 1
if ! echo "$HANDLED_SERIALS" | grep $ID_SERIAL >/dev/null 2>&1; then
    logger -t sd-autocopy "Got unhandled serial, skipping (Serial: $ID_SERIAL)"
    exit 1
logger -t sd-autocopy "Got handled serial ($ID_SERIAL), moving images..."
if [ ! -d "$MOUNT" ]; then
    mkdir -p "$MOUNT"
LINES=$(find "$MOUNT" -regextype posix-egrep -iregex "$FILE_MATCH" -printf "%TY-%Tm-%Td_%p\n")
DATES=$(echo "$LINES" | cut -d_ -f1 | uniq)
# First create the directories when missing
for DATE in $DATES; do
    echo $DATE
    mkdir -p "$TARGET/$DATE"
# Move the images
for LINE in $LINES; do
    logger -t sd-autocopy "Moving $FILE to $TARGET/$DATE"
umount $DEVNAME
# Fix the permissions for all directories recursive where
# files have been copied/moved to
for DATE in $DATES; do
    chown -R $USER.$USER "$TARGET/$DATE"
logger -t sd-autocopy "Finished job. Moved $NUM images."
} &
exit 0

The script can easily be extended by modifying the first few lines:

# Move or copy - or something different
  • USER is needed a) for gathering the target directory and b) for owning the copied files.
  • TARGET holds the path to the target base directory.
  • HANDLED_SERIALS holds a list of space separated serial numbers of the SD cards to use the script for.
  • MOUNT contains the mountpoint for the first partition on the SD card.
  • FILE_MATCH is the matching regex for the files to be copied. It applies to the whole path of the files.
  • TRANSFER defines the transfer mode. For example “mv” to move the files or “cp” to copy them.

The script sends it’s log entries to syslog, so you might want to have a look at the syslog files for debugging.

Btw. the script is not limited to handling images. By just modifying the FILE_MATCH pattern it is possible to copy other filetypes.

Comments (14) Trackbacks (0)
  1. GPNo Gravatar
    06:00 on March 17th, 2013

    Thank you, thank you, Lars! I just KNEW someone had already done this, and I was positive if I found it, it would be a better bash script than I could ever write! :)

    I am going to use this to automate workflow for my Nikon DSLR. My goal is to use this script to trigger a file copy and launch a photo browser to do a quick triage upon SD card import and cull any bad photos. Another script will archive the Camera Originals (RAW files) in a safe read-only share, and then have the RAWs and JPGs split to different working shares, so my wife can manage JPGs in iPhoto on her Mac, while I use RAWs in LightRoom, and everything that we edit gets archived on a network drive.

    Thanks again for sharing your script publicly!

  2. LeadNo Gravatar
    22:39 on March 31st, 2013

    thank you for this beautiful script, it almost does what I need. Just a few adjustment and combined with a Raspberry Pi it’ll be a foto-autocopystation on my wedding in may!

  3. FredrikNo Gravatar
    15:20 on April 13th, 2013

    Thank you for sharing interesting ideas! I used some parts of your script and had a thought of copy the files from my camera’s memcard to one location and rename them there based on EXIF, and copy the AVCHD from the camcorder’s memcard to another location and then automatically convert to mp4-files. However, I interpreted your article as if every SD card had a unique ID_SERIAL, but in my logs, both my memcards have the same ID_SERIAL, as if it was read from the cardreader. Hopefully I can identify them by ID_FS_LABEL instead, but I was curious about your opinion.

    Thank you! /Fredrik

  4. DanNo Gravatar
    04:15 on April 19th, 2013

    Thanks for your script — I’ve got this working fine, but I’ve been trying to provide some feedback to show that the files have been copied and the card is safe to remove. I’m trying to use matedialog (running Linux Mint), which works fine when the script is run from a terminal, but the notification won’t display when the script is run from udev. I thought it might have something to do with udev killing long-running processes, but I’m not sure whether there’s a workaround. Any ideas?

  5. LaMiNo Gravatar
    08:49 on April 19th, 2013

    Yes. udev kills long running processes. That is the reason for the {…}& construct in the sd-autocopy script. It makes the script parts within those brackets executed asynchronous. My solution for a visual feedback was opening a xterm window at start, showing the syslog messages and hiding it after the user hit any key in the terminal.

  6. LucaNo Gravatar
    19:53 on January 22nd, 2014

    hi Lars, great :) … the thing is I don’t have a linux box for this … any chance you know how to do it in windows 7 ? … been trying to find the event for the sd insertion but I don’t seem to able to find it … i could then attach the event to a task and the job would be done :) thanks Luca

  7. LSNo Gravatar
    00:27 on May 27th, 2014

    Hi Lars, Your script is awesome. Here are some bits I changed:

    • Included DEVNAME= in the user-part of the script, just because

    • Changed TRANSFER=”rsync -aq -t”: What it does is skipping already existing files and preserving the time stamp (both crucial if you ask me), plus you maintain the original files on the original SD card

    • Hardening RPi due to ON/OFF scenario. There is couple of things you can do, e.g. adding ‘/usr/bin/touch /forcefsck’ to /etc/rc.local to enforce fsck-checks. There are more tweaks, check here: http://thebeezspeaks.blogspot......ry-pi.html

    My setup is an ordinary RPi model B with 32GB SD card and Raspbian installed. Source is a Sony mirrorless camera writing to a micro SD card, which will be transferred by a mini usb dongle to the RPi SD card. This configuration is working for me, but I will run further tests and will review if any changes occur.

    Best regards, LS

  8. LSNo Gravatar
    00:25 on June 2nd, 2014


    I added another small feature:

    • Automatic shutdown

    The lazy way is to enable ‘pi’ to shutdown, using ‘sudo chmod 4755 /sbin/shutdown’ Then, add some line like ‘shutdown -h 1 &’, e.g. before the last line ‘logger -t … ‘ This basically does the job very well, once the system is halted, my Micro-SD adapter’s blue-LED is stopped, hence I can turn the box safely off and remove the adapter. This makes a very nice finish if you are without a keyboard and monitor, like my holiday setup.


  9. rastoNo Gravatar
    23:23 on January 25th, 2015

    really big thanks to you and this very nice script. I am new in linux but I am finding it the best because guys like you did things like you and save my time and energy in this full task life 😉

    thanks a lot.. owe you beer 😉

  10. urukaiNo Gravatar
    10:13 on March 21st, 2015


    Great idea, great script, great open source spirit !!!

    Works on x86/x64 with Ubunutu and Ubuntu based distros. Unfortunately, udevadm monitor command can’t see the SD add event in the reader on a Raspberry pi model B (tested with 3 SD card readers which all works on x86/x64 with Ubunutu and Ubuntu based distros, Windows 7 and 8.1, and OSX on macbook pro and iMac)

    As the event are not triggered, script can’t be launched :(

    Does someone have an idea to help me ?

    My aim is to have a box for back up on my NAS photos and videos from SD card, but also from USB devices such as phones or USB sticks

    Thanks for your help !

  11. bimmiNo Gravatar
    02:15 on May 2nd, 2015

    Hi Lars,

    thank you for this script! I love it. We have a meet one day in a month with 20 people and everybode makes pictures. And at the end it is worth to play SD-Card-Jokey !!!

    I made a “copyPi” station. It can copy 3 sticks or cards at the same time and safe the hole files on the internal ssd. For mobile use I can power it with an lipo.

    You can watch a picture here:


    I have a simple question. How can I disable the serialnumber check? I will copy every card without “registering” in the system…

  12. LaMiNo Gravatar
    20:47 on May 2nd, 2015


    this box looks pretty neat! To remove the serial check it should be enough to simply remove these lines:

    if ! echo "$HANDLED_SERIALS" | grep $ID_SERIAL >/dev/null 2>&1; then logger -t sd-autocopy "Got unhandled serial, skipping (Serial: $ID_SERIAL)" exit 1 fi

  13. bimmiNo Gravatar
    11:37 on May 3rd, 2015

    Hi Lars,

    thank you for comment!

    Your tip was good. Now it runs perfektly!

  14. bimmiNo Gravatar
    11:26 on May 11th, 2015

    Hi Lars,

    I’ve made a small howto for my copy station: https://fpv-team.de/blog-aktuelles-news/entry/raspberry-pi/copypii-mit-dem-raspberry-automatisch-jede-eingelegte-sd-karte-kopieren

    and I added in the code to make a directory with a timestamp. Because you do not know whether the files are not overwritten when different SD cards are inserted. If had more knowledge in Python I would part the syslog where he indicates that the job is done, display it graphically on the tft.

No trackbacks yet.