I'm writing a shell script to make a new save file for the game risk of rain. I'm trying to make it so that each section of the unlocks that are available, such as achievements, monster logs, artifacts, etc. can either be added all at once to the file, or the user will be able to pick which ones they want added. I am new to writing shell scripts and I am not quite sure why I am getting the error syntax error near unexpected token `}' when I run what I have so far of my script. If anyone could explain to me as to why I am getting the error, how to fix it, and/or how to improve my script, it would be greatly appreciated. Also I am on Mac OS X if that matters. Here is my script.
#!/bin/bash
mkdir ~/Desktop/ror_save_unlock_all
echo "This script takes ~/Library/Application Support/com.riskofrain.riskofrain/Save.ini and backs it up to ~/Desktop/ror_save_unlock_all/originalSave. It generates a new Save.ini that is built to your specifications, unlocking all items and achievements or just the ones you choose, and replaces the existing Save.ini with the new one." >> ~/Desktop/ror_save_unlock_all/README.txt
mkdir ~/Desktop/ror_save_unlock_all/originalSave
cp ~/Library/Application\ Support/com.riskofrain.riskofrain/Save.ini ~/Desktop/ror_save_unlock_all/originalSave/
cd ~/Desktop/ror_save_unlock_all
echo -n 'How would you like the new Save.ini to be built? Press 1 for all unlocks or 2 to customize.'
read text
if [ $text = "1" ]
then
{
echo "[Achievement]" >> EOF1
count=1
while [ $count -lt 55 ]
do
echo "acheivement${count}=\"2\"" >> EOF2
count=`expr $count + 1`
done
echo "[Record]" >> EOF3
count=1
while [ $count -lt 31 ]
do
echo "mons${count}=\"1\"" >> EOF4
count=`expr $count + 1`
done
count=1
while [ $count -lt 110 ]
do
echo "item${count}=\"1\"" >> EOF5
count=`expr $count + 1`
done
count=1
while [ $count -lt 10 ]
do
echo "artifact${count}=\"1\"" >> EOF6
count=`expr $count + 1`
done
cat EOF1 EOF2 EOF3 EOF4 EOF5 EOF6 > Save.ini
rm EOF1 EOF2 EOF3 EOF4 EOF5 EOF6
cp -force ~/Desktop/ror_save_unlock_all/Save.ini ~/Library/Application\ Support/com.riskofrain.riskofrain/Save.ini
echo "Original Save.ini successfully overwritten."
exit
}
elif [ $text = "2" ]; then
{
echo "You selected customize. Now we will build a custom Save.ini"
echo -n "Press 1 if you want to unlock all achievements, press 2 to continue without unlocking all achievements."
read text
if [ $text = "1" ]; then
{
echo "[Achievement]" >> EOF1
count=1
while [ $count -lt 55 ]
do
echo "acheivement${count}=\"2\"" >> EOF2
count=`expr $count + 1`
done
echo "All achievements successfully unlocked."
echo -n "Press 1 to make the Save.ini and replace the existing one with it and then exit, or press 2 to customize it further."
read text
if [ $text = "1" ]; then
{
cat EOF1 EOF2 > Save.ini
rm EOF1 EOF2
cp -force ~/Desktop/ror_save_unlock_all/Save.ini ~/Library/Application\ Support/com.riskofrain.riskofrain/Save.ini
echo "Original Save.ini successfully overwritten."
exit
}
elif [ $text = "2" ] then;
{
echo -n "Press 1 to unlock all monster logs, or press 2 to continue without unlocking all monster logs."
read text
if [ $text = "1" ]; then
{
echo "[Record]" >> EOF3
count=1
while [ $count -lt 31 ]
do
echo "mons${count}=\"1\"" >> EOF4
count=`expr $count + 1`
done
echo "All achievements successfully unlocked."
echo -n "Press 1 to make the Save.ini and replace the existing one with it and then exit, or press 2 to customize it further."
read text
}
}
}
}
Your script has some error:
- Line 93: elif [ $text = "2" ] then; should change to elif [ $text = "2" ]; then
- If command in bash must have fi to close a condition. You can refer to http://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO-6.html
Hope this help
Before deciding to write a script, you should have a basic understanding of the language in which you're writing. Even the most cursory reading of the documentation or any how-to pages would have told you the correct syntax for an if statement.
Beyond that, you have a number of inefficiencies in your script. The backtick operator starts a subshell, a new instance of bash, so this is not necessarily something you want to be doing 110 times in a loop.
Here are a couple of your bigger problems fixed. I'd suggest looking into functions to eliminate a lot of code repetition and conditional nesting.
#!/bin/bash
mkdir ~/Desktop/ror_save_unlock_all
echo "This script takes ~/Library/Application Support/com.riskofrain.riskofrain/Save.ini and backs it up to ~/Desktop/ror_save_unlock_all/originalSave. It generates a new Save.ini that is built to your specifications, unlocking all items and achievements or just the ones you choose, and replaces the existing Save.ini with the new one." >> ~/Desktop/ror_save_unlock_all/README.txt
mkdir ~/Desktop/ror_save_unlock_all/originalSave
cp ~/Library/Application\ Support/com.riskofrain.riskofrain/Save.ini ~/Desktop/ror_save_unlock_all/originalSave/
cd ~/Desktop/ror_save_unlock_all
read -n 1 -p 'How would you like the new Save.ini to be built? Press 1 for all unlocks or 2 to customize.' text
if [ $text = "1" ]
then
echo "[Achievement]" > Save.ini
for count in {1..54}; do
echo "acheivement${count}=\"2\"" >> Save.ini
done
echo "[Record]" >> Save.ini
for count in {1..30}; do
echo "mons${count}=\"1\"" >> Save.ini
done
for count in {1..109}; do
echo "item${count}=\"1\"" >> Save.ini
done
for count in {1..9}; do
echo "artifact${count}=\"1\"" >> Save.ini
done
cp -force ~/Desktop/ror_save_unlock_all/Save.ini ~/Library/Application\ Support/com.riskofrain.riskofrain/Save.ini
echo "Original Save.ini successfully overwritten."
exit
elif [ $text = "2" ]; then
echo "You selected customize. Now we will build a custom Save.ini"
read -n 1 -p "Press 1 if you want to unlock all achievements, press 2 to continue without unlocking all achievements." text
if [ $text = "1" ]; then
echo "[Achievement]" > Save.ini
for count in {1..54}; do
echo "acheivement${count}=\"2\"" >> Save.ini
done
echo "All achievements successfully unlocked."
read -n 1 -p "Press 1 to make the Save.ini and replace the existing one with it and then exit, or press 2 to customize it further." text
if [ $text = "1" ]; then
cp -force ~/Desktop/ror_save_unlock_all/Save.ini ~/Library/Application\ Support/com.riskofrain.riskofrain/Save.ini
echo "Original Save.ini successfully overwritten."
exit
elif [ $text = "2" ]; then
read -n 1 -p "Press 1 to unlock all monster logs, or press 2 to continue without unlocking all monster logs." text
if [ $text = "1" ]; then
echo "[Record]" >> Save.ini
for count in {1..30}; do
echo "mons${count}=\"1\"" >> Save.ini
done
echo "All monster logs successfully unlocked."
read -n 1 -p "Press 1 to unlock all monster logs, or press 2 to continue without unlocking all monster logs." text
if [ $text = "1" ]; then
#...
fi
fi
fi
fi
fi
Related
Trying to do a loop with this script below. It works if I break it by deleting Google Chrome from Applications. If I put Chrome back into place it continuously kills the dock and says No matching processes belonging to you were found. Do I have to add something to killall Dock to exit the script or is Done in the wrong spot? Tried multiple things without any luck. Eventually want it to try every 15 minutes until all apps are in Applications and kill the dock so the shortcuts show instead of Question marks. This will happen once all apps get installed and Dock gets restarted.
i=0
while [ $i -lt 4 ]
do
if [[ -d "/Applications/Company Portal.app" && -d "/Applications/Google Chrome.app" ]];
then
killall Dock
else
i=$((i + 1)) | echo "Will tray again...."
sleep 10
fi
done
Update Here is what I eventually came up with, yea messy! But works! Will have to look at it more after see the responses here though.
echo $(date)
# Check to see if script has already run on computer before, if so, then exit.
file=/Users/Shared/.If_Installed_Restart_Dock_Has_Run.txt
if [ -e "$file" ]; then
echo "The If_Installed_Restart_Dock.sh script has run before and exiting..." && exit 0
else
touch /Users/Shared/.If_Installed_Restart_Dock_Has_Run.txt
fi
i=0
while [ $i -lt 6 ]
do
if [[ -d "/Applications/Google Chrome.app" && -d "/Applications/Microsoft Edge.app" ]];
then
killall Dock && exit 0
else
echo "Applications still need installed in order to restart Dock, will check again in 10 minutes for up to an hour, intune will try again in 8hrs..."
fi
i=$((i + 1))
sleep 600
done
Chrome is irrelevant. You're falling victim to the classic blunder. Consider:
#!/bin/bash
i=0
while [ $i -lt 4 ]; do
if echo "in first if, i = $i"; false ; then
echo bar
else
echo "in else, i = $i"
i=$((i + 1)) | echo "Will tray again...." # (1)
echo "after echo i = $i"
i=$((i + 1))
echo "after 2nd echo i = $i"
fi
done
In the above code, the i=$((i + 1)) in line (1) does not increment the variable used in the control loop. Since that command is in a pipe, it is executed in a subshell, and the variable i in the main shell is not incremented. It would probably be better to structure your code as:
#!/bin/sh
i=0
while [ $((i++)) -lt 4 ]; do
if [ -d "/Applications/Company Portal.app" ] && [ -d "/Applications/Google Chrome.app" ]; then
killall Dock
else
echo "Will tray again...."
sleep 10
fi
done
or
#!/bin/bash
for (( i = 0; i < 4; i++ )); do
if [ -d "/Applications/Company Portal.app" ] && [ -d "/Applications/Google Chrome.app" ]; then
killall Dock
else
echo "Will tray again...."
sleep 10
fi
done
I am new to shell, and my code takes two arguments from the user. I would like to confirm their arguments before running the rest of the code. I would like a y for yes to prompt the code, and if they type n for no, then the code will ask again for new arguments
Pretty much, if i type anything when I am asked to confirm, the rest of the code runs anyways. I tried inserting the rest of the code after the first then statement, but that didn't work either. I have also checked my code with ShellCheck and it all appears to be legal syntax. Any advice?
#!/bin/bash
#user passes two arguments
echo "Enter source file name, and the number of copies: "
read -p "Your file name is $1 and the number of copies is $2. Press Y for yes N for no " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
echo "cloning files...."
fi
#----------------------------------------REST OF CODE
DIR="."
function list_files()
{
if ! test -d "$1"
then echo "$1"; return;
fi
cd ... || $1
echo; echo "$(pwd)":; #Display Directory name
for i in *
do
if test -d "$i" #if dictionary
then
list_files "$i" #recursively list files
cd ..
else
echo "$i"; #Display File name
fi
done
}
if [ $# -eq 0 ]
then list_files .
exit 0
fi
for i in "$#*"
do
DIR=$1
list_files "$DIR"
shift 1 #To read next directory/file name
done
if [ ! -f "$1" ]
then
echo "File $1 does not exist"
exit 1
fi
for ((i=0; i<$2; i++))
do
cp "$1" "$1$i.txt"; #copies the file i amount of times, and creates new files with names that increment by 1
done
status=$?
if [ "$status" -eq 0 ]
then
echo 'File copied succeaful'
else
echo 'Problem copying'
fi
Moving the prompts into a while loop might help here. The loop will re-prompt for the values until the user confirms them. Upon confirmation, the target code will be executed and the break statement will terminate the loop.
while :
do
echo "Enter source file name:"
read source_file
echo "Number of copies"
read number_of_copies
echo "Your file name is $source_file and the number of copies is $number_of_copies."
read -p "Press Y for yes N for no " -n 1 -r
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "cloning files...."
break ### <<<---- terminate the loop
fi
echo ""
done
#----------------------------------------REST OF CODE
I have a homework using for loop but I'm not quite understand the task that I have to do in there. I wrote a script but I feel like it's not a correct script. Please help!
Here is the question:
Write a shell script to list out the contents of any directory, and indicate for each file (including invisible ones) whether the file is a directory, a plain file, and whether it is public and/or executable to this process
#!/bin/bash
if [ $# -lt 1 ] ; then
echo " file doesn't exist"
echo
echo " variable needed to run a command"
fi
echo ---------------------------------------------
echo ---------------------------------------------
for i in $*
do
if [ -f $i ]; then
echo " it's a file";
echo "THIS IS A LIST OF FILE and DIRECTORY in $i"
ls -a $i
fi
done
echo -----------------------------------------
if [ -d $i ]; then
echo "directory" ;
echo "THIS IS A LIST OF FILES AND DIRETORY in $i"
ls -a $i
fi
echo ------------------------------------------
if [ -x $i ]; then
echo "executable"
echo "THIS IS A LIST OF EXECUTABLE FILE IN $i"
ls -x $i
fi
echo -----------------------------------------
if [ -r $i ]; then
echo "this file is a public file"
else "this is a private file"
fi
#!/bin/bash
if [ $# -lt 1 ] ; then
echo " file doesn't exist"
echo
echo " variable needed to run a command"
fi
echo ---------------------------------------------
echo ---------------------------------------------
for i in $*
do
if [ -f $i ]; then
echo " it's a file";
echo "THIS IS A LIST OF FILE and DIRECTORY in $i"
ls -a $i
fi
done
echo -----------------------------------------
if [ -d $i ]; then
echo "directory" ;
echo "THIS IS A LIST OF FILES AND DIRETORY in $i"
ls -a $i
fi
echo ------------------------------------------
if [ -x $i ]; then
echo "executable"
echo "THIS IS A LIST OF EXECUTABLE FILE IN $i"
ls -x $i
fi
echo -----------------------------------------
if [ -r $i ]; then
echo "this file is a public file"
else "this is a private file"
fi
Poorly written specifications are the bane of education. "Public" sounds like the wrong word here. I'll assume it means "readable".
You check if there's an argument, but you don't exit the program if there is not. I'd also confirm it's a directory, and readable.
The manual will do you a lot of good. Expect to do a lot of reading till you learn this stuff, and then reference it a lot to be sure.
Read this section carefully, create some tests for yourself to prove they work and that you understand them, and your job will be more than half done.
Don't use [. Generally it's just better to always use [[ instead, unless you are using (( or case or some other construct.
I don't see that a for loop was specified, but it ought to be fine. Just be aware that you might have to specify $1/* and $1/.* separately.
Put all your tests in one loop, though. For each file, test for whether it's a directory - if it is, report it. Test if it's a plain file - if it is, report it.
I do NOT like doing homework for someone, but it looks like you could use an example that simplifies this. I recommend you not use this as written - break it out and make it clearer, but this is a template for the general logic.
#! /bin/env bash
(( $# )) && [[ -d "$1" ]] && [[ -r "$1" ]] || {
echo "use: $0 <dir>" >&2
exit 1
}
for e in "$1"/.* "$1"/*
do echo "$e:"
[[ -d "$e" ]] && echo " is a directory"
[[ -f "$e" ]] && echo " is a plain file"
[[ -r "$e" ]] && echo " is readable"
[[ -x "$e" ]] && echo " is executable"
done
If you read the links I provided you should be able to break this apart and understand it.
Generally, your script is long and a bit convoluted. Simpler is easier to understand and maintain. For example, be very careful about block indentation to understand scope.
$: for i in 1 2 3
> do echo $i
> done
1
2
3
$: echo $i
3
Compare this to -
for i in $*
do if [ -f $i ]; then
echo " it's a file";
echo "THIS IS A LIST OF FILE and DIRECTORY in $i"
ls -a $i
fi
done
echo -----------------------------------------
if [ -d $i ]; then
echo "directory" ;
echo "THIS IS A LIST OF FILES AND DIRETORY in $i"
ls -a $i
fi
You are testing each entry to see if it is a file, and if it is, reporting "THIS IS A LIST OF FILE and DIRECTORY in $i" every time...
but then only testing the last one to see if it's a directory, because the [ -d $i ] is after the done.
...did you run this somewhere to try it, and look at the results?
#!/bin/sh
VAR2=0
while [ $VAR2 -eq 0 ]: do
echo "Please choose one of the following options:"
echo "1. List the current running processes"
echo "2. Check the available free memory"
echo "3. List the disks/partitions"
echo "4. Check for hardware (PCI)"
echo "5. Check for package installation"
echo "6. Create multiple files"
echo "7. Remove multiple files"
echo "8. List the contents of the current directory"
echo "0. Exit"
read VAR1
if [ $VAR1 -eq 0 ]; then
VAR2=2
fi
if [ $VAR1 -eq 1 ]; then
$(top)
fi
if [ $VAR1 -eq 2 ]; then
$(free)
fi
if [ $VAR1 -eq 3 ]; then
$(df)
fi
if [ $VAR1 -eq 4 ]; then
echo "Insert the name of the hardware that you want to search:" read VARHARD $(sudo lspci | grep $VARHARD)
fi
if [ $VAR1 -eq 5 ]; then
echo "Insert the name of the package that you want to search:" read VARPACK $(rpm -qa | grep VARPACK)
fi
if [ $VAR1 -eq 6 ]; then
echo "Insert the base name of the files:" read VARFILE echo "Insert the amount of files you want:" read VARNUMB $(touch $VARFILE{0001..000$VARNUMB})
fi
if [ $VAR1 -eq 7 ]; then
echo "Insert a string to delete all files that contain it:" read VARDEL $(find -type f -name '*$VARDEL*' -exec rm {} \;)
fi
if [ $VAR1 -eq 8 ]; then
$(ls -la)
fi
echo "Press any key and enter to continue... "
read teste
done
So, when I try to run the script "sh script.sh", it gives me an error that says "Syntax error near unexpected token `token'"
Can someone explain the error to me please? I'm new on scripting.
Thanks!
You have two problems in your code, the first is the invocation of subshells where is not due (using $() ) and the second is a typo at the line 26 (you have if instead of fi). The following corrected code works:
#!/bin/bash
VAR2=0
while [ $VAR2 -eq 0 ]; do
echo "Please choose one of the following options:"
echo "1. List the current running processes"
echo "2. Check the available free memory"
echo "3. List the disks/partitions"
echo "4. Check for hardware (PCI)"
echo "5. Check for package installation"
echo "6. Create multiple files"
echo "7. Remove multiple files"
echo "8. List the contents of the current directory"
echo "0. Exit"
read VAR1
if [ $VAR1 -eq 0 ]; then
VAR2=2
fi
if [ $VAR1 -eq 1 ]; then
top
fi
if [ $VAR1 -eq 2 ]; then
free
fi
if [ $VAR1 -eq 3 ]; then
df
fi
if [ $VAR1 -eq 4 ]; then
echo "Insert the name of the hardware that you want to search:"
read VARHARD
sudo lspci | grep $VARHARD
fi
if [ $VAR1 -eq 5 ]; then
echo "Insert the name of the package that you want to search:"
read VARPACK
rpm -qa | grep VARPACK
fi
if [ $VAR1 -eq 6 ]; then
echo "Insert the base name of the files:"
read VARFILE
echo "Insert the amount of files you want:"
read VARNUMB
touch $VARFILE{0001..000$VARNUMB
fi
if [ $VAR1 -eq 7 ]; then
echo "Insert a string to delete all files that contain it:"
read VARDEL
find -type f -name '*$VARDEL*' -exec rm {} \;
fi
if [ $VAR1 -eq 8 ]; then
ls -la
fi
echo "Press any key and enter to continue... "
read teste
done
Why are you using the syntax $(top)? That will execute top to completion (it may be long running, and never end), and then evaluate the output as a command and attempt to execute it. Most likely, the output of top is not valid shell syntax. I'm not sure exactly which command is generating the syntax error related to token, but that's probably the source of your error. Instead of $(top), just write top. Same for all the other instances of $() in the script.
I am creating a bash script that will backup a directory specified by the user. At the moment when the program runs the user will see all the files on the screen being compressed and copied, is there a while to simply replace what the user sees with a simple progress bar?
#!/bin/bash
ROOT="/Users/Rory/Documents"
ROOT_EXCLUDE="--exclude=/dev --exclude=/proc --exclude=/sys --exclude=/temp --exclude=/run --exlucde=/mnt --exlcude=/media --exlude"
DESTIN="/USer/Rory/Documents"
if [ "$USER" != "root" ]; then
echo "You are not the root user"
echo "To use backup please use: sudo backup"
exit
fi
clear
BANNER1="************************************************"
BANNER2="********* Backup Menu **************************"
BANNER3 ="************************************************"
echo $BANNER1
echo $BANNER2
echo $BANNER3
echo $BANNER3
OPTIONS="BACKUP DESTINATION EXIT"
LIST="1)BACKUP 2)SET DESTINATION 3)EXIT"
select opt in $OPTIONS; do
if [ "$opt" = "EXIT" ]; then
echo "GOODBYE!"
clear
exit
elif [ "$opt" = "BACKUP" ]; then
echo "BACKING UP FILES..."
tar cvpfz $DESTIN/backup.`date +%d%m%y_%k%M`.tgz $ROOT $ROOT_EXCLUDE_DIRS
echo "BACKUP COMPLETE"
exit
elif [ "$opt" = "DESTINATION"]; then
echo "DESTINATION IS $DESTIN/backup.`date +%d%m%y_%k%M`.tgz "
echo "TO CHANGE ENTER THE NEW DESTINATION..."
echo "TO LEAVE IT AS IS JUST PRESS ENTER..."
read NEW_DESTIN
#IF GREATER THEN 0 ASSIGN NEW DESTINATION
if [ ${#NEW_DESTIN} -gt 0 ]; then
DESTIN = "$NEW_DESTIN"
fi
clear
echo $BANNER1
echo $BANNER2
echo $BANNER3
echo $LIST
else
clear
echo "BAD INPUT!"
echo "ENTER 1 OR 2..."
echo $LIST
fi
done
If you copy your files using rsync, you can get a progress bar using the --progress parameter.
rsync --progress ...