zeeple
zeeple

Reputation: 5617

Properly quote bash alias definition

I have the following command that I am trying to put into a bash alias. The command by itself works fine, but when I try to alias it, I am getting the following errors:

The Command

find . -maxdepth 1 -mindepth 1 -type d -exec sh -c 'echo "$(find "{}" -type f | wc -l)" {}' \; | sort -nr

The Alias

alias csfiles='find . -maxdepth 1 -mindepth 1 -type d -exec sh -c 'echo "$(find "{}" -type f | wc -l)" {}' \; | sort -nr'

The Error:

-sh: alias 0: not found
-sh: alias {} \; | sort nr: not found

I think this means I am not using quotes right but I am having trouble determining the correct combo. Help?

Upvotes: 1

Views: 74

Answers (2)

chepner
chepner

Reputation: 531235

Your outer find doesn't do anything you couldn't do with a simple glob. This eliminates a layer of quotes (along with the sh process for each directory found).

# Ignoring the issue of assuming no file name contains a newline
for d in ./*/; do
   echo "$(find "$d" -type f | wc -l) $d"
done

Just define a shell function to eliminate the second layer imposed on the argument to alias.

csfiles () {
  for d in ./*/; do
    echo "$(find "$d" -type f | wc -l) $d"
  done
}

The remaining call(s) to find can also be replaced with a for loop, eliminating the problematic assumption of one line per file name:

csfiles () {
  for d in ./*/; do
    echo "$(for f in "$d"/*; do [ -f "$f" ] && echo; done | wc -l) $d"
  done
}

You could keep find if it supports the -printf primary, because you don't care about the actual names of the files, just that you get exactly one line of output per file.

csfiles () {
  for d in ./*/; do
    echo "$(find "$d" -type f -printf . | wc -l) $d"
  done
}

Upvotes: 4

hek2mgl
hek2mgl

Reputation: 158020

You can use double quotes around the definition, like this:

alias foo="find . -maxdepth 1 -mindepth 1 -type d -exec sh -c 'echo \"\$(find \"{}\" -type f | wc -l)\" {}' \; | sort -nr"

Every literal " inside the definition gets escaped: \".

Note: You also need to escape the inner command substitution to prevent it from getting expanded upon alias definition time. Like this ... \$(...)


As a follow up on chepners comment, you should pass the filename to the inner find command as an argument. Otherwise you will run into problems if one of your folders has a name with a " in it:

alias foo="find . -maxdepth 1 -mindepth 1 -type d -exec bash -c 'echo \"\$(find \"\${1}\" -type f | wc -l) \"\${1}\" \"' -- \"{}\" \; | sort -nr"

Upvotes: 1

Related Questions