Not able to replace String from file while iterating - bash

The final.txt looks like:
build/create_changes.sh
build/create_changes.sh-meta.xml
src/aura/camping/camping.design
src/aura/camping/camping.design-meta.xml
I would like to replace only replace the files which contains /aura/ in its line to src/aura/camping
if [ -e final.txt ]
then
ARRAY=()
while read CFILE
do
echo Analyzing file `basename $CFILE`
case "$CFILE"
in
*.design) TYPENAME="AuraDefinitionBundle";;
*) TYPENAME="UNKNOWN";;
esac
if [ "$TYPENAME" == "AuraDefinitionBundle" ]
then
if [ $(contains "${ARRAY[#]}" $(basename -- "$(dirname -- "$CFILE")")) != "y" ]
then
echo baseFile from new method cFile $CFILE
CFILENAME="$CFILE"
replace="src/aura/"$(basename -- "$(dirname -- "$CFILE")")
echo checkChanges "${CFILENAME/$CFILENAME/"$replace"}"
CFILE="${CFILENAME/$CFILENAME/"$replace"}"
echo baseFile from after change method cFile $CFILE
else
continue
fi
fi
done < final.txt
else
echo Change file not found!
This works, so I can see this in the window now:
[exec] baseFile from new method cFile src/aura/camping/camping.design
[exec] checkChanges src/aura/camping
[exec] baseFile from after change method cFile src/aura/camping
But the file final.txt does not change:
build/create_changes.sh
build/create_changes.sh-meta.xml
src/aura/camping/camping.design
src/aura/camping/camping.design-meta.xml
I replaced it right there CFILE="${CFILENAME/$CFILENAME/"$replace"}"
Tried this too :
if [ $(contains "${ARRAY[#]}" $(basename -- "$(dirname -- "$CFILE")")) != "y" ]
then
CFILENAME="$CFILE"
ARRAY+=($(basename -- "$(dirname -- "$CFILE")"))
replace="src/aura/"$(basename -- "$(dirname -- "$CFILE")")
#CFILE="${CFILENAME/$CFILENAME/"$replace"}"
sed -i 's/$CFILENAME/$replace/' final.txt
else
continue
fi
Am I missing something more here?

Your attempt has been good for the most parts, but the part involving changing the file content is incorrect in both the attempts.
Problem 1
You were surprised that the below attempt did not replace the contents of the file. It won't.
CFILE="${CFILENAME/$CFILENAME/"$replace"}"
Because the above is a bash internal construct for string replacement. It just replaces the string contents stored in the variable CFILENAME and puts the result to CFILE. No file modification is done at all.
Judging from your inputs your CFILENAME input would be src/aura/camping/camping.design and the replace variable would be src/aura/camping. Doing the above would put the string src/aura/camping to the variable CFILE and not to the file pointed by the variable CFILE.
Problem 2
You seemed to have identified that sed would solve your problem, which is in the right track, but you seemed to have missed a couple of tricks.
You have variables defined in your sed search and replacement parts, but the problem is with the quotes. Variables in bash shell don't expand when single quoted but only with double-quotes.
The next problem is with the de-limiter string used in sed, which is by default /. But remember both your source and replacement strings have / present, so sed would not understand what is the original and replacement text. You need to define a de-limiter that is not / and also any meta-character that is not part of your string. I would recommend | in your case
sed "s|$CFILENAME|$replace|" final.txt
Now the biggest problem is with the -i flag in your sed command which means in-place editing of your files whiles. Remember you are reading the file in a loop with a while read construct line by line and now your editing this file after parsing each line. You are messing up with the shell re-direction in a wrong way here. The ideal way would be re-direct your line by line edits to a temporary file and move it to your original file once the loop is done.
sed "s|$CFILENAME|$replace|" final.txt >> temp_final.txt
Something like above using >> which appends to a file. And once the loop is finished, revert to your original file using mv
mv -- temp_final.txt final.txt

Related

Bash script MV is disappearing files

I've written a script to go through all the files in the directory the script is located in, identify if a file name contains a certain string and then modify the filename. When I run this script, the files that are supposed to be modified are disappearing. It appears my usage of the mv command is incorrect and the files are likely going to an unknown directory.
#!/bin/bash
string_contains="dummy_axial_y_position"
string_dontwant="dummy_axial_y_position_time"
file_extension=".csv"
for FILE in *
do
if [[ "$FILE" == *"$string_contains"* ]];then
if [[ "$FILE" != *"$string_dontwant"* ]];then
filename= echo $FILE | head -c 15
combined_name="$filename$file_extension"
echo $combined_name
mv $FILE $combined_name
echo $FILE
fi
fi
done
I've done my best to go through the possible errors I've made in the MV command but I haven't had any success so far.
There are a couple of problems and several places where your script can be improved.
filename= echo $FILE | head -c 15
This pipeline runs echo $FILE adding the variable filename having the null string as value in its environment. This value of the variable is visible only to the echo command, the variable is not set in the current shell. echo does not care about it anyway.
You probably want to capture the output of echo $FILE | head -c 15 into the variable filename but this is not the way to do it.
You need to use command substitution for this purpose:
filename=$(echo $FILE | head -c 15)
head -c outputs only the first 15 characters of the input file (they can be on multiple lines but this does not happen here). head is not the most appropriate way for this. Use cut -c-15 instead.
But for what you need (extract the first 15 characters of the value stored in the variable $FILE), there is a much simpler way; use a form of parameter expansion called "substring expansion":
filename=${FILE:0:15}
mv $FILE $combined_name
Before running mv, the variables $FILE and $combined_name are expanded (it is called "parameter expansion"). This means that the variable are replaced by their values.
For example, if the value of FILE is abc def and the value of combined_name is mnp opq, the line above becomes:
mv abc def mnp opq
The mv command receives 4 arguments and it attempts to move the files denoted by the first three arguments into the directory denoted by the fourth argument (and it probably fails).
In order to keep the values of the variables as single words (if they contain spaces), always enclose them in double quotes. The correct command is:
mv "$FILE" "$combined_name"
This way, in the example above, the command becomes:
mv "abc def" "mnp opq"
... and mv is invoked with two arguments: abc def and mnp opq.
combined_name="$filename$file_extension"
There isn't any problem in this line. The quotes are simply not needed.
The variables filename and file_extension are expanded (replaced by their values) but on assignments word splitting is not applied. The value resulted after the replacement is the value assigned to variable combined_name, even if it contains spaces or other word separator characters (spaces, tabs, newlines).
The quotes are also not needed here because the values do not contain spaces or other characters that are special in the command line. They must be quoted if they contain such characters.
string_contains="dummy_axial_y_position"
string_dontwant="dummy_axial_y_position_time"
file_extension=".csv"
It is not not incorrect to quote the values though.
for FILE in *
do
if [[ "$FILE" == *"$string_contains"* ]];then
if [[ "$FILE" != *"$string_dontwant"* ]]; then
This is also not wrong but it is inefficient.
You can use the expression from the if condition directly in the for statement (and get rid of the if statement):
for FILE in *"$string_contains"*; do
if [[ "$FILE" != *"$string_dontwant"* ]]; then
...
If you have read and understood the above (and some of the linked documentation) you will be able to figure out yourself where were your files moved :-)

Rename file to a hidden file for loop

I'm writing a fairly basic shell script that loops through files within a directory and renames the file and adds a dot(.) to the start of the file however it does not work
any insight on whats going wrong?
for file in /tmp/test/*; do
mv $file \\.$file;
done
There are two problems.
You're putting the dot before the whole pathname, not just the filename part.
You're prefixing the filename with \. instead of just .. There's no need for \\ in the mv command.
Corrected code:
for file in /tmp/test/*; do
mv "$file" "${file%/*}/.${file##*/}";
done
${file%/*} returns the value of $file with everything starting from the last / removed, which is the directory part of the pathname. ${file##*/}" returns the value of $file with everything up to the last / removed, which is the filename part. Then it puts them back together with /. between them, which adds the . prefix that you want to the filename part. See Bash parameter expansion documentation for details of these operators.
Also, remember to quote variables so you don't get errors when the variable contains whitespace.
This is a simple script that takes a directory argument:
hide_files.sh:
if [ $# -ne 1 ] || [ ! -d $1 ]; then
echo 'invalid dir arg.'
exit 1
fi
for f in $(ls $1); do
mv -v "$1/$f" "$1/.$f"
done
output:
$ bash hide_files.sh mydir
mydir/a -> mydir/.a
mydir/c -> mydir/.c

Checking if substring is in filename in bash

I'm trying to create a script that identifies the names of files in a directory and then checks to see if a string is a substring of the name. I'm doing this in bash and cannot use the grep command. Any thoughts?
I have the following code to check if a user submission matches a file name or a string in the name.
read -p name
for file in sample/*; do
echo $(basename "$file")
if [[$(basename "$file") ~= $name]];
then echo "invalid"
fi
done
You can just interpolate the user input into the wildcard.
printf '%s\n' sample/*"$name"*
If you want to loop over the matches, try
for file in sample/*"$name"*; do
# cope with nullglob
test -e "$file" || break
: do things with "$file"
done
If you just need to check that the name isn't a substring of an existing file's name:
valid=true
for file in sample/*"$name"*; do
test -e "$file" && valid=false
done
echo "$name is valid? $valid"
The shell by default does not expand a wildcard which doesn't match any files; so in this case, your loop will run once, but the loop variable will not match any existing file. You might also want to look at the nullglob option in Bash to make it loop zero times in this case.

Basename puts single quotes around variable

I am writing a simple shell script to make automated backups, and I am trying to use basename to create a list of directories and them parse this list to get the first and the last directory from the list.
The problem is: when I use basename in the terminal, all goes fine and it gives me the list exactly as I want it. For example:
basename -a /var/*/
gives me a list of all the directories inside /var without the / in the end of the name, one per line.
BUT, when I use it inside a script and pass a variable to basename, it puts single quotes around the variable:
while read line; do
dir_name=$(echo $line)
basename -a $dir_name/*/ > dir_list.tmp
done < file_with_list.txt
When running with +x:
+ basename -a '/Volumes/OUTROS/backup/test/*/'
and, therefore, the result is not what I need.
Now, I know there must be a thousand ways to go around the basename problem, but then I'd learn nothing, right? ;)
How to get rid of the single quotes?
And if my directory name has spaces in it?
If your directory name could include spaces, you need to quote the value of dir_name (which is a good idea for any variable expansion, whether you expect spaces or not).
while read line; do
dir_name=$line
basename -a "$dir_name"/*/ > dir_list.tmp
done < file_with_list.txt
(As jordanm points out, you don't need to quote the RHS of a variable assignment.)
Assuming your goal is to populate dir_list.tmp with a list of directories found under each directory listed in file_with_list.txt, this might do.
#!/bin/bash
inputfile=file_with_list.txt
outputfile=dir_list.tmp
rm -f "$outputfile" # the -f makes rm fail silently if file does not exist
while read line; do
# basic syntax checking
if [[ ! ${line} =~ ^/[a-z][a-z0-9/-]*$ ]]; then
continue
fi
# collect targets using globbing
for target in "$line"/*; do
if [[ -d "$target" ]]; then
printf "%s\n" "$target" >> $outputfile
fi
done
done < $inputfile
As you develop whatever tool will process your dir_list.tmp file, be careful of special characters (including spaces) in that file.
Note that I'm using printf instead of echo so that targets whose first character is a hyphen won't cause errors.
This might work
while read; do
find "$REPLY" >> dir_list.tmp
done < file_with_list.txt

Renaming a file extension without specifying

I am creating a bash shell script that will rename a file extension without having to specify the old file extension name. If I enter "change foo *" to the Terminal in Linux, it will change all file extension to foo.
So lets say I've got four files: "file1.txt", "file2.txt.txt", "file3.txt.txt.txt" and "file4."
When I run the command, the files should look like this: "file1.foo", "file2.txt.foo", "file3.txt.txt.foo" and "file4.foo"
Can someone look at my code and correct it. I would also appreciate it if someone can implement this for me.
#!/bin/bash
shift
ext=$1
for file in "$#"
do
cut=`echo $FILE |sed -n '/^[a-Z0-9]*\./p'`
if test "${cut}X" == 'X'; then
new="$file.$ext"
else
new=`echo $file | sed "s/\(.*\)\..*/\1.$ext/"`
fi
mv $file $new
done
exit
Always use double quotes around variable substitutions, e.g. echo "$FILE" and not echo $FILE. Without double quotes, the shell expands whitespace and glob characters (\[*?) in the value of the variable. (There are cases where you don't need the quotes, and sometimes you do want word splitting, but that's for a future lesson.)
I'm not sure what you're trying to do with sed, but whatever it is, I'm sure it's doable in the shell.
To check if $FILE contains a dot: case "$FILE" in *.*) echo yes;; *) echo no;; esac
To strip the last extension from $FILE: ${FILE%.*}. For example, if $FILE is file1.txt.foo, this produces file1.txt. More generally, ${FILE%SOME_PATTERN} expands to $FILE with a the shortest suffix matching SOME_PATTERN stripped off. If there is no matching suffix, it expands to $FILE unchanged. The variant ${FILE%%SOME_PATTERN} strips the longest suffix. Similarly, ${FILE#SOME_PATTERN} and ${FILE##SOME_PATTERN} strip a suffix.
test "${TEMP}X" == 'X' is weird. This looks like a misremembered trick from the old days. The normal way of writing this is [ "$TEMP" = "" ] or [ -z "$TEMP" ]. Using == instead of = is a bash extension. There used to be buggy shells that might parse the command incorrectly if $TEMP looked like an operator, but these have gone the way of the dinosaur, and even then, the X needs to be at the beginning, because the problematic operators begin with a -: [ "X$TEMP" == "X" ].
If a file name begins with a -, mv will think it's an option. Use -- to say “that's it, no more options, whatever follows is an operand”: mv -- "$FILE" "$NEW_FILE".
This is very minor, but a common (not universal) convention is to use capital letters for environment variables and lowercase letters for internal script variables.
Since you're using only standard shell features, you can start the script with #!/bin/sh (but #!/bin/bash works too, of course).
exit at the end of the script is useless.
Applying all of these, here's the resulting script.
#!/bin/sh
ext="$1"; shift
for file in "$#"; do
base="${file%.*}"
mv -- "$file" "$base.$ext"
done
Not exactly what you are asking about, but have a look at the perl rename utility. Very powerful! man rename is a good start.
Use: for file in *.gif; do mv $file ${file%.gif}.jpg; done
Or see How to rename multiple files
For me this worked
for FILE in `ls`
do
NEW_FILE=${FILE%.*}
NEW_FILE=${NEW_FILE}${EXT}
done
I just want to tell about NEW_FILE=${FILE%.*}.
Here NEW_FILE gets the file name as output. You can use it as you want.
I tested in bash with uname -a = "Linux 2.4.20-8smp #1 SMP Thu Mar 13 17:45:54 EST 2003 i686 i686 i386 GNU/Linux"

Resources