Expert thoughts on this little snippet:
- If you’re dealing with a list of directories,
and a bunch of files (with names not known in advance),
don’t use
f
to represent a directory name.
That’s just too confusing.
When you’re dealing with a shell variable singly,
in isolation (not concatenated with anything else), simply,
without modification (e.g., by ##
, %
, :
or /
),
you don’t need curly braces.
See When should I assign double quotes around variables like "${var}"
to prevent problems?
So, I suggest that the outer loop should look like this:
for d in <list of directories>
do
cd "$d"
(Code to execute in each directory)
cd ..
done
In addition to @Eric Renouf’s perfectly good answer,
there are several ways to write the (Code to execute in each directory).
Here’s a simple one, close to what you tried:
for f in *.txt
do
if [ -e "$f" ]
then
mv -- "$f" "${PWD##*/}".txt
fi
done
If there is a single <something>.txt
file
in the directory, then this will do what you want and expect:
the for f …
loop will run once, with $f
equal to the name of the file,
and the file will be renamed.
(I use mv --
to protect against files
whose names begin with -
and would otherwise be interpreted as options.
mv ./"$f" …
would probably also be safe,
as long as you’re sure that $f
is a file in the current directory,
or is a relative pathname.)
If there are no *.txt
files in the directory, then,
also, the loop will run once, with $f
equal (literally) to *.txt
.
Since there is no file by that name, the -e
test will fail
and the mv
will not be executed.
There’s a problem if there are multiple text files in the directory.
If there are n such files, then the loop will run n times,
and each text file will be renamed to "${PWD##*/}".txt
.
As Zachary Brady pointed out,
this can result in the last file overwriting the first n−1 files.
One way to fix this is to add the -i
option to the mv
command,
so you will be warned of any attempt to overwrite an existing file,
and given the option to approve it or deny it.
This is, of course, not useful if the script is running as a cron
job.
Another approach is to add a break
command to the loop:
for f in *.txt
do
if [ -e "$f" ]
then
mv -- "$f" "${PWD##*/}".txt
break
fi
done
This will cause the loop to execute exactly once,
because, when it does run, it executes the break
command,
which terminates the loop.
This way, only the first text file will be renamed,
and any others will be left alone.
You will not be notified that there were multiple files.
A slightly more sophisticated approach is
to detect and handle the multiple file case:
first=1
for f in *.txt
do
if [ -e "$f" ]
then
if [ "$first" ]
then
mv -- "$f" "${PWD##*/}".txt
first=
else
printf "Warning: file %s not renamed.\n" "$f"
fi
fi
done
in which the first
flag is set at the beginning of the loop,
and then cleared during the first iteration,
so subsequent iterations know that they are not the first.
Alternatively, you could check whether "${PWD##*/}".txt
already exists.
.txt
,info.txt
and*.txt
. (2) What are you looping over? (3) You’re missing adone
. (99) Please clarify. Please do not respond in comments; edit your question to make it clearer and more complete.