$ find . -depth
./prova.sh
./pollo/pippo/prova2
./pollo/pippo/pluto
./pollo/pippo
./pollo/prova1
./pollo/prova con spazi
./pollo
.
and this script:
###
#! /bin/bash
lista=( $(find . -depth | sed "s/ /_/g" | sed "s/^\.//g" | sed
"s/^\///g") )
for i in $(seq 0 $((${#lista[@]} - 1)))
do
percorso=`echo '"'`${lista[$i]}`echo '"'` # for managing spaces
dir=`dirname $percorso`
base=`basename $percorso`
mv `echo $percorso | sed "s/_/ /g"` `echo $dir | sed "s/_/
/g"``echo "/"``echo $base | sed "s/_/ /g" | tr [:lower:] [:upper:] |
sed "s/_/ /g"`
done
###
but it returns:
mv: cannot stat `"prova.sh"': No such file or directory
mv: cannot stat `"pollo/pippo/prova2"': No such file or directory
mv: cannot stat `"pollo/pippo/pluto"': No such file or directory
mv: cannot stat `"pollo/pippo"': No such file or directory
mv: cannot stat `"pollo/prova1"': No such file or directory
mv: when moving multiple files, last argument must be a directory
Try `mv --help' for more information.
mv: cannot stat `"pollo"': No such file or directory
If I echo the command-line created by the script and it seems to be ok
and if I copy and paste it on the shell it works, so I suppose the
problem is how bash manage something into the command I create.
Has someone any suggestion?
Thanks, Elerdin.
Well, you certainly did attempt it first! It's not 100% clear what the
above is attempting to do or what your requirements are, so try this first:
find . -print | while IFS= read -r dirfile
do
srcDir="${dirfile%/*}"
srcFile="${dirfile##*/}"
destDir=`echo "$srcDir" | tr '[a-z]' '[A-Z]'`
destFile=`echo "$srcFile" | tr '[a-z]' '[A-Z]'`
echo "a) ${srcDir}/${srcFile} -> ${destDir}/${destFile}"
echo "b) ${srcDir}/${srcFile} -> ${srcDir}/${destFile}"
done
and tell us which of the transformations, a or b, if any, is what you
actually want. If neither, what do you want? It would also be useful to
know which shell you're using.
Ed.
Study the Frequently Asked Questions (FAQ), most notably 2.6):
Message-ID: <unix-faq/faq/part2_11...@rtfm.mit.edu>
http://groups.google.com/group/comp.unix.questions/msg/ec76fcc5a70422db
(Re)read and study the bash(1) manual pages, most notably the sections
on echo, quoting, and command substitution.
references/excerpts:
Message-ID: <unix-faq/faq/part2_11...@rtfm.mit.edu>
http://groups.google.com/group/comp.unix.questions/msg/ec76fcc5a70422db
bash(1) *
test(1) *
[(1) *
expr(1) *
echo(1) *
ksh(1) *
sh(1) *
tr(1)
sed(1)
perl(1)
* note that in many cases, many of these utilities also exist as
built-ins for many of the more modern shells and implementations
thereof.
I hope I can explain better, I have this directory structure:
./DIR1/DIR2/DIR3/FILE3.TXT
./DIR1/DIR2/DIR3
./DIR1/DIR2/FILE2.TXT
./DIR1/DIR2
./DIR1/FILE WITH SPACES.TXT
./DIR1/FILE1.TXT
./DIR1
I want to obtain this:
./dir1/dir2/dir3/file3.txt
./dir1/dir2/dir3
./dir1/dir2/file2.txt
./dir1/dir2
./dir1/file with spaces.txt
./dir1/file1.txt
./dir1
I create that script, but I do not understand why it does not work. I
post another time the script:
###
#! /bin/bash
list=( $(find . -depth | sed "s/ /_/g") )
for i in $(seq 0 $((${#list[@]} - 1)))
do
fullpath=`echo '"'`${list[$i]}`echo '"'`
dir=`dirname $fullpath`
base=`basename $fullpath`
echo "mv "`echo $fullpath | sed "s/_/ /g"`" "`echo $dir | sed "s/_/
/g"``echo "/"``echo $base | sed "s/_/ /$done
###
As you can see the "mv" line, the last one, is echoed, the command it
output are all correct, and if I put the into a file and execute that
file all works fine, but I do not understand why the same commands do
not work INSIDE the script. The problem is the "mv" line for sure, but
I do not understan where, probably the bash does something I do not
understand.
Thanks, Elerdin.
Actually, I don't understand why do you expect it to work correctly.
It's too complicated for such simple task.
> As you can see the "mv" line, the last one, is echoed, the command it
> output are all correct, and if I put the into a file and execute that
> file all works fine, but I do not understand why the same commands do
> not work INSIDE the script. The problem is the "mv" line for sure, but
> I do not understan where,
Nope. The problem is not in the line with mv. The problem is in your
whole script. It's one big mess. Let me explain:
> #! /bin/bash
>
> list=( $(find . -depth | sed "s/ /_/g") )
What would it do if name of some directory with files contains spaces?
Another thing, let's assume you don't have dirs with spaces. How would
you rename './ble/file with spaces.txt'? You would only get
'./ble/file_with_spaces.txt'.
> for i in $(seq 0 $((${#list[@]} - 1)))
> do
What is that `seq` for? Do you really need the index of current array
element? Let me guess: you have some programming experience in C or
Pascal and nothing of higher level, right? In most scripting languages
(and many compiled ones) an array is a list and you can move through
whole list using element by element. This is the correct way to process
each element in bash:
#v+
for element in "${list[@]}"; do
#v-
> fullpath=`echo '"'`${list[$i]}`echo '"'`
What are all these quote characters for? Explain it[*]. And then,
rewrite this assignment.
> dir=`dirname $fullpath`
> base=`basename $fullpath`
If $fullpath contains in some magical way spaces, then how would dirname
react? And basename? Another matter is the $fullpath will never contain
spaces, but names of your files will do.
> echo "mv "`echo $fullpath | sed "s/_/ /g"`" "`echo $dir | sed "s/_/
> /g"``echo "/"``echo $base | sed "s/_/ /$done
Ouch. Too many `echo|sed's and misused quote characters for me.
Again, try to explain every single quote character[*].
Last of all, I'll use "find ... | perl -e '...'", since Perl is able to
manipulate filenames and then rename files without exec()ing lots of
processes, which would speed up the command significantly for large
number of files (>500). And yes, I did use `find|perl' this way.
[*] I really mean "explain". What it should it do? And what it is
actually doing? I know from experience that this helps very much in
understanding explained thing.
--
Feel free to correct my English
Stanislaw Klekot
That's just this:
find . -print | while IFS= read -r src
do
dest=`echo "$src" | tr '[A-Z]' '[a-z]'`
srcDir="${src%/*}"
destDir="${dest%/*}"
mkdir -p "$destDir"
mv "$src" "$dest"
done
Regards,
Ed.
Maybe you didn't have a chance to see what I'd posted earlier. Didn't
exactly spell out a full answer, but provided hints and pointers to the
essential information resources:
Message-ID: <1137952831.1...@g44g2000cwa.googlegroups.com>
http://groups.google.com/group/comp.unix.shell/msg/f7909944d4615bdb
I suggest studying that stuff (totally unbiased opinion, of course).
;-)
> I hope I can explain better, I have this directory structure:
> ./DIR1/DIR2/DIR3/FILE3.TXT
> ./DIR1/DIR2/DIR3
> ./DIR1/DIR2/FILE2.TXT
> ./DIR1/DIR2
> ./DIR1/FILE WITH SPACES.TXT
> ./DIR1/FILE1.TXT
> ./DIR1
> I want to obtain this:
> ./dir1/dir2/dir3/file3.txt
> ./dir1/dir2/dir3
> ./dir1/dir2/file2.txt
> ./dir1/dir2
> ./dir1/file with spaces.txt
> ./dir1/file1.txt
> ./dir1
> I create that script, but I do not understand why it does not work. I
> post another time the script:
> #! /bin/bash
> list=( $(find . -depth | sed "s/ /_/g") )
> for i in $(seq 0 $((${#list[@]} - 1)))
> do
> fullpath=`echo '"'`${list[$i]}`echo '"'`
> dir=`dirname $fullpath`
> base=`basename $fullpath`
> echo "mv "`echo $fullpath | sed "s/_/ /g"`" "`echo $dir | sed "s/_/
> /g"``echo "/"``echo $base | sed "s/_/ /$done
> As you can see the "mv" line, the last one, is echoed, the command it
> output are all correct, and if I put the into a file and execute that
> file all works fine, but I do not understand why the same commands do
> not work INSIDE the script. The problem is the "mv" line for sure, but
> I do not understan where, probably the bash does something I do not
> understand.
Okay, ... how about some more hints and a potentially useful example:
$ find DIR1 -print
DIR1
DIR1/DIR2
DIR1/DIR2/DIR3
DIR1/DIR2/DIR3/FILE3.TXT
DIR1/DIR2/FILE2.TXT
DIR1/FILE1.TXT
DIR1/FILE WITH SPACES.TXT
$ ./lcase
$ find dir1 -print
dir1
dir1/dir2
dir1/dir2/dir3
dir1/dir2/dir3/file3.txt
dir1/dir2/file2.txt
dir1/file with spaces.txt
dir1/file1.txt
$ cat lcase
#!/bin/sh
find . -depth -name '*[A-Z]*' -exec bash -c '
t="`echo -nE '\''{}'\'' | sed -e '\''s![^/]*$!\L&!'\''`"
if [ -a "$t" ] || [ -h "$t" ]; then
1>&2 echo "$0: $t already exists"
else
mv -f '\''{}'\'' "$t"
fi
' \;
$
If one carefully studies bash(1), particularly all the quoting and
command substitution stuff and also the echo and test ([) built-in
to bash(1), and also find(1), sed(1) and mv(1), the above example
should all start to make lots of sense (as should why your earlier
example didn't achieve what you wanted).
And extra credit: The example I wrote won't work perfectly in all
cases. Find one or more flaws or potential unexpected behaviors (e.g.
where it won't precisely and only change the case of the the
filename/directory).
And at least some possible extra credit answers/hints (rot13(6)):
genvyvat arjyvar(f)
anzr pbyyvfvba grfgvat naq genvyvat arjyvar(f)
rkvg inyhr beqre qrcraqrapl
Use zsh:
autoload -U zmv # if not already in your ~/.zshrc
zmv '(**/)(*)' '$1${(L)2}'
--
Stéphane
The problem could be easier if you handle files and directories
separately.
#changing files:
find -type f | perl -nle 'm#[a-z]+[^/]*$# and m#^(.*/)(.*?)$# and print
"mv \"$_\" \"$1\U$2\""' | sh
1) use perl to separate filename with dirname, and print out only
filenames containing at least one lower-case character. then print out
in the following form:
m#[a-z]+[^/]*$# ---> at least one [a-z] in filename
m#^(.*/)(.*?)$# --> separate filename with dirname
print "mv \"$_\" \"$1\U$2\""' --> printout mv "$oldname" "$newname"
use quotes to enclose two arguments of "mv", which can solve the
problem of spaces in the filename/dirname.
#changing directories
find -type d | tac | perl -nle 'm#[a-z]+[^/]*$# and m#^(.*/)(.*?)$# and
print "mv \"$_\" \"$1\U$2\""' | sh
2) when operating on directories, use "tac" to reverse the output of
"find", and each time you change only the rightmost subdirectory. This
way you wouldnt get complained because you won't change the name of
parent-directory before that of sub-directory...
HTH,
Xicheng
I've seen many shell scripts which do it portably, and many which
take advantage of specific extensions, such as typeset -u in ksh,
or, in bash, William Park's extensions, or my loadable builtins,
ucase and lcase.
> especially if the files have embeded unicode and newlines,
With a litle care, a shell script can handle any characters in
filenames.
--
Chris F.A. Johnson, author | <http://cfaj.freeshell.org>
Shell Scripting Recipes: | My code in this post, if any,
A Problem-Solution Approach | is released under the
2005, Apress | GNU General Public Licence
I didn't. Where did Chris posted his code in this thread?
> anything this simple should not take that many lines of code... if it
> does, then the language is ill suited for the job, imho.
Shell is well suited. Especially shell aided by some other tools, such
as GNU find and Perl.
#v+
find . -depth -print0 | perl -0lne '
BEGIN{$\ = "\n"; $, = " -> "}
$old = $_; s|(/[^/]*$)|lc $1|e;
print $old, $_ if $_ ne $old
'
#v-
When you replace "print" with "rename" and optionally remove BEGIN{}
block, then the code is ready to do massive renaming. Nobody said that
you can't use Perl or Ruby interpreter when it is appropriate, and shell
glues find and Perl in very convenient way.
Before I got know about -depth I used `sort -rz'. For any reasonable
number of files it's fast enough.
I agree that code should be as short as possible. That does not
necessarily mean that it will be short. When you use a compiled
utility, you are probably executing more lines of code than it
would take to do the same thing with a shell function.
Even a short program in a language such as C will make use of
large quantities of code in the standard library.
For example, in my library of arithmetic shell functions, I have
one for multiplying decimal fractions. It is more than 80 lines
long. Do you think that writing:
. math-funcs
fpmul 1.23 45.67
is using more code than:
awk 'BEGIN { print 1.23 45.67; exit }'
Take a look at the source code for awk and say that with a
straight face.
If I rewrote the fpmul code every time I needed to multiply two
decimal fractions, then yes, the probability of a bug would be
high. On the other hand, by using a function with well debugged
code, that is not an issue.
That is why I put a lot of emphasis in my book on reusable
function libraries.
Portably (POSIXly):
find .//. -depth -print |
awk -F/ -vOFS=/ '
function escape(s) {
gsub(/'\''/, "'\''\\\'\'\''", s)
return "'\''" s "'\''"
}
function process_file(file) {
$0 = file
$NF = toupper($NF)
print "mv -i " escape(file) " " escape($0)
}
NR == 1 {
file = substr($0, 4)
next
}
/\/\// {
line = substr($0, 4)
process_file(file)
file = line
next
}
{
file = file "\n" $0
}
END {
if (file != "")
process_file(file)
}' | sh
--
Stéphane
That's not this simple, especially if you want to resolve
conflicts (which zmv does).
> and zmv is a beautiful thing to watch, but it can only really do one
> thing at a time, so that means slow....
Not sure what you mean. If you mean that mv is called for each
file, then make mv builtin:
~$ type mv
mv is /bin/mv
~$ zmodload zsh/files
~$ type mv
mv is a shell builtin
If you mean that the list is first built, and then the files in
that list renamed, then zmv has to do it that way to resolve
potential conflicts.
You can also use mmv for that:
mmv ';*' '#1#l2'
But once you have zsh, you don't need mmv. zmv can do a lot more
than what mmv can has it has all the expansion operators of zsh.
(mmv can't do padding or arithmetics or use associative arrays
or call commands like "file" for instance).
--
Stéphane
Using the Ruby language, it's a one-liner:
ruby -e 'Dir["**/*"].each{|x|File.rename(x,x.downcase)}'
Children need to be changed before the parent:
ruby -e 'Dir["**/*"].reverse.each{|x|File.rename(x,x.downcase)}'
Not yet, you need to downcase the basename only.
$ find .
.
./B
./B/C
./B/C/D
$ ruby -e 'Dir["**/*"].reverse.each{|x|File.rename(x,x.downcase)}'
-e:1:in `rename': No such file or directory - B/C/D or b/c/d (Errno::ENOENT)
from -e:1
from -e:1
--
Stéphane
File.rename(x, File.dirname(x)+"/"+File.basename(x).downcase)}'