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 thesource
, and/some/path/
is thedestination
. 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.
2 Answers 2
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 fromsource
todest
, notdest
tosource
.
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 atman ln
, and I think the only way to get the other order is if you writeln -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– Martin Boström2016年02月28日 12:58:23 +00:00Commented Feb 28, 2016 at 12:58 -
@MartinBoström No, I assure you, it's
ln -s source destination
, wheresource
is the (usually existing) target of the link anddestination
is the path to the link to create.Gilles 'SO- stop being evil'– Gilles 'SO- stop being evil'2016年02月28日 20:15:25 +00:00Commented Feb 28, 2016 at 20:15 -
@Gilles I see my mistake now, and the source of the confusion. I meant the same thing, but mixed up
source
anddestination
because I was thinking of the directory the link points to as thedestination
. 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– Martin Boström2016年02月28日 20:27:22 +00:00Commented Feb 28, 2016 at 20:27
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– Martin Boström2016年02月28日 20:33:23 +00:00Commented Feb 28, 2016 at 20:33
/some/path/*
and try to create symbolic links in place of those very files - second argument ofln -s
. You will surely getFile exists
error./some/path/test
and run theln -s
command twice, then the first link will be/some/path/test
, but the second will be/some/path/test/target
.