Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Shell script command line parameters

Status
Not open for further replies.

cardy

Technical User
Sep 5, 2000
33
GB
I'd like to write a (korn) shell script that replaces the mv command so that it doesn't overwrite a file that already exists. I'd like it to function like the mv command so wish to type something like :

move /dir1/*.txt /dir2/

and have all files ending in .txt in dir1 moved to dir2 but only if the file doesn't already exist in dir2.
My problem is how can I refer to the destination (in this case /dir2/) in my shell script, as using positional parameters ($1 $2 $3 etc.) doesn't work because *.txt gets expanded and $1 $2 $3 etc. then refer to the expanded file list (for example, a.txt b.txt c.txt ...).

TIA

cardy
 
Thanks Greg but the problem with mv -i is it requires a response. I'd just like to have any files that couldn't be moved (because the destination file already exists) reported on and skipped, without any user intervention.
 
Maybe a loop? For file in expanded *.txt list, test whether file exists, if not, move? Just a thought...New to scripting myself and I don't know ksh, but the idea seems reasonable...
 
Thanks for that DumTech but you haven't quite grasped my problem. Let me try to explain. I want to be able to type

move a b

where a is either one file (in which case b can be a file or a directory) or a number of files using wildcards e.g. *.txt (in which case b must be a directory). In my script I would refer to a with $1 and b with $2 (or so I thought) and loop using something like

for file in $1

as you so rightly suggested. The only problem is $2 does not refer to b if a refers to a number of files using wildcards. In fact $2 refers to the second matched file and $3 refers to the third and so on. So my problem is I don't know which positional parameter refers to the destination (b).

I hope I've explained my problem properly now and that there is someone out there with an answer as I'm sure there is because I can't believe I'm the only one who has ever tried doing this sort of thing.

TIA

Cardy
 
If I understand, you just want mv to NOT mv a file if the destination already exists? How about this:

#/usr/bin/ksh
SOURCE=$1
DEST=$2
if [[ -e $2 ]] ; then
echo "USAGE: $2 already exists"
else
/usr/bin/mv $1 $2
fi

Of course you can make many changes to this, the message could re-direct to a log and so forth. I would suggest placing this script (or something like it) in /usr/local/bin/mv, and then adjust the users path variables to look here first.

crowe
 
Thanks crowe but have you actually tried running this. Yes it works OK if you say

mv file1 file2

This moves file1 to file2 only if file2 does not exist, but what if you say

mv x* sub

i.e. move all files beginning with x to the directory sub. As I said above when you specify wildcards for the first parameter, $2 will be set to the second matched file NOT THE SECOND PARAMETER ON THE COMMAND LINE.
In other words, just to make things absolutely clear, if you have some files in a directory called f1 f2 & f3 and you typed

mv f* sub

then $1 will be set to f1, $2 will be set to f2 and $3 will be set to f3. In this case $4 will be set to sub, but when you don't know how many files you are going to match to how do you know which positional parameter will refer to your destination. Is there another way of doing it without referring to positional parameters (how does mv do it?).
 
As soon as the shell sees a *, it does the filename expansion automatically. The only way to avoid this is to either

1) turn of globbing in the shell. In ksh this is done by set -f to turn globbing off, and set +f to turn it on again. "globbing" is the term used for filename expansion.

2) Use a shell escape at the command line to "hide" the *. So if your script is called mv2, then you could do mv2 dir1/\*.txt /dir2

I guess it works with mv because mv is written to parse the positional parameters supplied through ARGV[] as the shell expands them.

What you could do is supply the destination as the 1st parameter, save it in the script (it will be $1). Then do a shift which will shift all the parameter 1 position "left". Then all the expanded filename will be available in $*

For example, I have a dir with 3 files:
file1
file2
file3

My script (mv2) looks like this
Code:
echo $*
shift
echo $*
If I do the command mv2 destination * I get the following output

destination file1 file2 file3
file1 file2 file3

Does this help at all?

Greg.
 
Thanks Greg, we seem to be getting somewhere. I'll use this idea for now but if someone can come up with a way of doing it like mv does it (i.e. specifiying the destination as the second parameter) I'd be very grateful. I'm trying to replace the mv command for the users (who are constantly overwriting files they don't mean to) and want to make it idiot-proof for them i.e. not have them remember to have to put the destination first.

Many thanks

Cardy
 
Given my simplistic knowledge of C, I would say it's easier for mv because if can easily get at the last parameter through the ARGV array. I can't think of any easier way to do that within the shell. Have you thought of writing something simple in C to do it?

Otherwise, in the shell I guess you could do something like this
Code:
FILES=""
while [ $1 != "" ]
do
  FILES = "$FILES $1"
  DEST = $1
  shift
done
What will happen here is that FILES will eventually contain a copy of all the original parameters, and DEST will hold the last parameter.

All you then need to do is get rid of the last parameter from the FILES variable! I guess you could loop through FILES, assigning each param to another variable until you find one equal to DEST, and ignore that.

More food for thought though :)

Greg.
 
Under Linux I would use this.
==================================
#!/bin/bash

echo "$(expr $# - 1 ) files to move"
I=1
while [ ${I} -lt ${#} ]
do
filelist="${filelist} ${1}"
shift
done
if [ ! -d ${1} ]
then
echo "Destination must be a directory"
exit 1
fi
mv -b ${filelist} ${1}
currdir=$(pwd)
cd ${1}
for file in $(ls *~)
do
orig=$(echo ${file}|tr -d '~')
mv ${orig} ${currdir}
mv ${file} ${orig}
done
===============================================
If your mv command does not support the backup option
I would use something like this... watch out for limits on variable size...
==========================================
#!/bin/sh
echo "$(expr $# - 1 ) files to move"
I=1
while [ ${I} -lt ${#} ]
do
filelist="${filelist} ${1}"
shift
done
if [ ! -d ${1} ]
then
echo "Destination must be a directory"
exit 1
fi
echo "Starting move with $(echo ${filelist}|wc -w) files"
mvcount=0
skipcount=0
for file in ${filelist}
do
if [ -e ${1}/${file} ]
then
skipcount=$(expr $skipcount + 1 )
else
mv ${file} ${1}
mvcount=$(expr $mvcount + 1 )
fi
done
echo "$mvcount files moved $skipcount files skipped"
Tony ... aka chgwhat
tony_b@technologist.com

When in doubt,,, Power out...
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top