bash script to replace the name of a zip file - linux

I am very new in programing scripts-.
I have a lot of zip files in a directory. I want to extract them replacing the name of the inside file by the zip file, with the correct extension. Error reporting if there is more than one file, excep if is "remora.txt" inside.
The file "remora.txt" was an ini file for the zip, and I wont use it any more, but is in a lot of my zip files.
Example 1.
ZIp file: maths.zip,
Inside it has:
- "tutorial in maths.doc"
- "remora.txt"
Action:
So the script should erase or deprease "remora.txt" and extract "tutorial in maths.doc" under the name maths.doc
Example 2.
ZIp file: geo.zip,
Inside it has:
- "excersices for geometry.doc"
- "geometry.doc"
- "remora.txt"item
Action:
It should out put "I found more than a file in geo.zip"
I am
Using linux, ubuntu 12
I have done this script, but is not working.
#!/bin/bash
#
# Linux Shell Scripting Tutorial 1.05r3, Summer-2002
#
for archive in *.zip # First I read the zip file
do
((i++))
unzip -Z1 $archive | while read line; # I read all the files in the ZIP
do
line=( ${line//,/ } )
inside[$a]=("${line[#]}") # Here I assigne the name of the file to an array
((a++))
done
If ( $a > 2) then
echo " Too much files in file $archive "
fi
If ($a <= 2)
then
if (inside[0]!= "remora.txt")
then unzip -p $archive > $(printf "%s" $archive).doc
fi
if (inside[1]!= "remora.txt")
then unzip -p $archive > $(printf "%s" $archive).doc
fi
fi
done

Try writing scripts incrementally. Instead of writing 20 statements and then trying to debug them all at once, write one statement at a time and test to make sure it works before writing the next one.
If you run e.g.
If ( $a > 2) then
echo " Too much files in file $archive "
fi
by itself, you'll see that it doesn't work. You then know more specifically what the problem is, and you can look up something like "bash if variable greater than" on Google or Stackoverflow.
Check out the bash tag wiki for more helpful tips on debugging and asking about code.
Things you'll find includes:
if has to be lower case
You need line feed or semicolon before then
To see if a variable is greater than, use [[ $a -gt 2 ]].
To see if an array element does not equal, use [[ ${inside[0]} != "remora.txt" ]]
Pipelines cause subshells. Use while read ...; do ...; done < <(somecommand) instead.

Related

Is there a way to pass multiple values into a CSV file, based on the output of a linux script

I have written a small script that will take the users input and then generate the md5sum values for it
count = 0
echo "Enter number of records"
read number
while [ $count -le $number ]
do
echo "Enter path"
read path
echo "file name"
read file_name
md5sum $path"/"$filename #it shows the md5sum value and path+filename
((count++))
done
How can I pass these values ( path,file name, and md5sums ) to CSV file. ( assuming the user chooses to enter more than 1 record)
The output should be like
/c/training,sample.txt,34234435345346549862123454651324 #placeholder values
/c/file,text.sh,4534534534534534345345435342342
Interactively prompting for the number of files to process is just obnoxious. Change the script so it accepts the files you want to process as command-line arguments.
#!/bin/sh
md5sum "$#" |
sed 's%^\([0-9a-f]*\) \(\(.*\)/\)?\([^/]*\)$%\3,\4,\1%'
There are no Bash-only constructs here, so I switched the shebang to /bin/sh; obviously, you are still free to use Bash if you like.
There is a reason md5sum prints the checksum before the path name. The reordered output will be ambiguous if you have file names which contain commas (or newlines, for that matter). Using CSV format is actually probably something you should avoid if you can; Unix tools generally work better with simpler formats like tab-delimited (which of course also breaks if you have file names with tabs in them).
Rather than prompting the user for both a path to a directory and the name of a file in that directory, you could prompt for a full path to the file. You can then extract what you need from that path using bash string manipulations.
#!/bin/bash
set -euo pipefail
function calc_md5() {
local path="${1}"
if [[ -f "${path}" ]] ; then
echo "${path%/*}, ${path##*/}, $(md5sum ${path} | awk '{ print $1 }')"
else
echo "
x - Script requires path to file.
Usage: $0 /path/to/file.txt
"
exit 1
fi
}
calc_md5 "$#"
Usage example:
$ ./script.sh /tmp/test/foo.txt
/tmp/test, foo.txt, b05403212c66bdc8ccc597fedf6cd5fe

catching user input to run scripts

I am trying to write a small bash script using mac OS TextEdit that will catch a user input in terminal and based on the file type stipulated in the command line albeit (jpg or gif) iterate through a directory on my desktop named dir1, pull all files of that filetype and place such in a new directory called dir2
i.e The user types jpg into the terminal and the script kicks into life and pulls all of the jpg files situated in dir1 and places such in dir2
What is the leanest and least convoluted way of achieving this conscious that I am new to shell scripting.
I am about to reach for the meds. What can I do to the below code to get it to work.
#!/bin/bash
echo “Good Morning, Please enter your file type for sorting [ENTER]:”
read $FILE
if [[ $file == *.jpg ]]; then
mv ~/DIR1/*jpg* ~/Users/christopherdorman/desktop/dir2/
echo “your files have been successfully processed”
fi
There are a couple confusions here regarding bash variables and syntax. You need to use fi to close your if statement instead of done. Also, you need to capitalize the variable in your if statement, since bash is case sensitive. I believe this is what you are looking for (assuming your input is "jpg" or "gif"):
#!/bin/bash
echo “Good Morning, Please enter your file type for sorting [ENTER]:”
read FILE
if [[ $FILE == "jpg" ]]; then
mv ~/DIR1/*jpg* ~/Users/christopherdorman/desktop/dir2/
echo “your files have been successfully processed”
fi

Bash write ls error to file

I have a folder with lots of files named with a continuing number and some text, but some numbers are missing. I want to write all missing numbers into a file.
Here is what I got so far:
#!/bin/bash
for (( c=23457; c<=24913; c++ ))
do
files=$(printf %q kassensystem/documents/"${c}")
ret=$(ls $files*)
echo "$ret" >> ./out.log
done
The output looks like that:
all existing files are written into file, all errors into console. I want exactly the other way. All errors (ls: ..file not found) written into the file!
I tried to use the complete command ls $files* | grep -v 'kasse*', but then I only get a file with empty lines.
Thanks for your help!
exec 4>out.log # open output file just once, not once per write
for (( c=23457; c<=24913; c++ )); do
files=( kassensystem/documents/"$c"* ) # glob into an array
[[ -e $files ]] || echo "$c" >&4 # log if first file in array doesn't exist
done

Delete files in one directory that do not exist in another directory or its child directories

I am still a newbie in shell scripting and trying to come up with a simple code. Could anyone give me some direction here. Here is what I need.
Files in path 1: /tmp
100abcd
200efgh
300ijkl
Files in path2: /home/storage
backupfile_100abcd_str1
backupfile_100abcd_str2
backupfile_200efgh_str1
backupfile_200efgh_str2
backupfile_200efgh_str3
Now I need to delete file 300ijkl in /tmp as the corresponding backup file is not present in /home/storage. The /tmp file contains more than 300 files. I need to delete the files in /tmp for which the corresponding backup files are not present and the file names in /tmp will match file names in /home/storage or directories under /home/storage.
Appreciate your time and response.
You can also approach the deletion using grep as well. You can loop though the files in /tmp checking with ls piped to grep, and deleting if there is not a match:
#!/bin/bash
[ -z "$1" -o -z "$2" ] && { ## validate input
printf "error: insufficient input. Usage: %s tmpfiles storage\n" ${0//*\//}
exit 1
}
for i in "$1"/*; do
fn=${i##*/} ## strip path, leaving filename only
## if file in backup matches filename, skip rest of loop
ls "${2}"* | grep -q "$fn" &>/dev/null && continue
printf "removing %s\n" "$i"
# rm "$i" ## remove file
done
Note: the actual removal is commented out above, test and insure there are no unintended consequences before preforming the actual delete. Call it passing the path to tmp (without trailing /) as the first argument and with /home/storage as the second argument:
$ bash scriptname /path/to/tmp /home/storage
You can solve this by
making a list of the files in /home/storage
testing each filename in /tmp to see if it is in the list from /home/storage
Given the linux+shell tags, one might use bash:
make the list of files from /home/storage an associative array
make the subscript of the array the filename
Here is a sample script to illustrate ($1 and $2 are the parameters to pass to the script, i.e., /home/storage and /tmp):
#!/bin/bash
declare -A InTarget
while read path
do
name=${path##*/}
InTarget[$name]=$path
done < <(find $1 -type f)
while read path
do
name=${path##*/}
[[ -z ${InTarget[$name]} ]] && rm -f $path
done < <(find $2 -type f)
It uses two interesting shell features:
name=${path##*/} is a POSIX shell feature which allows the script to perform the basename function without an extra process (per filename). That makes the script faster.
done < <(find $2 -type f) is a bash feature which lets the script read the list of filenames from find without making the assignments to the array run in a subprocess. Here the reason for using the feature is that if the array is updated in a subprocess, it would have no effect on the array value in the script which is passed to the second loop.
For related discussion:
Extract File Basename Without Path and Extension in Bash
Bash Script: While-Loop Subshell Dilemma
I spent some really nice time on this today because I needed to delete files which have same name but different extensions, so if anyone is looking for a quick implementation, here you go:
#!/bin/bash
# We need some reference to files which we want to keep and not delete,
 # let's assume you want to keep files in first folder with jpeg, so you
# need to map it into the desired file extension first.
FILES_TO_KEEP=`ls -1 ${2} | sed 's/\.pdf$/.jpeg/g'`
#iterate through files in first argument path
for file in ${1}/*; do
# In my case, I did not want to do anything with directories, so let's continue cycle when hitting one.
if [[ -d $file ]]; then
continue
fi
# let's omit path from the iterated file with baseline so we can compare it to the files we want to keep
NAME_WITHOUT_PATH=`basename $file`
 # I use mac which is equal to having poor quality clts
# when it comes to operating with strings,
# this should be safe check to see if FILES_TO_KEEP contain NAME_WITHOUT_PATH
if [[ $FILES_TO_KEEP == *"$NAME_WITHOUT_PATH"* ]];then
echo "Not deleting: $NAME_WITHOUT_PATH"
else
# If it does not contain file from the other directory, remove it.
echo "deleting: $NAME_WITHOUT_PATH"
rm -rf $file
fi
done
Usage: sh deleteDifferentFiles.sh path/from/where path/source/of/truth

at command force script not to read from text file

I have a script with the name bckp2 and a text file with the name variables.txt. When I am running the script like that in the terminal ./bckp2 it works exactly as I want, but when I try to run it with the at like this at -f bckp2 19:30 it doesn't work, it doesn't do the same thing. With the at command it seems like it doesn't read from the file and I have no idea why.
script bckp2 Reads 3 words from the text file variables.txt and is using them as source and destination for tar command. Just ignore the first word it's for testing
let count=0
while read line; do
for word in $line; do
if [ $count -eq 0 ]
then
username=$word
elif [ $count -eq 1 ]
then
source=$word
elif [ $count -eq 2 ]
then
destination=$word
fi
let count=count+1
done
done < variables.txt
echo $destination $source > test.txt
tar -cvf $destination.tar $source
Text file variables.txt
valkon fake faketar
In the script bckp2 I am saving the file content into variables, only to see if the at works or not, and it doesn't. It's writing to file blank text, so I assume that the script doesn't read from file at all. But as I told you, when I am running it like ./bckp2 it works.
at and batch default to interpreting commands with /bin/sh, not /bin/bash. If you want to use a different shell, you need to format your script appropriately, by adding #!/bin/bash as the first line of the script.
Also, your unnecessarily complicated loop for reading variables could be simply replaced with
read username source destination < variables.txt

Resources