2 # Copyright (C) 2014-2016 Ian Kelling
3 # This program is under GPL v. 3 or later, see <http://www.gnu.org/licenses/>
6 local target dest_file dest_dir
10 if [[ -L $dest_file ]]; then
11 if [[ $
(readlink
$dest_file) == "$target" ]]; then
12 # Leave the link in place, but make sure it's
14 # already exists. links all get 777 perms, so
15 # we dun have to mess with that.
16 if [[ $
(stat
-L -c%a
"$dest_dir") == 2* ]]; then
17 grp
=$
(stat
-L -c%g
"$dest_dir")
21 if [[ $EUID == 0 && $
(stat
-c%u
"$dest_file") != 0 ]]; then
22 chown
0:$grp "$dest_file"
23 elif [[ $
(stat
-c%g
"$dest_file") != "$grp" ]]; then
24 chgrp
$grp "$dest_file"
28 to_remove
+=("$dest_file")
29 elif [[ -e $dest_file ]]; then
30 to_remove
+=("$dest_file")
36 lnf [OPTIONS] -T TARGET LINK_NAME (1st form)
37 lnf [OPTIONS] TARGET (2nd form)
38 lnf [OPTIONS] TARGET... DIRECTORY (3rd form)
39 Create symlinks forcefully
41 If the link already exists, make it's ownership be the same as if it was
42 newly created (only chown if we are root). Removes existing files using
43 trash-put or rm -rf if it is not available, or if trash-put fails due to a
44 limitation such as a cross-filesystem link. Create directory of link if
45 needed. Slightly more restrictive arguments than ln.
47 In the 1st form, create a link to TARGET with the name LINK_NAME. In the 2nd
48 form, create a link to TARGET in the current directory. In the 3rd form, create
49 links to each TARGET in DIRECTORY.
51 -n|--dry-run Do verbose dry run.
52 -v|--verbose Print commands which modify the filesystem.
53 -h|--help Print help and exit.
61 temp
=$
(getopt
-l help,dry-run
,verbose hnTv
"$@") || usage
1
65 -n|
--dry-run) dry_run
=true
; verbose
=true
; shift ;;
66 -T) nodir
=-T; shift ;;
67 -v|
--verbose) verbose
=true
; shift ;;
68 -h|
--help) echo "$help"; return 0 ;;
70 *) echo "$0: Internal error! unexpected args: $*" ; exit 1 ;;
74 if (( $# == 0 )); then
80 if (( $# != 2 )); then
81 echo "lnf: error: expected 2 arguments with -T flag. Got $#"
86 local reset_extglob
=false
87 ! shopt extglob
>/dev
/null
&& reset_extglob
=true
91 local -a to_remove to_link
92 local x ret prefix dest_dir grp target
97 dest_dir
="$(dirname "$dest_file")"
98 _lnf_existing_link
"$1" "$dest_file" "$dest_dir" ||
return 0
99 if [[ ! -d $dest_dir ]]; then
101 if [[ -e $dest_dir ||
-L $dest_dir ]]; then
102 to_remove
+=("$dest_dir")
105 to_link
+=("$dest_file")
106 elif (( $# >= 2 )); then
108 if [[ -d $dest_dir ]]; then
109 prefix
="$dest_dir" # last arg
110 for target
in "${@:1:$(( $# - 1 ))}"; do # all but last arg
111 # Remove 1 or more trailing slashes, using.
112 dest_file
="${target%%+(/)}"
113 # remove any leading directory components, add prefix
114 dest_file
="$prefix/${target##*/}"
115 _lnf_existing_link
"$target" "$dest_file" "$dest_dir"
118 to_link
+=("${@:1:$(( $# - 1 ))}")
121 if (( ${#to_link[@]} == 0 )); then
124 to_link
+=("$dest_dir")
125 elif [[ $# -eq 1 ]]; then
127 _lnf_existing_link
"$1" "$dest_file" . ||
return 0
129 if (( ${#to_remove[@]} >= 1 )); then
130 if type -P trash-put
>/dev
/null
; then
132 echo "lnf: trash-put -- ${to_remove[*]}"
135 trash-put
-- "${to_remove[@]}" || ret
=$?
137 # trash-put will fail to trash a link that goes across filesystems (72),
138 # and for empty files (74)
139 # so revert to rm -rf in that case
140 if [[ $ret == 72 ]]; then
141 echo "lnf: using rm -rf to overcome cross filesystem trash-put limitation"
142 rm -rf -- "${to_remove[@]}"
143 elif [[ $ret == 74 ]]; then
144 echo "lnf: using rm -rf to overcome empty file & hardlink trash-put limitation"
145 rm -rf -- "${to_remove[@]}"
146 elif [[ $ret && $ret != 0 ]]; then
151 echo "lnf: rm -rf -- ${to_remove[*]}"
154 rm -rf -- "${to_remove[@]}"
159 $reset_extglob && shopt -u extglob
163 echo "lnf: mkdir -p $dest_dir"
166 if ! $dry_run && ! mkdir
-p "$dest_dir"; then
167 echo "lnf error: failed to make directory $dest_dir"
173 echo "lnf: ln -s $nodir -- ${to_link[*]}"
176 ln -s $nodir -- "${to_link[@]}"