I'd like to create symbolic links for multiple books searched from running find command.

Firstly, I collect all the JS books

find ~ -type f -iregex  '.*javascript.*\.pdf' > js_books.md 2>/dev/null

It returns 35 books

../Coding/Books/HTML_Collections/Freeman E.T., Robson E. - Head First HTML5. Programming Building Web Apps with JavaScript - 2011.pdf
../Coding/Books/HTML_Collections/Learning Web Design - A Beginner's Guide to HTML, CSS, JavaScript, and Web Graphics - Jennifer Niederst Robbins - 4th Edition - August 2012.pdf

Additionally to copy them to directory js_books

mkdir js_books
find ~ -type f -iregex  '.*javascript.*\.pdf' -print0 -exec cp '{}' js_books

It works, however, multiple copies consume lots of disk space.
So I delete the books and try to make symbolic link within.

find ~ -type f -iregex  '.*javascript.*\.pdf' -print0 -exec ln -s '{}' js_books/'{}' \;

It returns nothing in dir js_books.

How to work out such a problem?


Your find expression is simple enough to replicate with bash shell globbing:

shopt -s globstar nocaseglob
for book in $HOME/Coding/**/*javascript*.pdf
  ln -s "$book" "js_books/$(basename "$book")" 

First we tell bash to enable case-insensitive filename globbing and to be able to recurse subdirectories via **, then we loop over all the filenames that match the "(anything) javascript (anything) pdf" pattern. For each of those filenames, we create a symlink into js_books.

You elided the beginning of each filename, so I'll just note that you'll want to provide the full path in the for loop so that the ln command creates valid symlinks (or else use the proper amount of ../ prefixes in the symlink target). I replaced your find ~ with $HOME/Coding, just in case you were using a different starting directory.


The target js_books/'{}' should just be js_books . If it is a directory, ln just uses the same name as the original file.

The problem here most probably is that {} adds upper directory names, which jeopardizes the function.

  • Utilizing dir/. as opposed to simply dir in an ln, cp, mv, etc. to indicate that the command expects dir to be a directory it can put the files in is a bit more explicit. In this example that may be overkill due to the name of the directory, but I've found it's a good practice to get in to for readability. Mar 29 '18 at 17:10

One way would be to do the following:

find ~ -type f -iregex  '.*javascript.*\.pdf' -printf %f"*"%h | awk -F* '{ system("ln -s "$2"/"$1" "$2"/js_books/"$1) }'

Run the find command and print the file name followed by an asterix and then the directory path. Run this through awk separating the filename and directory and building the ln command to execute with awk's system function.

Be wary that this is open to command inject and also use print to print the command to screen before running the final command with system.

  • Nice use of system in awk as it is not seen much. But very iffy indeed when applied in blanket fashion on a priori unknown file names. Good thing you mentionned it in yr last sentence.
    – Cbhihe
    Mar 30 '18 at 8:18

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.