1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
#! /bin/bash
#
# original from:
# @(#) ncp.ksh,nmv.ksh 1.1 94/07/23
# 92/01/18 john h. dubois iii (john@armory.com)
# 92/01/31 added check for no args left after shifts
# 92/02/17 added help
# 92/02/25 remove path component from filename before tacking it onto dest.
# 92/03/15 exec mv or cp
# 93/07/13 Added -i
# 93/09/29 Made abort if file exists optional.
# 93/11/19 Exit before invoking mv if no files to move
# 94/01/03 Added o option
# 94/04/13 Added x option.
# Fixed appending of source filename, broken by earlier change.
# 94/07/23 Append only the filename part of the source path.
#
# conversion to bash v2 syntax done by Chet Ramey
false()
{
return 1
}
true()
{
return 0
}
phelp()
{
echo "$name: do a $cmd with extra checking and options.
$Usage
$name is used as a front end for $cmd to get the [icfo] options, and so
that a trailing / will force the last component of the path to be
interpreted as a directory, so that $name foo bar/ will fail if bar is
not an existing directory, instead of changing the name of foo to bar.
Effectively, $name foo bar/ is short for $name foo bar/foo
Options:
-h prints this help.
-c checks first for the existence of each file, and fails if it exists.
-i is like -c except that if the file exists and stdin and stdout are a
tty, a query is printed and a reply is read; a file is overwritten only
if the reply begins with 'y'.
-f unsets -c and -i (in case $cmd is aliased to $name).
-o (overwrite only) checks that the named file(s) exist and fails for any
that do not. It is the complement of the -c option.
Whichever of [cifo] comes later on the command line determines the behaviour.
Any of these options must come before any standard $cmd options."
}
# interactive: Attempt to overwrite file should result in interactive
# query rather than automatic failure.
# noover: Do not overwrite files (if interactive is true, query, else fail)
# overwrite: Only overwriting is allowed, not creation of new files.
# debug: Print debugging info.
typeset interactive=false noover=false overwrite=false debug=false
name=${0##*/}
case "$name" in
ncp|nmv) cmd=/bin/${name#?} ;;
*) echo "$name: Must be invoked as ncp or nmv." 1>&2 ; exit 2;;
esac
Usage="Usage: $name [-cfhio] $cmd-cmd-line"
while getopts :cfhiox opt; do
case $opt in
h) phelp; exit 0;;
x) debug=true ;;
c) noover=true ;;
i) noover=true ; interactive=true ;;
f) noover=false ; interactive=false ;;
o) overwrite=true ; noover=false ; interactive=false;;
+?) echo "$name: options should not be preceded by a '+'." 1>&2; exit 2;;
?) echo "$name: $OPTARG: bad option. Use -h for help." 1>&2 ; exit 2;;
esac
done
# remove args that were options
shift $((OPTIND - 1))
if [ $# -lt 2 ]; then
echo -e "$Usage\nUse -h for help."
exit
fi
Check()
{
if [ ! -f "$1" ] && $overwrite; then
echo "$name: $1: File does not exist." 1>&2
return 1
elif [ -f "$1" ] && $noover; then
if [ $interactive = false ] || [ ! -t 0 ] || [ ! -t 1 ]; then
echo "$name: $1: File exists." 1>&2
return 1
else
while :; do
echo -n \
"$name: $1: File exists. Overwrite? (y)es/(n)o/(a)bort/(Y)es for all: " 1>&2
read reply
case "$reply" in
y*)
echo "$name: Overwriting $1."
return 0
;;
Y*)
echo "$name: Overwriting $1."
interactive=false
noover=false
return 0
;;
[nN]*)
echo "$name: Skipping $2."
return 1
;;
[aA]*)
echo "$name: Aborting."
exit 1
;;
*)
echo "$name: Invalid response." 1>&2
;;
esac
done
fi
else
return 0
fi
}
# i is the index of the filename being examined
# lastarg is the index of the last filename before the dest directory name
typeset -i i=0 lastarg=$(($#-1))
# Sets argv[0..$#-1]
argv=("$@")
$debug && echo argv = "${argv[@]}" 1>&2
dest=${argv[lastarg]}
if $debug; then
echo \
"interactive=$interactive noover=$noover overwrite=$overwrite debug=$debug
lastarg=$lastarg dest=$dest name=$name cmd=$cmd
files=$*" 1>&2
fi
if $noover || $overwrite; then
$debug && echo "checking for existance of directories..." 1>&2
# If the destination is not intended to be a directory...
if [ $# -eq 2 ] && [ ! -d "$dest" ]; then
Check "$dest" "$1" || exit 0 # No files to copy
else
while [ $i -lt $lastarg ]; do
Check "$dest/${argv[i]##*/}" "${argv[i]}" || unset argv[i]
let i+=1
done
fi
fi
[ ${#argv[@]} -lt 2 ] && exit 0
# If only 2 args are given, mv/cp will not insist that the destination
# be a directory, which we want if the destination ends in "/" or if
# the original number of args was >2.
# $# is still the original number of args.
# Tack the file name onto the destination to force this behaviour.
lastisslash()
{
case "$1" in
*/) return 0;;
*) return 1;;
esac
}
if [ ${#argv[@]} = 2 ] && { lastisslash "$2" || [ $# -gt 2 ]; }; then
$debug && echo "Appending filename." 1>&2
# Don't know which element of argv[] holds the source filename,
# since may have started with more than 1 source file & had some unset.
# So, compact args to make it easy to find the set one.
argv=("${argv[@]}")
argv[1]="${argv[1]}/${argv[0]##*/}"
fi
$debug && echo "Executing command: $cmd ${argv[@]}" 1>&2
exec $cmd "${argv[@]}"
|