I'm learning BASH scripting, and I've got a problem. I wrote a generator, that checks if there is script with same name as given and if not it makes one, makes it executable and gives it proper shebang. But it doesn't work. I wanted it to exit when there is already one script with that name. Can you tell me what I'm doing wrong?
#!/bin/bash
clear
echo "Name the script"
read name
echo "What shell? (bash/sh)"
read type
if [ -e ./$name ]
then
echo "You already have that script"
read
exit
else
touch $name
chmod 755 $name
fi
case $type in
"bash") echo '#!/bin/bash' > $name ;;
"sh") echo '#!/bin/bash' > $name ;;
*) echo "I don't know what do you want" ;;
esac
vim $name
There is an extra read which is holding you up:
if [[ -e ./$name ]]
then
echo "'./$name' already exists" # <<<< always report the name
# read <<<< This is the line which is causing problems
exit 1 # <<<< indicate there was an error
else
touch "$name"
chmod 755 "$name"
fi
Quote filenames incase they contain embedded whitespace (you don't need them around variables if you use [[ ]].
Also note that the #! line for sh is wrong (you are using bash for both)
Edit:
If you have /etc/shells on your system, this is a good exercise in using select menu. Try this:
#!/bin/bash
read -p "Name the script: " name
if [[ -e ./$name ]]
then
echo "'./$name' already exists" # <<<< always report the name
# read <<<< This is the line which is causing problems
exit 1 # <<<< indicate there was an error
else
touch "$name"
chmod 755 "$name"
fi
declare -a shells
i=0
while read shell
do
if [[ $shell != \#* && $shell != "" ]]
then
shells[i++]="$shell"
fi
done < /etc/shells
PS3='Please select the shell: '
select type in "${shells[#]}" QUIT
do
echo "You selected $type"
if [[ $type == QUIT ]]
then
exit 2
fi
break
done
echo "#!$type" > "$name"
vim $name
You might also consider using $EDITOR rather than hard-coding vim.
Related
For a while, I've had a need for a bash script to make a directory and cd into it. Most of the solutions online work but are very minimal so I wanted to make one that handles things like creating parent directories and permission checking. Here's my code:
#!/bin/bash
function mkcd() {
# Check for no arguments
if "$#" -eq 0; then
echo "Error: no arguments provided"
return 1
fi
# Checks if help flag is used
# Not with other flags to ensure the directory isn't assumed to be a flag
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
echo "mkcd - Makes a directory and changes directory to it\n"
echo "Flags:"
echo " -h, --help Display help message"
echo " -p, --parents Makes parent directories as neeeded"
echo " -a, --absolute Receive an absolute directory instead of relative\n"
echo "Format: mkcd [arguments] directory"
return 0
fi
# Flag checker
while test "$#" -gt 1; do
case "$1" in
-p | --parents)
mkcd_parents=true
shift
;;
-a | --absolute)
shift
;;
esac
done
mkcd_path="$1"
if [[ ! -w "$PWD" ]]; then
echo "Error: Permission denied"
return 1
fi
if [[ -d "$mkcd_path" ]]; then
echo "Error: Directory already exists"
return 1
fi
if "$mkcd_parents"; then
mkdir -p "$mkcd_path"
cd "$mkcd_path"
else
mkdir "$mkcd_path"
cd "$mkcd_path"
fi
}
I also sourced it in my .zshrc file with source ~/bin/*
When I run the command, I get this output:
~ ❯ mkcd test_dir
mkcd:3: command not found: 1
mkcd:45: permission denied:
~/test_dir ❯
Does anyone understand why I'm getting this error?
if "$#" -eq 0; then
Since you have one argument to the script, that becomes after expansions
if 1 -eq 0; then
You probably meant to do
if [[ "$#" -eq 0 ]]; then
instead. (With either of [ .. ] or [[ .. ]].)
As an aside, I would change this
if "$mkcd_parents"; then
to
if [ "$mkcd_parents" = "true" ]; then
Otherwise if the -p option isn't given, $mkcd_parents is unset, "$mkcd_parents" expands to the empty string, and you get an error about that command not being found.
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 am trying to implement confirmation prompt with a bash script but for some reason, prompt won't wait for user input. I've tried many examples but no luck so far. I am on MacOS if it makes any difference.
Just a few examples I tried (All copy+paste from other answers in SO):
#!/bin/bash
read -p "Are you sure? " -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
# do dangerous stuff
fi
#!/bin/bash
read -p "Continue (y/n)?" CONT
if [ "$CONT" = "y" ]; then
echo "yaaa";
else
echo "booo";
fi
#!/bin/bash
while true; do
read -rsn1 input
if [ "$input" = "a" ]; then
echo "hello world"
fi
done
#!/bin/bash
read -p "Continue (y/n)?" choice
case "$choice" in
y|Y ) echo "yes";;
n|N ) echo "no";;
* ) echo "invalid";;
esac
This doesn't even prompt anything:
#!/bin/bash
read -n 1 -s -r -p "Press any key to continue"
Changed to answer from comment : in commit-msg hook it seems standard input is closed, indeed this can be checked adding following command
ls -l /dev/fd/
which gives
... 0 -> /dev/null
as mentioned in this post
exec 0< /dev/tty
will restore standard input to tty, another solution as noticed standard output and error are still redirected to tty
exec 0<&1
The original question has the important part missing and it is my fault not making it very clear in very first place. It became apparent after #NahuelFouilleul's comment. The confirmation/question prompt was not waiting for user to hit a key. The reason was because my bash script was being called by a git hook. Things seem to be done in slightly different way in such cases. The solution is below but the original answer is here.
#!/bin/bash
exec < /dev/tty
while true; do
read -p "Accepting the offer? (y/n) " answer
if [[ $answer =~ ^[Yy]$ ]] ;
then
echo "Accepted"
else
echo "Not accepted"
fi
break
done
Try this:
echo -n "Continue (y/n)?"
read CONT
if [ "$CONT" = "n" ]
then
echo "NO"
else
echo "YES"
fi
the echo -n means no newline
I'm writing my first bash script and having trouble assigning a file path to a variable:
$target="/etc/httpd/conf/httpd.conf"
It seems bash wants to interpret this with the "=" assignment operator resulting in the script throwing an error to the effect "No such file or directory."
Is there an easy way to do this? I've discovered I can assign a full path to a constant like this:
readonly TARGET=/etc/httpd/conf/httpd.conf
but that seems rather cumbersome. How would I perform string ops to modify/manipulate?
I've also discovered I can put full paths in an array like this:
declare -a cfile=('/root/.bashrc' '/etc/fstab')
All well and good, but how do I assign a file path to a variable?
== == == ==
finished! my first bash script - a basic config file manager
#!/bin/bash
# cfmgr.sh - configuration file manager bash script
# options: -get, -put
# '-get' creates SOURCEDIR/USERDIR and copies config files to USERDIR
# '-put' copies files in SOURCEDIR/USERDIR to system-defined locations on server
# purpose: helps with moving LAMP VMs to different hosts, bulk edits of
# of config files in editors like Notepad++, and backing up config files.
readonly SOURCEDIR=/usr/bin/_serverconfig
while [[ $# > 0 ]]
do
arg="$1"
shift
case $arg in
-put)
put=true
;;
-get)
get=true
;;
*)
badarg=true
;;
esac
done
clear
if [ $badarg ]; then
echo "Invalid argument. Use either 'scf.sh -put' or 'scf.sh -get' to put"\
"or get config files."
exit
elif [ $get ]; then
echo "Enter directory name to store files cfmgr will GET from this server:"
elif [ $put ]; then
echo "Enter directory name containing files cfmgr will PUT to this server:"
else
echo "Use either 'scf.sh -put' or 'scf.sh -get' to put or get config files."
exit
fi
read -e -i $SOURCEDIR"/" USERDIR
pattern=" |'"
if [[ $USERDIR =~ $pattern ]]; then
echo "Spaces not allowed. Please try again."
exit
fi
declare -a cfile=('/root/.bashrc' '/etc/fstab' '/etc/hosts' '/etc/networks'\
'/etc/php.ini' '/etc/nsswitch.conf' '/etc/ntp.conf' '/etc/resolv.conf'\
'/etc/sysctl.conf' '/etc/httpd/conf/httpd.conf' '/etc/selinux/config'\
'/etc/samba/smb.conf' '/etc/samba/smbusers' '/etc/security/limits.conf'\
'/etc/sysconfig/network' '/etc/sysconfig/network-scripts/ifcfg-eth0'\
'/etc/sysconfig/network-scripts/ifcfg-eth1')
if [ $get ]; then
if [[ -d "$USERDIR" ]]; then
echo $USERDIR "directory already exists. Please try again."
exit
else
mkdir -m 755 $USERDIR
fi
for file in ${cfile[#]}
do
if [ -e $file ]; then
rsync -q $file $USERDIR
if [ $? -eq 0 ]; then
sleep 0.1
printf "# "$file"\n"
fi
else
printf "not found: "$file"\n"
fi
done
elif [ $put ]; then
if [[ ! -d "$USERDIR" ]]; then
echo $USERDIR "directory does not exist. Please try again."
exit
fi
id=0
cd $USERDIR
for item in *
do
if [[ -f $item ]]; then
cdir[$id]=$item
id=$(($id+1))
fi
done
for file in ${cdir[#]}
do
case $file in
.bashrc)
idx=0
;;
fstab)
idx=1
;;
hosts)
idx=2
;;
networks)
idx=3
;;
php.ini)
idx=4
;;
nsswitch.conf)
idx=5
;;
ntp.conf)
idx=6
;;
resolv.conf)
idx=7
;;
sysctl.conf)
idx=8
;;
httpd.conf)
idx=9
;;
config)
idx=10
;;
smb.conf)
idx=11
;;
smbusers)
idx=12
;;
limits.conf)
idx=13
;;
network)
idx=14
;;
ifcfg-eth0)
idx=15
;;
ifcfg-eth1)
idx=16
;;
*)
printf "not found: "$file"\n"
continue
esac
target=${cfile[$idx]}
if [[ -e $target ]]; then
dtm=$(date +%Y-%m-%d)
mv $target $target"."$dtm
fi
source=$USERDIR"/"$file
dos2unix -q $source
rsync -q $source $target
if [ $? -eq 0 ]; then
sleep 0.1
printf "# "$target"\n"
fi
done
read -p "reboot now? (y|n)" selection
case $selection in
[Yy]*)
`reboot`
;;
*)
exit
;;
esac
fi
exit 0
Instead of
$target="/etc/httpd/conf/httpd.conf"
Use:
target="/etc/httpd/conf/httpd.conf"
When bash sees the former, it first substitutes in for "$target". If target was empty, then the line that bash tries to execute, after the variable substitution and quote removal steps, is:
=/etc/httpd/conf/httpd.conf
Since there is no file named "=/etc/httpd/conf/httpd.conf", bash returns with a "No such file or directory" error.
I'm using the find command in my bash script like so
for x in `find ${1} .....`;
do
...
done
However, how do I handle the case where the input to my script is a file/directory that does not exist? (ie I want to print a message out when that happens)
I've tried to use -d and -f, but the case I am having trouble with is when ${1} is "." or ".."
When the input is something that doesn't exist it does not enter my for loop.
Thanks!
Bash gives you this out of the box:
if [ ! -f ${1} ];
then
echo "File/Directory does not exist!"
else
# execute your find...
fi
Bash scripting is a bit weird. Practice before implementation. But this site seems to break it down well.
If the file exists, this works:
if [ -e "${1}" ]
then
echo "${1} file exists."
fi
If the file does not exist, this works. Note the '!' to denote 'not':
if [ ! -e "${1}" ]
then
echo "${1} file doesn't exist."
fi
assign the find to a variable and test against the variable.
files=`find ${1} .....`
if [[ "$files" != “file or directory does not exist” ]]; then
...
fi
You can try something like this:
y=`find . -name "${1}"`
if [ "$y" != "" ]; then
for x in $y; do
echo "found $x"
done
else
echo "No files/directories found!"
fi