1

I'm trying to find all the directories in a given path, and create soft links inside those directories into directories with the same names at another location. Many of the directories have spaces in their names. I have cobbled the following code together, and it seems to work provided there are no spaces.

find /some/path/* -maxdepth 0 -exec sh -c "ln -s /some/other/path/"'$(basename {})'" {}" \;

How should I change this to handle spaces? I normally don't have them in my directory names, but these mirror directories on my Windows PC, where I do use spaces. Any help is greatly appreciated!

EDIT

In response to the points made by cuonglm and Gilles:

  • The order of the ln -s command arguments is not mistaken, but what I wanted to do is not quite clear from my explanation. For every directory in /some/path/, I want to create a symbolic link in that directory pointed at a directory with the same name in /some/other/path/. So /some/other/path/ is the source, and /some/path/ is the destination. The reason I want to do this is because /some/path/ contains a subset of the directories in /some/other/path/, and I want a link from the subset to the full set for every directory.

  • There won't be too many directories in the path, but I agree that not guarding against it is a pointless flaw.

  • The reason I didn't use -type d is that there will only be directories and not files in the given path, but I realise including it is better.

  • How come this works? You are taking file names from /some/path/* and try to create symbolic links in place of those very files - second argument of ln -s. You will surely get File exists error. – techraf Feb 28 '16 at 11:32
  • I don't know. As I'm sure is obvious, I am really a novice when it comes to linux. Can ln -s be taking the second argument as the path to create the link in? It does appear there, and with the same name as the directory it's in. – Martin Boström Feb 28 '16 at 11:55
  • @techraf This indeed seems to be the case. In fact, if I specify a non-existing path, like /some/path/test and run the ln -s command twice, then the first link will be /some/path/test, but the second will be /some/path/test/target. – Martin Boström Feb 28 '16 at 13:18
1

There're some issues with your command:

  • /some/path/* can cause argument list too long error if there're too many directories under /some/path

  • It does not filter directory or file

  • It's very in-efficient because it used inline-script sh -c but with -exec ... {} \;

  • Using ln with wrong argument position, ln -s source dest will create symbolic link from source to dest, not dest to source.


You can do it POSIXly:

find /some/path ! -path /some/path -prune -type d -exec sh -c '
  for f do
    ln -s "$f" /some/other/path/"${f##*/}"
  done
' sh {} +

Or if your find supports -maxdepth:

find /some/path -maxdepth 1 -type d -exec sh -c '...' sh {} +
  • It works now. Thank you! However, the order I need to use is definitely ln -s dest source. I had a look at man ln, and I think the only way to get the other order is if you write ln -s -t source dest. Also, the paths I use will not have very many directories, and there will only be directories and no files. However, that part of your solution is still much better practice. I will edit my question to reflect this. Thank you for your help! – Martin Boström Feb 28 '16 at 12:58
  • @MartinBoström No, I assure you, it's ln -s source destination, where source is the (usually existing) target of the link and destination is the path to the link to create. – Gilles Feb 28 '16 at 20:15
  • @Gilles I see my mistake now, and the source of the confusion. I meant the same thing, but mixed up source and destination because I was thinking of the directory the link points to as the destination. However, the order I put the terms in in the question is right (but with poorly chosen words) because what I want to do is this: if a directory exists in /some/path, make a link in that directory to a directory with the same name in /some/other/path. This is obviously neither clear, nor a normal thing to want to do. I will edit my question to clarify this. Apologies for the confusion! – Martin Boström Feb 28 '16 at 20:27
1

First, always use {} as a separate argument in find -exec …. Using stuff{}morestuff works with some (not all) versions of find, but cannot possibly cope with filenames containing “unusual” characters such as whitespace, ', ", etc. (exactly which characters cause trouble depends on exactly where you use {}). Pass {} as a separate argument. The first argument after sh -c CODE is $0. Don't forget the double quotes around variable expansion.

find … -exec sh -c 'ln -s /some/other/path/$(basename "$0") "$0"' {} \;

(I also simplified the quoting to use single quotes throughout.)

There is at least one other problem with your command. You're attempting to create a symbolic link at the location returned by find, where by construction there's already a symbolic link. It seems that you intended to create a symbolic link under /some/other/path, in which case you need to swap the arguments to ln (like with mv and cp, the existing file(s) come first, and the destination comes last).

find /some/path/* -maxdepth 0 -exec sh -c 'ln -s "$0" /some/other/path/$(basename "$0")' {} \; 

You can make this faster by using shell string manipulation constructs instead of basename, and by invoking sh in batches at a time.

find /some/path/* -maxdepth 0 -exec sh -c '
  for x do
    ln -s "$x" /some/other/path/${x##*/};
  done' {} +

If that's your actual find command, then find isn't useful here. The point of find is to recurse into subdirectories. A non-recursive find can sometimes be useful to take advantage of its filters (e.g. to act only on files of a certain type or modified within a certain time span). But a non-recursive find with no filtering is pointless.

for x in /some/path/*; do
  ln -s "$x" "/some/other/path/${x##*/}";
done' {} +

Or

cd /some/path
for x in *; do
  ln -s "$PWD/$x" "/some/other/path/$x";
done' {} +

Alternatively, with zsh:

autoload -U zmv
alias zln='zmv -L'
zln -s '/some/path/*' '/some/other/path/$f:t'
  • This post is very educational for someone as inexperienced with Linux as myself. Thank you! – Martin Boström Feb 28 '16 at 20:33

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.