LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Bash script to change a filename associated with an inode index number. (https://www.linuxquestions.org/questions/programming-9/bash-script-to-change-a-filename-associated-with-an-inode-index-number-180741/)

Ziv 05-12-2004 01:54 PM

Bash script to change a filename associated with an inode index number.
 
I am trying to write a BASH script that will search through a directory and find filenames with illegal characters in them then find the inode associated with the file and then change the filename associated to that inode number to a legal name by stripping out illegal chars and replacing them with underscores.

I have a piece of code that is in the right direction. It finds illegal names and removes them.

With code like this:

//Remove by filenames with illegal chars.
find . -name '*[+{;"\\=?~()<>&*|$ ]*' -exec rm -f '{}' \;

or

//Remove by passing an inode number.
find . -inum $inum -exec rm {} \;

Instead of removing the file I want to keep the file but change the filename associated with the inode number.

Missing pieces that I think i still need:
Method of stripping the bad filename of illegal chars and replacing them with underscores. I assume there will need to be a string variable that holds the old name and then is processed and then stored in a new name variable.
These two variables could be used with a move (mv) command to change the bad name.

Is there a direct way to update the name associated with an inode number, without using mv?

I am still working on finding the solution to this and I will post what I find, but any help out there would be great.

Thank you,
Ziv

Hko 05-12-2004 02:29 PM

Quote:

Is there a direct way to update the name associated with an inode number, without using mv?
Why not mv?

Muzzy 05-12-2004 02:55 PM

Replacing with underscores:

filename='test=example+file?'
echo $filename
echo $filename | sed 's/[+{;"\\=?~()<>&*|$ ]/_/g'

I don't understand why you don't want to use mv either!

Ziv 05-12-2004 03:16 PM

I didn't know if it would work...
 
When I try it at the command line, it gives me trouble with the filenames, depending on the illegal chars it encounters in the name.

mv is fine, but it seems that I will need to do it by referring to it's inode number.

Am I right about this?

Ziv

unSpawn 05-12-2004 03:24 PM

When I try it at the command line, it gives me trouble with the filenames, depending on the illegal chars it encounters in the name.
I was gonna warn about that:..

filename="A$(echo -en \\x01\\x10\\x0101\\x002\\x83)Z"
now try:
echo $filename | sed 's/[+{;"\\=?~()<>&*|$ ]/_/g'
vs:
echo ${filename//[^a-zA-Z0-9]/}
replacement:

Code:

retScrubbedString() { # Slow but no sed needed:
declare -r static_char_restr="1234567890-_./abcdefghijklmnopqrstuv\
wxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
declare -r char_rpl="_"; str_arg=( $1 )
for charPos in $(seq 0 $(expr ${#str_arg[0]} - 1)); do
expr index "${str_arg[0]:${charPos}:1}" ${static_char_restr} >/dev/null
case "$?" in 1) str_arg_cpy_ok="${str_arg_cpy_ok}${char_rpl}";;
0) str_arg_cpy_ok="${str_arg_cpy_ok}${str_arg[0]:${charPos}:1}";;
esac; done; printf "%s${str_arg_cpy_ok}\n"; unset str_arg_cpy_ok; }

# retScrubbedString "${filename}"
A__1__Z

Muzzy 05-12-2004 04:09 PM

Tried your script, actually it outputs on my computer:
A___012_Z

Gonna take me a while to figure out exactly how that script works.

How about this, which I think has the same effect as your script, why not use sed with a more accurate regular expression?:
echo $filename | sed 's/[^0-9a-zA-Z_./-]/_/g'

unSpawn 05-12-2004 05:15 PM

Tried your script, actually it outputs on my computer:
A___012_Z

Depends on the charset you're using I guess?


How about this, which I think has the same effect as your script
Yeah, that works fine. That's the *easy* way tho :-]

Ziv 06-17-2004 09:28 PM

How can I use the find command to execute a mv?
 
I like the sed command that is above, but I am having problems tying that into a mv command that will change the old filename into a new one.

I was trying something like this...

find . -name '*[;"\\=?()<>&*|$]*' -exec $badname= '{}' \; | $filname= sed 's/[;"\\=?()<>&*|$]/_/g' | mv $badname $filename

I want the script to search the directory for bad filenames then replace the bad characters with underscores and mv the old badname to the new corrected filename.

Any help is appreciated...

ZIv

homey 06-17-2004 10:37 PM

How about something like this....

Code:

#!/bin/bash

for i in *; do
echo $i | grep -e '*[~!#%^+;"\\=?()<>&*|$]*' | \
mv $i `echo $i | sed 's/[~!#%^+;"\\=?()<>&*|$]/_/g'`
done


Ziv 06-18-2004 12:18 AM

Slight Problem...
 
There is a slight problem with that solution...

I get the following output after running the script.

**********

#sh rename_filename_test.sh
mv: when moving multiple files, last argument must be a directory
Try `mv --help' for more information.
mv: `rename_filename_test.sh' and `rename_filename_test.sh' are the same file

**********

Should the script file be included in the mv?
None of the illegal characters are in the filename.

Also, should the file be in the same folder the script is running on?

Thank you for the reply,
Ziv

homey 06-18-2004 12:30 AM

Hmm, it works ok for me. Try copy the entire script and paste it into a text file called test. I have that file located in the directory and run it with the command: sh test

Ziv 06-18-2004 12:33 AM

Quote:

Originally posted by homey
Hmm, it works ok for me. Try copy the entire script and paste it into a text file called test. I have that file located in the directory and run it with the command: sh test
I get the same type pf thing again...

# sh test
mv: `rename_filename_test.sh' and `rename_filename_test.sh' are the same file
mv: `test' and `test' are the same file

What is the output like when you run it?

Ziv

homey 06-18-2004 12:38 AM

Oh, I see what you are saying.

Quote:

mv: `rename_filename_test.sh' and `rename_filename_test.sh' are the same file
mv: `test' and `test' are the same file
That's because those files are already changed to the underscore type of name so the move command gets a little confused about it. No harm done and the script is working. :)

Ziv 06-18-2004 12:48 AM

Looks like it is working good...
 
Two last things that need ironed out...

1) It doesn't seem to be changing the "[" or "]" chars in the filenames.

2) It is not recursing subdirectories it only tries to change the name of it.

See below...

# sh test
mv: `23-Mar-2004_120109[1].tar.gz' and `23-Mar-2004_120109[1].tar.gz' are the same file
mv: `backup_utils.txt' and `backup_utils.txt' are the same file
mv: cannot move `cups-pdf' to a subdirectory of itself, `cups-pdf/cups-pdf'
mv: `encryptpdf.exe' and `encryptpdf.exe' are the same file
mv: cannot move `printerdrivers' to a subdirectory of itself, `printerdrivers/printerdrivers'
mv: `QCS_A2LA_Request.doc' and `QCS_A2LA_Request.doc' are the same file
mv: `remove_illegals.txt' and `remove_illegals.txt' are the same file
mv: `test' and `test' are the same file
mv: `western_web.zip' and `western_web.zip' are the same file


Any ideas on these problems?

Ziv

homey 06-18-2004 11:12 AM

Well, I hit a wall on this one. :(

The part about a name with brackets ( [ ] ) in it can be solved but it creates yet another problem as the period ( . ) before the file extension is also in the same class as the brackets. That is they both are [[:punct:]]

I also had very little luck with the recursive problem.

Code:

#!/bin/bash
#Note: those smileys don't belong there. It is alnum punct
for i in *; do
echo $i | grep -e '[[:alnum:]][[:punct:]]' | \
mv $i `echo $i | sed 's/[[:punct:]]/_/g'`
done


Muzzy 06-18-2004 04:52 PM

I'll have another BASH at this :)
 
Hello again,

I have had another go at this one....

Using find might not work because if you change the names of some directories half way through the search, I guess it can confuse find, possibly missing some files, therefore if you want a recursive algorithm, you should use a script with a for loop.

DISCLAIMER: I am not a bash expert... I am still learning every day. I have done a small amount of testing on this script and it seems to work on the few test cases I have tried, but recursive scripts that modify the filesystem are powerful tools to be used with care, so don't run this as root, and if the data is important, back it up first. If there are problems though, I will be happy to try to fix them, but I can't recover any lost data!

Code:

bad='[^A-Za-z0-9_.-]'

f() {
for i in *
do
  echo $i | grep $bad &&
    new=`echo $i | sed s/$bad/_/g` && mv -i "$i" "$new" &&
    i=$new
  [ -d $i ] && [ ! -s $i ] cd $i && f && cd ..
done
}

f

Description: the first line specifies which characters are not allowed. Next a function f() is declared. This function lists all the files and directories in the pwd. It then greps each filename $i to see if it contains a bad character. If so, it calculates $new (the new filename), performs a move (asks before overwriting), and then updates $i to the new name. Then it checks if $i is a directory (but skip symbolic links) and if so, cd's into the directory and calls itself recursively. By skipping symbolic links, it hopefully won't go into an infinite loop, but I may have overlooked something. The last line calls the function that was jsut declared, starting at the current directory.

I used && instead of ;'s and if fi blocks. You may find this hard to read. Sorry! I'm sure this code could be cleaned up a bit and it can definitely be made more readable! Improvements are very welcome, I would be happy to see alternative ways to do this.

I hope this is at least be something you can work from. Let me know how you get on.

Happy Bashing!
Mark.

Ziv 06-18-2004 05:49 PM

Thank you I will give it a try...
 
I will let you know how it goes...

Thank you for your help putting this together.

Ziv

Ziv 06-18-2004 08:12 PM

I think I got it!!!
 
Well this seems to work for me, with some modifications...

Here is the code I created based off of your help.

Code:

#!/bin/bash

badnames=`find -name '*[;"\\=?()<>&*|$]*'`

for x in $badnames
do
  new=`echo $x | sed 's/[{;"\\=?()<>&*|$]/_/g'`
  echo $x
  echo $new
  mv -i "$x" "$new"
done

It worked perfectly on a 3 level deep directory.

Do you see any problems with this method?

Ziv

Ziv 06-18-2004 09:32 PM

It is not working perfectly...
 
Dang!

It doesn't handle spaces in directory names.

Well... any other ideas?

Ziv

Muzzy 06-19-2004 07:47 AM

What error did you get?
 
As I tried to point out in my earlier post, I don't think you should use find if you are going to be renaming the directories, since this will cause lots of file not found errors once you have renamed the parent directories (NOTE: I reconsidered this again, and now i have decided that if you make sure that you rename the deepest file first, maybe you can get this to work! I will experiment with this).

Your problem with spaces is because the way 'for' works on a variable, splitting at the spaces. Although I'm sure there is a way to fix this, you will still end up with the problem with find I mention above.

To avoid both these problems, I would advise using the 'for i in *' method, as I did in my example, and iterating through the directories manually. I have no idea what problem you encountered. Perhaps I didn't explain well enough how to use the script? Or perhaps you have something in your directory structure that I hadn't thought of (sockets / hard links / some feature I've never heard of / a strange character that causes my script to fail).

If you could explain to me exactly what didn't work wiht my script, I am sure I can fix the problem. Ideally, post a script which creates a directory structure for which my proposed renaming script fails such as
Code:

mkdir 'foo bar'; touch 'foo bar/baz';
Also post the error message you get. Then I can run it on my computer to reproduce the error, and work out how to fix it. It is much easier for me to fix an error if it is reproducable on my computer.

Mark.

Muzzy 06-19-2004 07:54 AM

I found an error in my script! The last minute change I did to ignore softlinks was incorrect in the example I posted! (I missed a &&)

Code:

bad='[^A-Za-z0-9_.-]'

f() {
for i in *
do
  echo $i | grep $bad &&
    new=`echo $i | sed s/$bad/_/g` && mv -i "$i" "$new" &&
    i=$new
  [ -d $i ] && [ ! -s $i ] && cd $i && f && cd ..
done
}

f

If this doesn't work for you, please post the error!

Muzzy 06-19-2004 08:18 AM

OK there are problems with my script too! First the test for a symbolic link is -h, and not -s. Second, it looks like the values of local variables are not restored to their original values on returning from a function call.... perhaps it is possible to specify variables as local somehow. I'll look at this now and get back to you later when I have an answer.

Muzzy 06-19-2004 08:41 AM

Script version 3 :

Code:

bad='[^A-Za-z0-9_.-]'

f() {
for i in *
do
  j=$i
  echo $i | grep $bad && { j=`echo $j | sed s/$bad/_/g`; mv -i "$i" $j; }
  [ -d $j ] && [ ! -h $j ] && { cd $j; f;  cd ..; }
done
}

f

Test: (assumption: script exists in the current directory and is called 'renamebad')

Code:

$ mkdir 'a b'; touch 'a b/c d'; sh renamebad
Expected result:

Code:

$ ls -R
.:
a_b  renamebad
 
./a_b:
c_d

Have a go with this! Slightly better tested than my first effort.


All times are GMT -5. The time now is 07:02 PM.