Reputation: 8241
I have the following directory structure
/symdir
sym1 -> ../dir1
sym2 -> ../dir2
hello.txt
And then
/dir1
some
files
here
/dir2
more
files
I would like to replace the symlinks in symdir (sym1, sym2) with the originals. I.e.
some_awesome_bash_func symdir symdir_output
Would create
/symdir_output
/dir1
some
files
here
/dir2
more
files
hello.txt
How would I accomplish this?
Upvotes: 46
Views: 29927
Reputation: 1467
This works for directories as well. Replace dir
with your desired local directory:
mv ./dir ./dir.sym; cp -r $(readlink ./dir.sym) ./dir; rm -r ./dir.sym
This moves the symbolic directory dir
to dir.sym
. It then reads the source path of the just moved dir.sym
, which still points to the same source as dir
, and uses it to recursively copy all directory content to the new local dir
. Finally, it removes the working copy dir.sym
.
Upvotes: 0
Reputation: 2183
a related answer, this solution keeps the file at it's original place and creates a copy in place of the symlink
#!/bin/bash
for f in $(find . -maxdepth 1 -type l)
do
cp --remove-destination $(readlink -e $f) $f
done
Upvotes: 18
Reputation: 33
Extending Julien's suggestion. This command recursively replaces all symbolic links in the current directory with it's target.
find . -type l -exec sed -i '' {} \;
find .
: Search in the current directory, recursively-type l
: Finds symlinks only-exec sed -i '' {} \;
: Run sed -i '' PATH
for each symlink. Please see Julien's answer for an excellent explanation of why we use sed
here.Upvotes: 3
Reputation: 11526
My very personal trick for files (not directories):
sed -i '' **/*
Note that I'm using **
which uses the bash globstar option, you may have to enable it beforehand:
shopt -s globstar
I trick sed
to do the job, by using an implementation detail of the sed
inplace mode.
sed
is a tool to edit streams of text. The -i
option of sed
means inplace
, the empty string ''
is the instruction set: so there's no instruction, sed
will do nothing. **/*
is a bash globstar
pattern meaning "all files and all folders, at all depth, from here".
The algorithm sed
uses to edit a file inplace is:
As I'm asking no transformations (the empty string), the algorithm can be simplified as:
The temporary file is a real file, sed
completly ignores that the input file was a symlink, it just reads it. So at the last step, when sed
moves the temporary file over the real file, it "overwrite" the symlink with a real file, that's what we wanted.
This also explains why it won't work to transform a "symlink to a directory" to a real directory: sed
works on file contents.
Upvotes: 74
Reputation: 52858
Probably not the best way, but it works:
#!/usr/bin/bash
for link in $(find /symdir -type l)
do
loc="$(dirname "$link")"
dir="$(readlink "$link")"
mv "$dir" "$loc"
rm "$link"
done
Upvotes: 16
Reputation: 1131
here is a slightly more general solution, based on @Daniel Haley
it also preserves the symlinks for reference and asks the user to select a directory to edit.
ls
read -p 'Which directory do you want to update?: ' linkdir
pushd $linkdir
for linkname in $(find ./ -type l)
do
orig=$(readlink $linkname)
mv $linkname ${linkname}.linkbak
cp $orig $linkname
done
popd
Upvotes: 0
Reputation: 123632
I did it this way:
ls -la | awk '/-\>/{system("rm "$10); system("cp "$12" .")}'
How it works:
ls -la
outputs something like this:
lrwxr-xr-x 1 username groupname 44 10 Oct 12:17 Queue.swift -> ../../../Platform/DataStructures/Queue.swift
Column 10 is Queue.swift
which is the name of the local file.
Column 12 is ../../../Platform/DataStructures/Queue.swift
which is the name of the link target
The first part of the awk
command is '/-\>/'
which means "match lines which contain ->
using a regex
The next part of the awk command is two calls to system
First system("rm "$10)
which expands to system("rm Queue.swift")
.
This will cause the original file (the symlink) to get deleted
Second is system("cp "$12" .")
which expands to system("cp ../../../Platform/DataStructures/Queue.swift .")
Putting it all together, what happens is for each file (which is a symlink), first we delete the symlink, then we copy the target file in it's place.
Although it's not part of the original question, I was using this in conjunction with git. If you happen to be doing that too, you can run git status .
afterwards and you should see a bunch of type changes (and nothing else), like this:
typechange: Queue.swift
Upvotes: 0
Reputation: 602
tl;dr: much more general, much more reliable answer:
find -type l -exec sh -c 'PREV=$(realpath -- "$1") && rm -- "$1" && cp -ar -- "$PREV" "$1"' resolver {} \;
The "rsync-to-other-destination" approach is strictly superior, and usually leads to better design.
The answer by @PinkFloyd doesn't quite work with unusual filenames, "buried" symlinks, or symlinked directories. I came here because I wanted to resolve directory-symlinks, so I expect others to find this question for this reason, too. Also, my version of cp
(GNU coreutils 8.25) doesn't properly handle --remove-destination
for @PinkFloyd's answer to work with directories. So this answer uses manual rm
.
Also note:
-rf
. That's because a symlink is not a directory, and should not need -r
. And unless you have symlinks with restricted permissions (why would you ever want that?!), you don't need -f
either.realpath
is perfectly fine in this context, because it allows us to find out the actual location in the current system, in the current context, and nothing else matters. This path won't be written to disk, so this is not an error.resolver
string is for sh
. See man sh
.--version
or similar.find
("parent directory is listed at somewhere before its content"), this would first replace the parent directory, and then any symlinks within the symlinked directory. So it would work perfectly fine with "stacked" symlinks.Upvotes: 7
Reputation: 12815
You can do this easily with rsync:
rsync symdir/ symdir_output/ -a --copy-links -v
(-a means preserve basically every detail about the files, --copy-links overrides -a to turn symlinks into the real files/directories, and -v is for verbose)
Edit:
Sorry, my solution doesn't do exactly what you asked for. It will preserve the symlink's names instead of using the destination names. symdir_output would have sym1 and sym2 instead of dir1 and dir2 (though sym1 and sym2 would be a real copy of dir1 and dir2). Hope it still works for you.
Upvotes: 34