I'm working on a deploy script for my dotfiles, they are stored on a Git bare repository, from which i git checkout it's files into my home directory, in order to seamlessy do it without thousands of Git checkout errors, i must remove the already existing ones (.bashrc for instance), i'm actually retrieving them with the following command

git --git-dir=$HOME/repos/dots --work-tree=$HOME ls-tree --full-tree --full-name --name-only -r HEAD | sed "s|^|/mnt/home/henriquehbr/|"

Note: the reason for /mnt is that it is the mountpoint for my chrooted installation, i'm actually running it from my custom Arch Linux installation image

But when it comes to actually removing these files, i had no success, the problem lies on the ones that contains spaces in their names (Font\ Awesome\ Icons.otf for instance)

I've tried quoting every single file to be removed by piping the command above on the following sequence of commands:

<get_dotfiles> | sed "s|^|\"/mnt/home/henriquehbr/|" | sed "s/$/\"/"

The method above didn't worked, i think rm was treating the quotes literally, as if they were part of the filename

Other trial was by making a good ol' for loop:

dotfiles=$(git --git-dir=$HOME/repos/dots --work-tree=$HOME ls-tree --full-tree --full-name --name-only -r HEAD | sed "s|^|/mnt/home/henriquehbr/|")

for dotfile in $dotfiles; do
    echo "$dotfile" # Works properly, displays dotfile path
    rm "$dotfile" # Doesn't works, writes: '$'\n'' after the path and fails
  • If you are not depending on substituting variables, it's always safer to single quote strings ('string or regexp') rather than double quote ("string or regexp"). Single quotes prevent shell interpretation. Always paste your script into https://shellcheck.net, a syntax checker, or install shellcheck locally. Make using shellcheck part of your development process. – waltinator Jun 12 at 17:28
  • @ibuprofen Both samples weren't intended for showing the rm usage, the first one is just for showing the command i'm using to fetch dotfiles, the second command surround each one of them in double quotes, the actual code is the third sample – henriquehbr Jun 12 at 17:29
  • @waltinator I'm using shellcheck for linting my script, but it didn't complained about anything wrong – henriquehbr Jun 12 at 17:30
  • @ibuprofen sorry, my mistake, forgot to add it from my code, already edited the question, even with the forward slash, it doesn't works, nothing is removed – henriquehbr Jun 12 at 17:36
  • OK. That out of the way. But, hmm: I do not get how your echo statements yield the result you want. dotfiles is not an array. You miss end quote. etc. And: yes. Even if shellcheck does not say anything about double-quoted patterns in sed, waltinator is completely right. – ibuprofen Jun 12 at 17:43

Use null bytes as separators:

git ls-tree -z … | xargs -0 rm

Note that in most shells, you can't put null bytes in a variable, so $(git ls-tree -z …) won't do anything useful.

Your attempts with quotes couldn't have worked if they resulted in passing the quotes to rm. rm, like most programs, just expects file names. To it, " is an ordinary character, like any other character that can appear in a file name. Injecting quotes also cannot help with substitution in the shell: an unquoted variable substitution or command substitution ($foo or $(mycommand …)) just splits at whitespace (and then treats each word as a wildcard pattern), quotes are not relevant at this stage. Injecting quotes and passing to xargs (without -0) can work if the filenames themselves don't contain characters that are too “exotic”, such as backslashes, tabs, double quotes, etc. but it's far more complicated than using git ls-tree -z and xargs -0.

Assuming that the file names don't contain any of the characters that git ls-tree itself quotes (which includes newlines), this will work (once again, without meddling with quotes):

set -f # disable wildcard expansion (globbing)
' # set only newline as the word separator
for dotfile in $(git ls-tree …); do … 

Not that any of this is a good idea. If you just want to forcibly erase the existing files when you run git checkout, Git will happily remove files if you run git checkout -f …. It would be safer to check the existing files in, in case they turned out to contain something important.

git checkout -b original-files
git --git-dir=… ls-tree -z | xargs -0 git add
git commit -m "Original files on $HOSTNAME"
git checkout origin/master

I actually ended up with a way simpler solution that doesn't require all this complexity, a simple "forced" git checkout does the trick of replacing all my old dotfiles on the deploy process:

git checkout -f

Props to Sir_not_sir on Reddit

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.