Xophmeister
Xophmeister

Reputation: 9211

Sync shell script fails on ls when file not found

I have written a shell script to sync my dotfiles repository to my home directory. This was working fine in Cygwin (zsh), but I've just migrated to Linux (zsh on Xubuntu 12.10) and it's failing.

The script makes a list of the relevant dotfiles in the repo then, before creating symlinks, it archives any clashes. The archiving routine is as follows:

for i in $dotFiles; do
  toArchive=$toArchive $(ls -d $i)
done

This fails when any item in $dotfiles doesn't exist; ls returns No such file or directory and the script terminates.

Would redirecting stderr to /dev/null resolve this? i.e.:

for i in $dotFiles; do
  toArchive=$toArchive $(ls -d $i 2>/dev/null)
done

...or is there a better solution?

(My script is here, for sake of completeness.)

Upvotes: 1

Views: 1207

Answers (4)

Xophmeister
Xophmeister

Reputation: 9211

I have taken the suggestions posted here individually and wrapped them into a whole solution. I don't know what it was that was breaking the logic in my script, but if I were to guess, I'd say it was probably parsing the results of ls and/or using a spaced string, rather than a zsh array... Anyway, the working script is as follows:

#!/bin/zsh

# Break on first error and unset variables
set -e
set -u

# Gather dotfiles
repoDir=$(pwd)

cd $repoDir/public
publicDotfiles=(.*)
cd $repoDir/private
privateDotfiles=(.*)

# Determine clashes, to backup
typeset -a toBackup
for file in $publicDotfiles $privateDotfiles; do
  dotFile=$HOME/$file
  if [[ -e $dotFile ]] toBackup=($toBackup $dotFile)
done

if [[ "$toBackup" != "" ]]; then
  backupFile=$HOME/dotfiles.$(date +%Y%m%d).tar.gz
  tar czPf $backupFile $toBackup --remove-files
fi

# Create symlinks
for i in $publicDotfiles; do
  ln -s $repoDir/public/$i $HOME/$i
done

for i in $privateDotfiles; do
  ln -s $repoDir/private/$i $HOME/$i
done

While I was at it, I made it slightly more robust:

  • There are two classes of dotfiles, public and private. The private directory is for things like .gnupg, etc. Its contents are ignored by Git, so they never get pushed out.
  • Providing the script is run from its own directory, the project folder can be anywhere and then the script works out absolute paths.

Upvotes: 0

user507077
user507077

Reputation:

Zsh, unlike other shells, does not execute a command when the file name pattern does not match anything. Meaning an echo hello * in an empty directory will not actually print hello * on the screen. Instead, zsh will print said error message and echo will not get executed.

You can switch that on and off with one of those funky options called NOMATCH which is active by default. Read zsh's info pages on how it works, please. And chapter 2 in the zsh FAQ also explains about this.

However, if I interpret your intention correctly then you have a list of file names that may or may not actually exist, and you only want to include a file name in the backup if it exists. If that is the case then here is a better version using arrays and file existence tests:

typeset -a files_to_backup
for file in $dotFiles ; do
  if [[ -f $file ]] files_to_backup=($files_to_backup $file)
done

This also uses zsh's short conditional syntax for the if, in case you're wondering.

Upvotes: 1

Francisco
Francisco

Reputation: 4110

The worst thing with a shell-script is making it work. The second worst is maintaining (having t figure out in six months time what each line/option did). With that in mind, I suggest you to just add an existence check:

for i in $dotFiles; do
  if [[ -e $i ]]; then
      toArchive=$toArchive $(ls -d $i)
  fi
done

Upvotes: 2

Jens
Jens

Reputation: 72667

I don't understand exactly why your script terminated on the first error (set -e in effect? Running as part of a Makefile rule?), but ignoring the non-zero status of any command can be achieved by running

 cmd || :

(read: command or true). Does

  $(ls -d $i || :)

do the trick?

PS: I looked at your script. In fact, set -e is in effect. You might want to remove it completely or selectively turn it on only for specific regions of your script (set +e undoes the effect of set -e).

Upvotes: 1

Related Questions