Single variable but multiple values - bash

I want to write a program which will check if soft links exist or not
#!/bin/bash
file="/var/link1"
if [[ -L "$file" ]]; then
echo "$file symlink is present";
exit 0
else
echo "$file symlink is not present";
exit 1
fi
There will be link2, link3, link4, link5.
Do I have to write the same script n number of times for n number of links or can this be achieved in one script?
Also I want to have the exit 0 and exit 1 so that I can use for monitoring purpose.

You can use a function:
checklink() {
if [[ -L "$1" ]]; then
echo "$1 symlink is present";
return 0
else
echo "$1 symlink is not present";
return 1
fi
}
file1="/var/link1"
file2="/var/link2"
file3="/var/link3"
file4="/var/link4"
for f in "${file1}" "${file2}" "${file3}" "${file4}"; do
checklink "$f" || { echo "Exit in view of missing link"; exit 1; }
done
echo "All symlinks checked"
exit 0

Related

How to Ask User for Confirmation: Shell

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 don't know if my shell script is correct

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?

Semaphore thinks lock file already exists

I'm using link to create a semaphore - the idea is to lock out of writing to a db.
Here I have a script to create a table in a database:
#!/bin/bash
if [ "$#" -lt 3 ]; then
echo "Not enough parameters"
exit 1
elif [ "$#" -gt 3 ]; then
echo "Too many parameters"
exit 1
fi
if [ ! -d "$1" ]; then
echo "That database doesn't exist!"
exit 1
fi
./P.sh $1
if [ -f "$1/$2.txt" ]; then
echo "That table already exists!"
./V.sh $1
exit 1
else
touch "$1/$2.txt"
fi
./V.sh $1
echo "$3" > "$1/$2.txt"
echo "Ok, table created"
exit 0
Here's my P file:
#!/bin/bash
if [ -z "$1" ]; then
echo "Usage $0 mutex-name"
exit 1
elif [ ! -e "$1" ]; then
echo "Target for the lock must exist"
exit 2
else
while ! ln "$1" "$1.lock"; do
sleep 1
done
exit 0
fi
and my V:
#! /bin/bash
if [ -z "$1" ]; then
echo "Usage $1 mutex-name"
exit 1
else
rm "$1.lock"
exit 0
fi
let's say I create a table by running ./create_table people footballers age,height
This should create a file footballers.lock (created by P) and then once the writing has happened the V should remove it. But for some reason the P thinks that the .lock file already exists, and it definitely doesn't.
Can anyone spot what's going wrong?
Found it - you can't use ln on directories...

Bash script errors: i in for loop gives 'no such file or directory'

Long story short, I need to write a shell script. The script will take a single command line argument which will be a directory path.
The script will then read each of the files in that directory and output it to standard output; the output will be in HTML and will be a table.
The files will be in this format:
owner sysadmin group
admin ajr
loc S-309
ser 18r97
comment noisy fan
What I have so far:
PATH=/bin:/usr/bin
cd "$#"
if [ test $? -ne 0]
then
exit 1
fi
filenames=$(ls "$#")
for i in $filenames
do
while read item value
do
if [ $item="owner" ] || [ $item="admin" ] || [ $item="loc" ] || [ $item="ser"]
then
a[$item]=$value
fi
done < i
done
echo '<html>'
echo '<body>'
echo '<table border=1>'
echo '<tr><th>hostname</th><th>location</th><th>Admin</th><th>Serial Number</th><th>owner</th><tr>'
for i in filename
do
echo '<tr><td>'$i'</td><td>'${i[loc]}'</td><td>${i[admin]}'</td><td>'${i[ser]}'</td><td>'${i[owner]}'</td><tr>'
done
echo
echo '</table>'
echo '</body>'
echo '</html>'
The HTML isn't my main concern since I am just following a format given, with each of the values going in between. However, I am getting an error that I have no idea why:
invrep: line 10: i: no such file or directory
but I am using it in a loop. Why is it giving me this error?
Also to confirm, the directory that I used exists; I'm not sure if that has to do with anything though.
Caveat Lector: the code in the question has been edited. The code I commented on may not be the code you can see.
Not directly the problem (chepner diagnosed that in his comment), but:
cd "$#"
if [ test $? -ne 0]
then
exit 1
fi
has a variety of problems. You don't verify that there's only one argument, and you pass all the arguments that are given to cd, which may just quietly ignore the surplus. The test line should use either [ or test but not both. If you use [, the last argument must be ] so you're missing a space:
if test $? -ne 0
if [ $? -ne 0 ]
However, you could short circuit that paragraph by:
cd "$#" || exit 1
(or you could drop the 1 even, though I'd leave it there).
You might want to consider:
case $# in
1) cd "$1" || exit 1;;
*) echo "Usage: $0 directory" >&2; exit 1;;
esac
This verifies that a single argument was passed and that it names a directory you can cd to.
Your looping code also has problems. The while loop should be redirected from "$i" once you've fixed things up:
filenames=$(ls "$#")
for i in $filenames
do
while read item value
do
if [ $item="owner" ] || [ $item="admin" ] || [ $item="loc" ] || [ $item="ser"]
then
a[$item]=$value
fi
done < $i
# Print HTML here!! Not after this loop
done
Your HTML loop has a lot of problems too — notably using $i as an array instead of $a.
PATH=/bin:/usr/bin
case $# in
1) cd "$1" || exit 1;;
*) echo "Usage: $0 directory" >&2; exit 1;;
esac
echo '<html>'
echo '<body>'
echo '<table border=1>'
echo '<tr><th>hostname</th><th>location</th><th>Admin</th><th>Serial Number</th><th>owner</th><tr>'
filenames=$(ls "$#")
for i in $filenames
do
while read item value
do
if [ $item="owner" ] || [ $item="admin" ] || [ $item="loc" ] || [ $item="ser"]
then
a[$item]=$value
fi
done < $i
echo "<tr><td>$i</td><td>${a[loc]}</td><td>${a[admin]}</td><td>${a[ser]}</td><td>${a[owner]}</td><tr>"
done
echo '</table>'
echo '</body>'
echo '</html>'
And that still doesn't fix the problem with using ls to generate a list of file names. For that, given the rest of the script, lose filenames altogether and use for file in * instead. You then need to quote $i in the I/O redirection, too.
PATH=/bin:/usr/bin
case $# in
1) cd "$1" || exit 1;;
*) echo "Usage: $0 directory" >&2; exit 1;;
esac
echo '<html>'
echo '<body>'
echo '<table border=1>'
echo '<tr><th>hostname</th><th>location</th><th>Admin</th><th>Serial Number</th><th>owner</th><tr>'
for i in *
do
while read item value
do
if [ $item = "owner" ] || [ $item = "admin" ] ||
[ $item = "loc" ] || [ $item="ser"]
then
a[$item]=$value
fi
done < "$i"
echo "<tr><td>$i</td><td>${a[loc]}</td><td>${a[admin]}</td><td>${a[ser]}</td><td>${a[owner]}</td><tr>"
done
echo '</table>'
echo '</body>'
echo '</html>'
(Also fixed spacing in the if statement in the loops. The code is still not very elegant, but it is somewhat related to the original code.)
filenames=$(ls "$#")
is wrong, and should never be used by anyone. See the first entry in http://mywiki.wooledge.org/BashPitfalls, or the entire page http://mywiki.wooledge.org/ParsingLs.
If your argument list is a set of directories, the inner loop would look more like this:
declare -A a
for dir in "$#"; do
for i in "$dir/"*; do
while read -r item value; do
case $item in
owner|admin|loc|ser)
a[$item]=$value
;;
esac
done <"$i"
done
done

Check if passed argument is file or directory in Bash

I'm trying to write an extremely simple script in Ubuntu which would allow me to pass it either a filename or a directory, and be able to do something specific when it's a file, and something else when it's a directory. The problem I'm having is when the directory name, or probably files too, has spaces or other escapable characters are in the name.
Here's my basic code down below, and a couple tests.
#!/bin/bash
PASSED=$1
if [ -d "${PASSED}" ] ; then
echo "$PASSED is a directory";
else
if [ -f "${PASSED}" ]; then
echo "${PASSED} is a file";
else
echo "${PASSED} is not valid";
exit 1
fi
fi
And here's the output:
andy#server~ $ ./scripts/testmove.sh /home/andy/
/home/andy/ is a directory
andy#server~ $ ./scripts/testmove.sh /home/andy/blah.txt
/home/andy/blah.txt is a file
andy#server~ $ ./scripts/testmove.sh /home/andy/blah\ with\ a\ space.txt
/home/andy/blah with a space.txt is not valid
andy#server~ $ ./scripts/testmove.sh /home/andy\ with\ a\ space/
/home/andy with a space/ is not valid
All of those paths are valid, and exist.
That should work. I am not sure why it's failing. You're quoting your variables properly. What happens if you use this script with double [[ ]]?
if [[ -d $PASSED ]]; then
echo "$PASSED is a directory"
elif [[ -f $PASSED ]]; then
echo "$PASSED is a file"
else
echo "$PASSED is not valid"
exit 1
fi
Double square brackets is a bash extension to [ ]. It doesn't require variables to be quoted, not even if they contain spaces.
Also worth trying: -e to test if a path exists without testing what type of file it is.
At least write the code without the bushy tree:
#!/bin/bash
PASSED=$1
if [ -d "${PASSED}" ]
then echo "${PASSED} is a directory";
elif [ -f "${PASSED}" ]
then echo "${PASSED} is a file";
else echo "${PASSED} is not valid";
exit 1
fi
When I put that into a file "xx.sh" and create a file "xx sh", and run it, I get:
$ cp /dev/null "xx sh"
$ for file in . xx*; do sh "$file"; done
. is a directory
xx sh is a file
xx.sh is a file
$
Given that you are having problems, you should debug the script by adding:
ls -ld "${PASSED}"
This will show you what ls thinks about the names you pass the script.
Using -f and -d switches on /bin/test:
F_NAME="${1}"
if test -f "${F_NAME}"
then
echo "${F_NAME} is a file"
elif test -d "${F_NAME}"
then
echo "${F_NAME} is a directory"
else
echo "${F_NAME} is not valid"
fi
Using the "file" command may be useful for this:
#!/bin/bash
check_file(){
if [ -z "${1}" ] ;then
echo "Please input something"
return;
fi
f="${1}"
result="$(file $f)"
if [[ $result == *"cannot open"* ]] ;then
echo "NO FILE FOUND ($result) ";
elif [[ $result == *"directory"* ]] ;then
echo "DIRECTORY FOUND ($result) ";
else
echo "FILE FOUND ($result) ";
fi
}
check_file "${1}"
Output examples :
$ ./f.bash login
DIRECTORY FOUND (login: directory)
$ ./f.bash ldasdas
NO FILE FOUND (ldasdas: cannot open `ldasdas' (No such file or directory))
$ ./f.bash evil.php
FILE FOUND (evil.php: PHP script, ASCII text)
FYI: the answers above work but you can use -s to help in weird situations by checking for a valid file first:
#!/bin/bash
check_file(){
local file="${1}"
[[ -s "${file}" ]] || { echo "is not valid"; return; }
[[ -d "${file}" ]] && { echo "is a directory"; return; }
[[ -f "${file}" ]] && { echo "is a file"; return; }
}
check_file ${1}
Using stat
function delete_dir () {
type="$(stat --printf=%F "$1")"
if [ $? -ne 0 ]; then
echo "$1 directory does not exist. Nothing to delete."
elif [ "$type" == "regular file" ]; then
echo "$1 is a file, not a directory."
exit 1
elif [ "$type" == "directory" ]; then
echo "Deleting $1 directory."
rm -r "$1"
fi
}
function delete_file () {
type="$(stat --printf=%F "$1")"
if [ $? -ne 0 ]; then
echo "$1 file does not exist. Nothing to delete."
elif [ "$type" == "directory" ]; then
echo "$1 is a regular file, not a directory."
exit 1
elif [ "$type" == "regular file" ]; then
echo "Deleting $1 regular file."
rm "$1"
fi
}
https://linux.die.net/man/2/stat
https://en.m.wikipedia.org/wiki/Unix_file_types
A more elegant solution
echo "Enter the file name"
read x
if [ -f $x ]
then
echo "This is a regular file"
else
echo "This is a directory"
fi
Answer based on the title:
Check if passed argument is file or directory in Bash
This works also if the provided argument has a trailing slash .e.g. dirname/
die() { echo $* 1>&2; exit 1; }
# This is to remove the the slash at the end: dirName/ -> dirName
fileOrDir=$(basename "$1")
( [ -d "$fileOrDir" ] || [ -f "$fileOrDir" ] ) && die "file or directory $fileOrDir already exists"
Testing:
mkdir mydir
touch myfile
command dirName
# file or directory mydir already exists
command dirName/
# file or directory mydir already exists
command filename
# file or directory myfile already exists
#!/bin/bash
echo "Please Enter a file name :"
read filename
if test -f $filename
then
echo "this is a file"
else
echo "this is not a file"
fi
One liner
touch bob; test -d bob && echo 'dir' || (test -f bob && echo 'file')
result is true (0)(dir) or true (0)(file) or false (1)(neither)
This should work:
#!/bin/bash
echo "Enter your Path:"
read a
if [[ -d $a ]]; then
echo "$a is a Dir"
elif [[ -f $a ]]; then
echo "$a is the File"
else
echo "Invalid path"
fi

Resources