2

I have recently managed to convince my mother to let me switch her PC to Ubuntu, and to make her transition easier I want to automate as many tasks as possible for her. I managed to do quite a bit, however there is a script I'd like to leave her with, but, unfortunately I have no knowledge of scripting.

The purpose would be to rename all text files in a folder (mainly Desktop) named e.g. "note*" (w/out extension) as "note.txt" (for interoperability's sake and easy upload to google docs) and move them in a specially designated folder. The commands I'd need would be:

- find files in current folder named note* (and not ending in .txt) and rename them as note*.txt
- move files named note*.txt to /home/username/notes

Unfortunately I don't know how to put it in script form so I'm asking for help.

5 Answers 5

4

This might get you started:

#!/bin/bash

find . -name 'note*' -not -name '*txt' -exec mv -bf '{}' '{}'.txt \;
find . -name 'note*.txt' -exec mv -bf '{}' /home/username/notes/ \;

The -bf makes mv not asking questions and backup if overwriting.

1
  • Problems! The script is not behaving as expected... When I run the script, it recursively pulls in everything in my home directory with the text "home" in it, and doesn't end in ".txt"
    – Peter.O
    Apr 2, 2011 at 15:28
2

Open up a terminal, and open up a text editor using this command (gedit, or your favourite!)

gedit ~/.gnome2/nautilus-scripts/Notes

This will open up a file in the Nautilus (file browser) Scripts folder for some magic that you will see soon :D
Now copy the following simplified code into gedit and save. (You can use Marcelo Morales if you want :P)

#!/bin/bash

# Words prefixed with a hash are comments.

# Save directory. Add in your own username and directory here.
movePath=/home/<username>/notes

# Iterate over files in current folder.
for noteFile in **
do
# Check if is a notes file (even if UPPERCASE or lowercase), and not already edited.
    if [[ ${noteFile,,} == *"notes"* ]] && [[ ${noteFile,,} != *".txt" ]] && [[ ! -d "$noteFile" ]]
    then
        # If so, move and rename the file to your save directory.
        mv "$noteFile" "$movePath/$noteFile.txt"
    fi
done

Give the script executable permissions.

chmod u+x ~/.gnome2/nautilus-scripts/Notes

Now, to see the magic of Nautilus Scripts.
Right click in a folder with a "notes" file or two within, go to Scripts and then click "Notes" and you'll magically see all of your "notes" files turn to "notes*.txt"

How much more mother friendly can you get? :P

4
  • Please see mywiki.wooledge.org/BashPitfalls. In particular, pitfall 1 and 2.
    – geirha
    Apr 1, 2011 at 21:57
  • No worries, completely looked over the white spaces by accident :S Thanks for the good read, really did notice my bad habits haha Added in changes to my answer to compensate; although, for some reason if the file is called just "notes", it won't change it. I have a feeling that it might be something to do with the wildcards, wanna shed some light on this geirha? :P Apr 4, 2011 at 14:07
  • If you run the script in your home directory and the destination directory is ~/notes then it will obviously fail on notes as moving a directory inside itself is not possible. You can use [[ $noteFile -ef $movePath ]] to test if two files are the same (see help test). A few more comments: 1) *"notes"*[^.txt] does not match what you think it matches. 2) In bash4, you can lowercase with parameter expansions, "${noteFile,,}". mywiki.wooledge.org/BashSheet#Parameter_Operations
    – geirha
    Apr 4, 2011 at 21:52
  • Man, you are a lifesaver. Also, that wiki is BASH gold :P Updated the code, didn't try too many crazy things. Threw in a directory check so that we don't get silly errors. Apr 5, 2011 at 0:05
2

This will handle filenames safely.

#!/bin/bash
shopt -s extglob  # see http://mywiki.wooledge.org/glob

for file in note!(*.txt); do
    mv -i "$file" "$HOME/notes/$file.txt"
done
mv -i note*.txt "$HOME/notes/"

If you want to case-insensitive match, also enable the nocaseglob shell option.

shopt -s extglob nocaseglob

EDIT: Another approach

#!/bin/bash
for file in note*; do
    # Make sure it's a regular file and not the destination directory
    [[ -f $file && ! $file -ef $HOME/notes ]] || continue
    mv -i "$file" "$HOME/notes/${file%.[Tt][Xx][Tt]}.txt"
done
7
  • @geirha... Your general idea is on track (I like the globbing), but it has some problems... 1) it doesn't handle filenames with embedded spaces... 2) you are moving the files twice!... 3) it tries to move directories and links named "note*"
    – Peter.O
    Apr 2, 2011 at 15:17
  • @fred.bear 1) It handles any filename, why do you think it doesn't? 2) It's not moving files twice... unless the current directory is "$HOME/notes/". 3) It does, yes. If that's wanted or not, the question doesn't say...
    – geirha
    Apr 2, 2011 at 15:58
  • @geirha: For 1) It failed to process files with embedded spaces: "note 1" "note 2" .... and for 2) the first mv moves the file out of the current dir, and the second mv is still current in the now "empty" original dir... And in either case, mvallows you to rename and move at the same time; why hit the directory routines twice? ... 3) the question does say... (the) purpose would be to rename all text files in...
    – Peter.O
    Apr 2, 2011 at 16:32
  • Here is the error message for (1): mv: cannot move "notes" to a subdirectory of itself, "/home/fred/notes/notes.txt" That particular file is actually named note 1.
    – Peter.O
    Apr 2, 2011 at 16:35
  • 2
    @fred.bear that error message is harmless, and not about a file named note 1...
    – geirha
    Apr 2, 2011 at 16:56
2

Updated version: as per helpful suggestions by geirha.

I got rid of the array, which was quite unnecessary,
and made changes to how and which globs are set/unset.
The original version is still included; (for comparison)

################################################
cd ~/ # create sample files with embedded spaces 
touch note\ {1..3}
################################################
#  
tdir="$HOME/notes"; # make target dir
[[ ! -d "$tdir" ]] && mkdir -p "$tdir"
#  
shopt -q nullglob; Xnullglob=$? # state of nullglob  
shopt -s nullglob                 # enable nullglob
shopt -s extglob                  # enable  extglob
#  
for f in note!(*.txt) ; do
  if [[ -f $f ]] ; then
     mv -i "$f" "$tdir/$f.txt"
  fi 
done
#  
((Xnullglob==1)) &&  shopt -u nullglob # Reset nullglob 
#

Original version (with extra cruft):

cd ~/ 
# create some sample files with embedded spaces
touch note\ {1..3}
#  
tdir="$HOME/notes";   # make target dir if not present
[[ ! -d "$tdir" ]] && mkdir -p "$tdir"
#  
state=($(shopt extglob)) # Save extended globbing state  
[[ ${state[1]} == off ]] &&  shopt -s extglob
#  
farray=( note!(*.txt) )  # Build an array of filenames
fcount=${#farray[@]}     # Get size of the array
#  
for ((findex=0; findex<fcount; findex++));do
  if [[ -f "${farray[findex]}" ]] ; then
     echo -e $findex "${farray[findex]}"    
     mv -i "${farray[findex]}" \
     "$tdir/${farray[findex]}.txt"
  fi 
done
#  
[[ ${state[1]} == off ]] &&  shopt -u extglob # Reset extglob 
#  
# `mv -i` will interactively check with you before overwiting and existing file.
#  You can use `mv -bf` to backup an existing file before overwriting it.
4
  • A couple of comments on this: 1) I don't see the point in setting and unsetting extglob. When the script starts it will be off. And since it won't affect regular globs, there's no point in turning it off at all. 2) For iterating the array I'd just use for file in "${farray[@]}";. Shorter and easier. 3) A thing we both forgot is to set nullglob to account for no files matching the pattern. 4) Lastly, your script doesn't move the files that DOES end with .txt.
    – geirha
    Apr 2, 2011 at 17:04
  • :) ..I thought you'd find some things. It's good. About unsetting extglob. I'd thought about it, but as bash scrpting is quite new to me (6 months), I just wanted to try some way of doing it. In longer script it may come in handy (some day) so this was a good place to practice:)... Re itterating the array.. Yes, I suppose that would work. I'm still trying to work our when and where bash-words lose their integrity and word-split.. I gusess that it would save a lot of individual array-element accesses... (thanks).. re "nullglob" I didn't forget :), but it I was on overload, so I ignored it.
    – Peter.O
    Apr 2, 2011 at 17:44
  • ...continued: Re the .txt extension, he does say "named e.g. "note*" (w/out extension)" ... but now I understand why your updated script actually catered for the extension to be included in the shell filename expansion.... btw.. Your new updated method looks great.. nice and simple and straight forward... and the .txt is easily tweaked either way.... It's very late here ..must go... bye :)
    – Peter.O
    Apr 2, 2011 at 17:52
  • bear Well, it doesn't make sense to disable extglob. nullglob, however, makes more sense to disable as soon as it's not needed anymore. nullglob will have "surprising" effects if you don't have full control of which expansions take place at which point. Note that shopt nullglob will return false if it's unset, and true if it's set. Much better to just test the return value, rather than string-comparison. See mywiki.wooledge.org/Arguments for a thorough explanation of word splitting.
    – geirha
    Apr 2, 2011 at 19:03
-1

for noteFile in note*[^.txt]; do mv $noteFile /home/username/notes/$noteFile.txt; done

I like that answer better :) The only drawback is that it only moves files it has renamed

7
  • Would it work with filenames that have white spaces?. At first sight it seems it does not.
    – HMM
    Mar 31, 2011 at 22:59
  • note*[^.txt] will match all files starting with note and not ending with a ., t or x. Also see mywiki.wooledge.org/BashPitfalls.
    – geirha
    Apr 1, 2011 at 22:01
  • you are correct! bash is kind of irritating. I've been trying other methods, like for my_file in $((ls note*.txt; ls note*) | sort | uniq -u); do mv $my_file /where/ever; done but that doesn't handle spaces correctly. :( There is an IFS variable you can mess with, but that is beyond my bash skills.
    – user1974
    Apr 2, 2011 at 16:41
  • @user1974 ls outputs a representation of files, not filenames, they just happen to be the same for simple cases (e.g. where there's no odd characters in the filenames). IFS will allow you to handle a few more cases, but it's by no means a solution. ls should really never be used in a script. See fred.bear and mine's answers for a glob that'll do what you want. Also, don't forget quotes.
    – geirha
    Apr 2, 2011 at 19:10
  • @user1974. Just a bit more about using "quoted filenames".. In both mine and geirha's examples, you will see the filename being used without quotes (but not everywhere). This is because the construct [[ ... ]] does not do any word-splitting, so with or without quotes is okay there .. Note that the older single-bracket version does not "protect" variables in this way, so you do need quotes there when whitespace may be involved... More double-brackets: ((...)) is for integer conditions ((x==1)) and assignment/calculation ((x+=1)); you don't need $ or whitespace with integers.
    – Peter.O
    Apr 3, 2011 at 10:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .