LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - Software (http://www.linuxquestions.org/questions/linux-software-2/)
-   -   rename HIDDEN files recursively (http://www.linuxquestions.org/questions/linux-software-2/rename-hidden-files-recursively-893701/)

mrmnemo 07-25-2011 04:08 PM

rename HIDDEN files recursively
 
Hi,
How would I rename all files with a leading decimal point recursivley? I some how got all my music files to have a decimal point.
I tried the below and got a " sed argument to long".
[CODE]find /media/MUSIC -type f -name "*.wma" | xargs -0 sed -i 's/.\(.*\)/\1/'[CODE]

Another question, can i just use -type f with out -name ? I am sure that all the files got the decimal point added as the first character.

berbae 07-25-2011 05:22 PM

You can try :
Code:

#!/bin/bash
for f in $(find /media/MUSIC -type f -name "\.*"); do
    fn=$(basename $f)
    fm=${fn:1}
    mv $fn $fm
done


MTK358 07-25-2011 05:32 PM

That won't go into subdirectories recursively. Try this:

Code:

find /media/MUSIC -type f -name '.*.wma' | while read file
do
    basename="${file##*/}"
    mv "$file" "${file%/*}/${basename#.}"
done


berbae 07-25-2011 05:38 PM

Quote:

Originally Posted by MTK358 (Post 4424983)
That won't go into subdirectories recursively

Yes I saw it and I edited my post to go to subdirectories, before seeing your post.

MTK358 07-25-2011 05:53 PM

Quote:

Originally Posted by berbae (Post 4424992)
Yes I saw it and I edited my post to go to subdirectories, before seeing your post.

Your new solution has another problem: It won't work if the filenames have spaces.

Nominal Animal 07-25-2011 09:01 PM

I recommend using
Code:

find ./ -depth -name '.*.wma' -print0 | xargs -r0 rename -v 's|/\.+([^/]+)$|/$1|'
  • The -depth option for find selects depth-first search.
    In this case it does not matter since we work on files only, not on directories.
    If you rename directories too, you want to rename the deepest items first.
    I think it's best to always use -depth when renaming.
  • The -print0 option for find and the -0 option for xargs are a pair. This way the file names are separated by NULs (zero bytes); the only byte never allowed in file names. This will work correctly for even the weirdest file names, for example those containing newlines.
  • The -r option for xargs tells it to not run the command if there are no file names. It just avoids a silly error message if the find does not find anything.
  • The rename command is a common shell utility written in Perl.
    It receives a regular expression and one or more file or directory names as parameters. It will apply the regular expression to each name; if the name changes, the file or directory is renamed accordingly. In this case, it will remove any leading dots from the file name component.

Note that the regular expression for rename works on the entire argument. In this case the arguments contain the entire path. Because the regular expression should only work on the final component, the file name, the pattern starts with a slash. (There will always be a slash, if find starts at ./.)

It seems that Perl 5.10.1 fails to obtain statistics on files or directories when the name contains newlines. (Weird. Neither the stat utility or the stat() family of functions have any issues with such files. It must be some Perl brain damage.) Fortunately, this does not seem to cause any errors or even problems when running the rename command; you will just see superfluous Unsuccessful stat on filename containing newline warnings. You can safely ignore those.

grail 07-25-2011 10:26 PM

@Nominal - nice one liner although if you don't have the Perl version of rename it tends to fall in a bit of a hole :(

berbae 07-26-2011 04:28 AM

I forgot the path in the mv command. So here is my last try, with double quotes in case of space in the names :
Code:

#!/bin/bash
for f in $(find /media/MUSIC -type f -name "\.*"); do
    dn="$(dirname "$f")"
    fn="$(basename "$f")"
    fm="${fn:1}"
    mv "$dn/$fn" "$dn/$fm"
done


grail 07-26-2011 06:14 AM

Quote:

with double quotes in case of space in the names
The point you are missing here is that it is the for loop which will split on white space so no amount of quoting inside will help as the splitting has already occurred.

MTK358 07-26-2011 08:00 AM

Quote:

Originally Posted by berbae (Post 4425363)
I forgot the path in the mv command. So here is my last try, with double quotes in case of space in the names :
Code:

#!/bin/bash
for f in $(find /media/MUSIC -type f -name "\.*"); do
    dn="$(dirname "$f")"
    fn="$(basename "$f")"
    fm="${fn:1}"
    mv "$dn/$fn" "$dn/$fm"
done


No, it will still fail with spaces in filenames. This is where the problem is:

Code:

for f in $(find /media/MUSIC -type f -name "\.*")
How is the shell supposed to know the difference between spaces in filenames and the whitespace that find uses to separate the filenames?

berbae 07-26-2011 10:17 AM

Yes you are right. So I think that will work better now :
Code:

#!/bin/bash
listfiles=$(find /media/MUSIC -type f -name "\.*")
while read f; do
    dn="$(dirname "$f")"
    fn="$(basename "$f")"
    fm="${fn:1}"
    mv "$dn/$fn" "$dn/$fm"
done <<< "$listfiles"


Nominal Animal 07-26-2011 01:05 PM

For those who wish to apply a Basic Regular Expression to all file and directory names using nothing but Bash, GNU find, GNU sed, and GNU tr, consider using this snippet:
Code:

bash -c '
    OLD_LANG="$LANG"
    OLD_LC_ALL="$LC_ALL"
    LANG=C
    LC_ALL=C
    export LANG LC_ALL
    find ./ -depth -name '"'"'.*.wma'"'"' -print0 | while read -d "" FILE ; do
        DIR="${FILE%/*}"
        OLD="${FILE##*/}"
        echo -n "$OLD" \
        | tr '\n' '\0' \
        | sed -e '"'"'s/^\.\+//'"'"' \
        | tr '\0' '\n' \
        | (
            read -d "" NEW
            if [ "$NEW" != "$OLD" -a -n "$NEW" -a -n "$OLD" ]; then
                env LANG="$OLD_LANG" LC_ALL="$OLD_LC_ALL" \
                mv -vi "$DIR/$OLD" "$DIR/$NEW" || exit $?
            fi
        )
    done
'

The first red part specifies the directories where the search is to be started in. They must all end with a slash.

The second red pattern is the glob pattern defining the file names to be modified.

The third red pattern is the regular expression or expressions to apply to the file name. Since it is a POSIX BRE (Basic Regular Expression, see Wikipedia) and not a Perl Regular Expression, parentheses and the plus ("one or more") need to be escaped, and so is a bit different to when using rename . Since this one operates on the file name, there will never be a slash in the subject.

The '"'"' may look a bit weird, but it just evaluates to one single-quote ('). This is because the entire body of the command is in single quotes. (The body will only work in Bash, so I made sure it is run with Bash too.)

Locale (LANG and LC_ALL) and tr are needed to stop sed from applying the pattern in the middle of the file name, if it happens to have newlines in it. Newlines are temporarily converted to NULs (\0), so if your pattern specifies newlines, use \0 instead of \n.

The snippet is only lightly tested, but it really should work with any file and directory names possible in Linux.

mrmnemo 07-30-2011 01:10 PM

Quote:

Originally Posted by Nominal Animal (Post 4425085)
I recommend using
Code:

find ./ -depth -name '.*.wma' -print0 | xargs -r0 rename -v 's|/\.+([^/]+)$|/$1|'

Thanks for that. I went that route to get things solved. It was the regex stuff. so, for a "." you have to back tick it to make it be seen as absolute? 's|/. vs. 's|/\. ?

Thanks to all of you guys.
Marking as solved. However, I really like what I am learning regarding regex from people that know it. While marking as solved I may post questions regarding the use of regex; that is, assuming thats ok.

MTK358 07-30-2011 02:18 PM

Quote:

Originally Posted by mrmnemo (Post 4429429)
's|/. vs. 's|/\. ?

I have no idea what you mean here.

Putting a backslash before a regex special character makes it literal. So to match a literal dot:

Code:

\.

mrmnemo 07-30-2011 07:28 PM

One more question: if my variable has () in it
Code:

find /media/music/ -depth \( -name "*.m4a" -o -name "*.wma \)
how would i format the command to work as
Code:

for f in $((find /media/music/ -depth \( -name "*.m4a" -o -name "*.wma \)); do ffmpeg -i $f -sameq -map_meta_data ${f%.*}.mp3:$f ${f%.*}.mp3; done
I guess its really two questions. If I have options in closed in () that is ALSO insidew a variable do i have to do anything special? Also, if I want to use the file name found using the variable as the new file name would the above work?
any help would be appreciated.


All times are GMT -5. The time now is 03:11 AM.