add verbose mode to log removals
[lnf] / lnf
1 #!/bin/bash
2 # Copyright (C) 2014-2016 Ian Kelling
3 # This program is under GPL v. 3 or later, see <http://www.gnu.org/licenses/>
4
5 lnf() {
6 local help="Usage:
7 lnf -T TARGET LINK_NAME (1st form)
8 lnf TARGET (2nd form)
9 lnf TARGET... DIRECTORY (3rd form)
10 Create symlinks forcefully
11
12 Removes existing files using trash-put or rm -rf if it is not available,
13 or trash-put fails due to a limitation such as a cross-filesystem link.
14 Create directory if needed. Slightly more restrictive arguments than ln.
15
16 In the 1st form, create a link to TARGET with the name LINK_NAME. In the 2nd
17 form, create a link to TARGET in the current directory. In the 3rd form, create
18 links to each TARGET in DIRECTORY.
19
20 Do export LNF_VERBOSE=true for verbose output
21 "
22
23 if [[ $1 == --help || $1 == -h || $# -eq 0 ]]; then
24 echo "$help"
25 return 0
26 fi
27
28 local nodir
29 if [[ $1 == -T ]]; then
30 nodir=-T
31 shift
32 if (( $# != 2 )); then
33 echo "lnf error: expected 2 arguments with -T flag. Got $#"
34 return 1
35 fi
36 fi
37
38 local reset_extglob=false
39 ! shopt extglob >/dev/null && reset_extglob=true
40 shopt -s extglob
41
42
43 local x ret prefix dir to_remove
44 local mkdir=false
45
46 to_remove=()
47 if [[ $nodir ]]; then
48 dir="$(dirname "$2")"
49 if [[ -e $2 || -L $2 ]]; then
50 to_remove+=("$2")
51 elif [[ ! -d $dir ]]; then
52 mkdir=true
53 if [[ -e $dir || -L $dir ]]; then
54 to_remove+=("$dir")
55 fi
56 fi
57 elif (( $# >= 2 )); then
58 if [[ -d ${!#} ]]; then
59 prefix="${!#}/" # last arg
60 for x in "${@:1:$(( $# - 1 ))}"; do # all but last arg
61 # Remove 1 or more trailing slashes, using.
62 x="${x%%+(/)}"
63 # remove any leading directory components, add prefix
64 x="$prefix/${x##*/}"
65 [[ -e "$x" || -L "$x" ]] && to_remove+=("$x")
66 done
67 else
68 if ! mkdir -p "${!#}"; then
69 echo "lnf error: failed to make directory ${!#}"
70 return 1
71 fi
72 fi
73 elif [[ $# -eq 1 ]]; then
74 [[ -e "${1##*/}" || -L "${1##*/}" ]] && to_remove+=("${1##*/}")
75 fi
76 if (( ${#to_remove[@]} >= 1 )); then
77 if type -P trash-put >/dev/null; then
78 if [[ $LNF_VERBOSE == true ]]; then
79 echo "lnf: trash-put -- ${to_remove[*]}"
80 fi
81 trash-put -- "${to_remove[@]}" || ret=$?
82 # trash-put will fail to trash a link that goes across filesystems (72),
83 # and for empty files (74)
84 # so revert to rm -rf in that case
85 if [[ $ret == 72 ]]; then
86 echo "$0: using rm -rf to overcome cross filesystem trash-put limitation"
87 rm -rf -- "${to_remove[@]}"
88 elif [[ $ret == 74 ]]; then
89 echo "$0: using rm -rf to overcome empty file & hardlink trash-put limitation"
90 rm -rf -- "${to_remove[@]}"
91 elif [[ $ret && $ret != 0 ]]; then
92 return $x
93 fi
94 else
95 if [[ $LNF_VERBOSE == true ]]; then
96 echo "lnf: rm -rf -- ${to_remove[*]}"
97 fi
98 rm -rf -- "${to_remove[@]}"
99 fi
100 fi
101
102 $reset_extglob && shopt -u extglob
103
104 if $mkdir; then
105 if ! mkdir -p "$(dirname "$2")"; then
106 echo "lnf error: failed to make directory $(dirname "$2")"
107 return 1
108 fi
109 fi
110
111 ln -s $nodir -- "$@"
112 }
113 lnf "$@"