6

My directory's name contains a $ and ', for example

:~$ export DIR=\$my\'dir
:~$ sh -c "ls $DIR"

How can I run above command without $my being expanded? I know the single quote doesn't work since the $DIR contains one.

Actually, the question came from using find command as follows:

:~$ find ./\$my\'dir -type d -exec sh -c 'ls "{}"' \;

It says ./\'dir: No such file or directory, if I remove the double quotes then it says unexpected EOF while looking for matching '.

  • Why do you use sh -c? -exec ls {} should work. – Hauke Laging Feb 26 '14 at 6:04
  • @HaukeLaging: I know that, because i want to find what is in the directory using ls {} | grep. – will hunt Feb 26 '14 at 6:07
  • I don't get it. You are using find and in order to examine a directory content you want to use -exec, ls and grep? That seems crazy to me. – Hauke Laging Feb 26 '14 at 6:44
  • @HaukeLaging: That's not really what i done, I am using find to find out all sub-directories that don't contain any pdf files. the command is:find . -type d ! -exec sh -c 'ls "{}" | grep -qi \.pdf' \; -print. some directories make that failed, so the question came out, do you have any ideas to do that job? – will hunt Feb 26 '14 at 7:47
  • May I suggest mv \$my\ \'dir/ 'my dir'? – terdon Feb 26 '14 at 15:46
4

Wow, some complex solutions here! I think all you need to do is this though:

find ./\$my\'dir -type d -exec sh -c 'ls "$1"' sh {} \;

Instead of putting the arguments into the sh command string, just use them as arguments to sh. Note the second sh is the value of $0, everything after that is a positional argument.

As for your full problem of finding directories not containing pdfs, you are usually always better off using find for this kind of thing rather than ls. This should be close to what you are looking for:

find . -type d \
  -exec sh -c '[ "$(find "$1" -maxdepth 1 -type f -iname "*.pdf")" = "" ]' sh {} \; \
  -print
  • Thanks, that works. I think find ./\$my\'dir -type d -exec sh -c 'ls "$0"' {} \; also work. – will hunt Feb 27 '14 at 5:38
  • My thought for not using that was that some shells behave differently depending on the name they are in invoked with. Eg if you invoke bash as sh, it enters posix mode. I tested this and it is only for actual executable though, so the above may be safe. – Graeme Feb 27 '14 at 9:48
3

Ok... here is a convoluted way to do it. It'll fail if the filenames contain new line characters.

  find . -type d -print0                          \ # get the directory list,
                                                  \ # separated by nulls
| xargs -0 ls -1 -d -Q --quoting-style=shell      \ # pick it with xargs, and use
                                                  \ # ls to apply shell quotes to each
                                                  \ # entry
| xargs -d '\n' -I '{}'                           \
        sh -c "ls {} | grep -qi \.pdf$ || echo {}"  # pick each quoted entry
                                                    # with xargs again
                                                    # (using -d, so it keeps the quotes)
                                                    # and insert it in the string

This relies on ls -Q --quoting-style=shell quoting with single quotes... otherwise, use single quotes on the argument to sh -c.

Ugh.

1

What you want is not easily possible because it is not possible to quote arbitrary strings from "outside" only (i.e. by enclosing in "" or ''). This could be done by quoting each character with \ but I don't see how that shall be done with simultaneously calling the shell.

This is possible:

echo "$DIR" | sed -e 's/./\\&/g' -e 's/^.*$/echo &/' | bash -s

But that cannot be put in an -exec statement. And the echo cannot be called from a shell because that shell would run into the quoting problem with the argument. You can make the find output the shell input:

find ./\$my\'dir -type d -print0  | xargs -0 -n 1 ls

But: You could write the string to a file and have the shell read it from there:

find ./\$my\'dir -maxdepth 0 -type d -fprint tmpfile -exec bash -c 'cat tmpfile' \;

Sorry, no, not even that works: find opens the file, does the -exec action and afterwards writes to the file... So... this works but is kind of crazy, of course:

find ./\$my\'dir -maxdepth 0 -type d -fprint tmpfile -exec bash -c '(sleep 1; cat tmpfile)&' \;
  • sh -c 'echo "$DIR"' - Gives empty line result sh -c 'ls "$DIR"' - Gives ls: : No such file or directory – kumarprd Feb 26 '14 at 5:21
  • @PDK Of course, but that has nothing to do with this problem. sh -c 'ls fubar' most probably has the same effect. And you have forgotten to export $DIR: export DIR – Hauke Laging Feb 26 '14 at 5:30
  • Its already exported as I am getting : echo $DIR $my\'dir – kumarprd Feb 26 '14 at 5:31
  • @PDK My statement was not enough reason for you to just give export DIR a try but you prefer to argue with your interpretation of the echo $DIR output instead? – Hauke Laging Feb 26 '14 at 5:37
0

I'm not sure I understand your problem:

$ mkdir \$my\'dir
$ touch \$my\'dir/foo
$ ls $DIR
foo

As for finding subdirectories that don't include PDF files, just run this:

find . -type d -print0 | while IFS= read -r -d '' dir; do 
    [ $(find "$dir" -iname "*.pdf"  | wc -l) -gt 0 ] || echo $dir; 
done

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.