Reputation: 850
I am creating a script that basically look for all the things that ends with .symlink
and try to create a symlink to the $HOME
directory removing the symlink
part and adding dot
in front of the name.
Below is the line showing how the destination is created.
dst="$HOME/.$(basename "${src%.*}")"
There are the two functions created for that
create_symlink () {
local src=$1 dst=$2
# Create the symlink
ln -s -i "$src" "$dst"
message "Linked $1 -------> $2" # This just use echo
}
run_symlinks() {
message "Creating Symlinks"
local root=$1
# Find the files/folder and try to do the symlink
for src in $(find -H $root -name '*.symlink')
do
dst="$HOME/.$(basename "${src%.*}")"
create_symlink "$src" "$dst"
done
}
The problem
I have a folder called atom.symlink
that folder basically have some configuration for my environment with the files of Atom text editor inside. When I run run_symlinks
function that folder is being synced but in the wrong place.
The output of message "Linked $1 -------> $2"
is:
Linked $HOME/.dotfiles/src/symlinks/atom.symlink -------> $HOME/.atom
But when I look at the folder the symlink is actually to $HOME/.atom/atom.symlink
instead of just to the .atom
folder.
Note: the $HOME/.atom
folder is not empty and I need to figure out how to make this script without worry about having an empty folder.
I tried to find the answer on Google but I could't even know how to ask this specific question about it.
Upvotes: 1
Views: 3054
Reputation: 440471
To complement anishsane's helpful answer, which explains the problem with your approach well:
Your desire to retain possibly preexisting (non-symlink) target folders in $HOME
and add to their content requires a fundamentally different approach:
The only way to solve this is to avoid symlinking the *.symlink
directories themselves; instead, the files in these directories must be individually symlinked to the target folder, which is either a preexisting folder or one that must be created as a regular folder on demand.
That is the only way to guarantee that the existing content of the target folder is not (invariably) lost:
while IFS= read -r f; do
# Strip $root from the file path, then remove suffix '.symlink' and add prefix '.'
# to the path commponent(s) to get the link path.
link="$HOME/$(sed 's#\([^/]\{1,\}\)\.symlink\(/\{0,1\}\)#.\1\2#g' <<<"${f#$root/}")"
# Make sure that the parent path exists, creating it on demand (as *regular* folder(s)).
mkdir -p -- "$(dirname -- "$link")"
# Now we can create the symlink.
echo "symlinking [$link] <- [$f]"
# Note the need to redirect from `</dev/tty` so as not
# to suppress the interactive prompt (`-i`).
ln -s -i "$f" "$link" </dev/tty
done < <(find -H "$root" -type f \( -path '*.symlink' -or -path '*.symlink/*' \))
The approach is a follows:
The find
command finds only files, namely those themselves named *.symlink
, and those inside directories named *.symlink
(whatever suffix the files themselves have).
For each file, the target symlink path is determined by removing the $root
path prefix, and then removing suffix .symlink
and adding prefix .
to matching path components.
The existence of each target symlink path's parent path is ensured with mkdir -p
: any existing path components are retained as-is, and any non-existent ones are created as regular folders.
Once the existence of the target folder for the symlink is ensured / established, the ln
command can be invoked.
-i
- to present an interactive prompt asking for replacement in case the link's path already exists - requires stdin to be a terminal in order to kick in; thus, given that stdin is redirected to the process substitution providing the output from find
, </dev/tty
is needed to show the prompt.Upvotes: 1
Reputation: 20980
From man ln
:
ln [OPTION]... [-T] TARGET LINK_NAME (1st form)
ln [OPTION]... TARGET (2nd form)
ln [OPTION]... TARGET... DIRECTORY (3rd form)
ln [OPTION]... -t DIRECTORY TARGET... (4th form)
In the 1st form, create a link to TARGET with the name LINK_NAME. In the
2nd form, create a link to TARGET in the current directory. In the 3rd
and 4th forms, create links to each TARGET in DIRECTORY. Create hard
links by default, symbolic links with --symbolic. By default, each destination
(name of new link) should not already exist. When creating hard
links, each TARGET must exist. Symbolic links can hold arbitrary text; if
later resolved, a relative link is interpreted in relation to its parent
directory.
After first iteration of your script, you have
$HOME/.atom/ -> $HOME/.dotfiles/src/symlinks/atom.symlink # This is the first form in above man page snippet.
In the second iteration, you fall in the 3rd form, because the target already existed & after symlink dereferencing, it's a directory.
So, the command run is same:
ln -s -i $HOME/.dotfiles/src/symlinks/atom.symlink $HOME/atom
Only difference is that in the second iteration, the target is an existing directory (after dereferencing).
So you should first delete the target (rm -f "$dst"
) & then create a symlink. Luckily, ln
can do it by itself:
Change your code to:
ln -sfn "$src" "$dst" # Note that with -f & -n in place, usage of -i is moot.
Upvotes: 3