1

Running on OS X 10.11.1, I execute the following series of commands (one by one), in the console:

FILE="a b c.tiff"  # file in the current folder
VAR=$(mdls -name kMDItemContentCreationDate $FILE) # storing the creation time string  
TS=$(echo ${VAR[2]}; echo ${VAR[3]}  # saving the date and time only
echo $TS  

and the expansion works beautifully. Output shows:

2016-01-16 15:34:29

However, when I save these in a script and run it, it seems that something during the evaluation is different.

Debugging (with bash -x) yields:

FILE='a b c.tiff'
mdls -name kMDItemContentCreationDate a b c.tiff
VAR='a: could not find a.'
echo
echo
TS=
echo

, so I see the expansion behaving differently.

My concern is why does this difference occur and how should I correct my script.Thank you.

1 Answer 1

2

I'm pretty sure you haven't run those exact commands in a console, or you would've gotten the same result. There are two serious problems here, and a few bad scripting practices. Serious problems first:

  • When you refer to a variable (e.g. $FILE) without double-quotes around it, the shell will split it into "words" and then expand any wildcards before passing it to the command. In this case, that means that a b c.tiff will get split into "a", "b", and "c.tiff". That's why you're getting the "a: could not find a." error.

    Solution: you should put variable references in double-quotes unless you specifically want word-splitting and wildcard expansion. (There are some cases where leaving off the double-quotes is safe, but keeping track of them is more trouble than it's worth. Just get in the habit of using double-quotes.)

  • When you use an assignment like VAR=$(somecommand), it assigns the variable as a plain string, not an array. To store it as an array, use parentheses on the right side, as in VAR=( $(somecommand) ). Note that since $(somecommand) is not in double-quotes, it'll be word-split and wildcard-expanded, but in this case we want the want the word splitting (so each "word" gets stored in a separate array element), and the output format is predictable enough that wildcard expansion won't do anything weird to screw us up. So this is one of the rare cases where leaving off the double-quotes is ok.

With these two fixed, the second line becomes:

VAR=( $(mdls -name kMDItemContentCreationDate "$FILE") )

Now, for some things that're bad scripting habits that aren't actually causing trouble here:

  • In the assignment TS=$(echo ${VAR[2]}; echo ${VAR[3]}) (note: I added the missing close parenthesis), the command substitution and echo commands aren't doing anything useful. What this does is take the array element values, word-split and wildcard-expand them (which does nothing here), pass them as parameters to echo commands, take the output of those commands and collect it into a variable. That's a lot of work to stick two strings together. Just use `TS="${VAR[2]} ${VAR[3]}".

    BTW, it's also doing something a bit weird: it's sticking the strings together with a newline between them. When you print it with echo $TS, it (again) gets word-split, so each line gets treated as a separate argument to echo, which sticks spaces between the arguments. Net result: the echo command is effectively converting the newline to a space. It'd be much cleaner to make it a space in the first place, and then double-quote it when you use it, so your script doesn't depend on two bits of weird behavior that happen to cancel each other out.

  • Finally, using all-caps variables isn't very safe. There are a number of all-caps variables that have special meaning to the shell (and some commands), and if you accidentally use one of them you can get weird results. The classic example is assigning something to PATH, at which point all commands suddenly become unrecognized. It's hard to keep track of all the magic variables, so just use lowercase variables for your stuff and you'll be safe.

With all this cleaned up, here's what I get for the script:

file="a b c.tiff"  # file in the current folder
var=( $(mdls -name kMDItemContentCreationDate "$file") ) # storing the creation time string as an array
ts="${var[2]} ${var[3]}"  # saving the date and time only
echo "$ts"

Final note: when in doubt, run your script through shellcheck.net -- it'll point out many of the standard beginner mistakes and save you a lot of time!

3
  • Thank you. I wonder whether pasting from Notes.app had any kind of side-effect in the behavior I was seeing. For example, even using double-quotes around $FILE didn't work in the script to expand its contents, but rather quoted ad-literam (tried this before posting). I will need to double-check whether they were "smart quotes" or whatnot. I will once again thank you for the clear explanations, as well as the advices on good practices; priceless. Jan 19, 2016 at 13:53
  • @elderelder Yeah, most editors will do "helpful" things (like quote substitution) that mess up scripts. TextWrangler is a much better choice for scripting, because it pretty much does what you tell it to. Jan 19, 2016 at 18:53
  • I appreciate the suggestion. Jan 19, 2016 at 19:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .