Logical OR error in Linux shell script - linux

I have written a shell script for printing book:
#!/bin/sh
if [ -z "$1" ]
then
exit 1
fi
filename=$1
options=""
mode="color"
first=""
last=""
pages="All pages from"
shift
until [ -z "$1" ]
do
if [ $1 = "gray" -o $1 = "grey" -o $1 = "grayscale" -o $1 = "greyscale" ]
then
options=" -o ColorModel=KGray"
mode=$1
elif [ $1 = "from" ]
then
shift
first="$1"
elif [ $1 = "to" ]
then
shift
last="$1"
fi
shift
done
if [ $first -o $last ]
then
pages="Pages"
if [ $first ]
then
pages="$pages $first"
first=" -f $first"
else
pages="$pages 1"
fi
if [ $last ]
then
pages="$pages to $last"
last=" -l $last"
else
pages="$pages to last"
fi
pages="$pages from"
fi
echo -n "$pages $filename will be printed in $mode mode. If it's OK, put paper in your printer and press ENTER. Else press CTRL+C. "
read ack
pdftops$first$last -expand $filename - | psbook | psnup -2 > tmp.ps
psselect -o tmp.ps | lpr$options
echo -n "Wait for the end of printing, then take printed pages, put them back in printer to print on other side and press ENTER again."
read ack
psselect -e -r tmp.ps | lpr$options
rm tmp.ps
exit 0
When I saved this code to file "print-book" and ran it like:
print-book test.pdf gray
I got this:
Pages 1 to last from test.pdf will be printed in gray mode. If it's OK, put paper in your printer and press ENTER. Else press CTRL+C
i.e. condition "$first -o $last" was true. But if check "$first" and "$last" separately in this place, they both are false.
How is this possible?

If $first and $last are empty, [ $first -o $last ] will be evaluated as [ -o ], which is not what you want.
You should instead use [ "$first" -o "$last" ], which is equivalent to [ "" -o "" ].
Never use variables without quoting them (unless you know what you're doing): the results will be unexpected most of the times.
Also, test weird behaviors interactively in the command line: just input [ $a -o $b ] && echo y to quickly see what's going on and being able to play with your variables.

Related

Editing file with vim without typing path (similar to autojump)

A few months back, I installed a utility on my mac so that instead of typing something like this:
vim /type/path/to/the/file
I could just type:
v file
9 times out of 10 it would guess the right file based on the past history, similar to the way autojump works. And instead of typing in vim I can just type the letter v.
I can't remember how I set this up though. It still works on my mac but I don't see anything in my .bash_profile that shows how I did that.
I'm trying to get this to work on my linux box.
This can be found here
https://github.com/rupa/v/blob/master/v
it should work in Linux too. It is a bash script that uses the viminfo
history file to fill in partial strings.
It can be installed on macOS with brew install v
Ah! I found the command with which. Here is the magical script. I can't determine where I got it.
#!/usr/bin/env bash
[ "$vim" ] || vim=vim
[ $viminfo ] || viminfo=~/.viminfo
usage="$(basename $0) [-a] [-l] [-[0-9]] [--debug] [--help] [regexes]"
[ $1 ] || list=1
fnd=()
for x; do case $x in
-a) deleted=1;;
-l) list=1;;
-[1-9]) edit=${x:1}; shift;;
--help) echo $usage; exit;;
--debug) vim=echo;;
--) shift; fnd+=("$#"); break;;
*) fnd+=("$x");;
esac; shift; done
set -- "${fnd[#]}"
[ -f "$1" ] && {
$vim "$1"
exit
}
while IFS=" " read line; do
[ "${line:0:1}" = ">" ] || continue
fl=${line:2}
[ -f "${fl/\~/$HOME/}" -o "$deleted" ] || continue
match=1
for x; do
[[ "$fl" =~ $x ]] || match=
done
[ "$match" ] || continue
i=$((i+1))
files[$i]="$fl"
done < "$viminfo"
if [ "$edit" ]; then
resp=${files[$edit]}
elif [ "$i" = 1 -o "$list" = "" ]; then
resp=${files[1]}
elif [ "$i" ]; then
while [ $i -gt 0 ]; do
echo -e "$i\t${files[$i]}"
i=$((i-1))
done
read -p '> ' CHOICE
resp=${files[$CHOICE]}
fi
[ "$resp" ] || exit
$vim "${resp/\~/$HOME}"

How to compare two stat values of the same file?

Screenshot of the my code
I am trying to make a shell program that tells me when a file has been created, when it has been modified, and when it has been deleted. I think I can solve this but my only issue is that I cant compare the stat values. It tells me that I have "too many arguments". Any help would be much appreciated :)
#!/bin/bash
run=yes
if [ -f $1 ]
then
while [ run=yes ]
do
time1=$(stat -c %y $1)
time2=$(stat -c %y $1)
if [ ! $time2 ]
then
echo "The file "$1" has been deleted."
run=no
elif [ $time2 -gt $time1 ]
then
echo "The file "$1" has been modified."
run=no
fi
done
else
while [ run=yes ]
do
sleep 2
if [ -f $1 ]
then
echo "The file "$1" has been created."
run=no
fi
done
fi
The output of static -c %y ... includes spaces, which is what the shell uses to separate arguments. When you then run:
if [ ! $time2 ]; then
This translates into something like:
if [ ! 2017-09-02 08:57:19.449051182 -0400 ]; then
Which is an error. The ! operator only expects a single argument. You could solve it with quotes:
if [ ! "$time2" ]; then
Or by using the bash-specific [[...]]] conditional:
if [[ ! $time2 ]]; then
(See the bash(1) man page for details on that second solution).
Separately, you're not going to be able to compare the times with -gt as in:
elif [ $time2 -gt $time1 ]
This (a) has the same problem as the earlier if statement, and (b) -gt can only be used to compare integers, not time strings.
If you were to use %Y instead of %y, you would get the time as an integer number of seconds since the epoch, which would solve all of the above problems.
The code is now working and I thought I would share the final result if anyone wanted to know.
#!/bin/bash
run=true
if [ -f $1 ]
then
while [ "$run" = true ]
do
time1=$(stat -c %Y $1 2>/dev/null)
sleep $2
time2=$(stat -c %Y $1 2>/dev/null)
if [ ! "$time2" ]
then
echo "The file "$1" has been deleted."
run=false
elif [ $time2 -gt $time1 ]
then
echo "The file "$1" has been modified."
run=false
fi
else
while [ "$run" = true ]
do
sleep 2
if [ -f $1 ]
then
echo "The file "$1" has been created."
run=false
fi
done
fi

Linux shell script, differences between two directories

I made this code
if [ $# -ne 2 ]; then
echo "use $0 dir1 dir2"
exit 1
fi
if [ ! -d $1 ]; then
echo "$1 nu este un director"
exit 1
fi
if [ ! -d $2 ]; then
echo "$2 nu este un director "
exit 1
fi
a=0
k=1
for $1 in `ls`
do
if [ -f $1 ]; then
a=`exp $a + 1`
fi
done
echo "Ther are $a file "
I want to compare two folders and the folder are arguments to the command line.. it should be something like this : ./script.sh dir1 dir2
But i have this eror :
**./director.sh: line 29: `$1': not a valid identifier
**
I want to count the file from dir1 who is argument to the command line.
Can someone help me please ?
This is the main error:
for $1 in `ls`
$1 is not a valid variable name
don't parse ls
Do this instead
for file in *
Also, quote your variables: you want to protect your script from any filenames containing whitespace.
if [ ! -d "$1" ]
if [ -f "$file" ]
Instead of this part:
a=0
k=1
for $1 in `ls`
do
if [ -f $1 ]; then
a=`exp $a + 1`
fi
done
do this:
a=$(ls "$1" | wc -l)
If you absolutely have to use your looping, change it like this:
a=0
for i in ${1}/*
do
if [ -f "$i" ]; then
let a=a+1
fi
done
echo "There are $a files"

Bash, How to check command output and proceed

I have a bash script that rsyncs my files to my remote server. I want to check if there are lines with "import ipdb;ipdb.set_trace()" before syncing, but I can't. This is my script:
#!/bin/bash;
var="$1" ;
if [ "$var" == "main" -o "$var" == "all" ] ; then
echo "*** checking of ipdb lines, first ***" ;
res=$(ack-grep --type=python "import ipdb" -c -l) ;
if [ $res ] ; then
echo $res ;
else
rsync -Paz --exclude ".*" -e "ssh -i /home/chris/.ssh/thishost-rsync-key" this_pc remote_pc
fi
fi
I always get this type of error:
*** checking of ipdb lines, first ***
sync.sh: line 10: [: too many arguments
sending incremental file list
Line 10 in my script is this one: if [ $res ] ; then
What am I doing wrong?
When you do
res=$(ack-grep --type=python "import ipdb" -c -l) ;
the variable $res is set to contain the output from the command(s) inside $(...).
If you want the resulting exit status use $? instead, otherwise use e.g.
if [ ! -z "$res" ] ...
to check if $res is not empty (meaning that there was output from the command).
Instead of :
if [ $res ] ; then
Use:
if [ ! -z "$res" ] ; then
-z => return True if the length of string is zero.

check file or user script assignment problems

Below is the assignment for the bash shell script I'm writing. I'm having a
problem with -u information being output even though I am using the -f option.
This class is a beginner class, so please bear with me. Would be grateful to
have some input on my code. Thanks for taking the time to check this out if you
do.
Here is the sample output:
[***#***]$ chk3 -f share
share is a directory and it is readable | writable | executable | abecker is
currently logged in their home directory is /students/abecker
Here is the usage
chk -f filepath
If filepath exists, output in readable sentences
if it is a symbolic link, say so. You do not have to continue and report the
permissions.
if it doesn't exist, say so. Don't continue to report the permissions
report what it is: file, directory, or something else, and continue to
report the permissions:
report what combination of read, write and execute access rights your
program has for the data. Note that this is dependent on who runs your
program. Do not attempt to do this by looking at the permissions as output
by ls -l. You must use the test operators to do this.
If filepath does not exist (and is not a symbolic link), your program should
report this instead in an informative error message. In this case, you
should exit with an error.
chk -u user
If the user exists on the system, report
the path to the user's home directory
if the user is currently logged in, say so. Otherwise, report when they last
logged in. (Take some care so that this is generated reliably and quickly.)
If the user doesn't exist, report this in an informative error message, and
exit with an error.
Here is my code
#!/bin/bash
if [ $# -gt 2 ]
then
echo "only 2 aruments can be used"
exit 1
fi
if [ "$1" != '-f' -a "$1" != '-u' ]
then
echo "first argument must be -f or -u"
exit 1
fi
if [ "$1" = '-f' -a $# -ne 2 ]
then
echo 'Usage: chk -f [FILEPATH]'
exit 1
fi
if [ "$1" = '-f' ]
then
FILEPATH=$2
fi
if [ -L "$FILEPATH" ]
then
echo "$FILEPATH is a symbolic link"
exit 0
elif [ -d "$FILEPATH" ]
then
echo -e "$(basename "$FILEPATH") is a directory and it is \c"
elif [ -f "$FILEPATH" ]
then
echo -e "$(basename "$FILEPATH") is a file and it is \c"
else
echo "I cannot determine what $(basename "$FILEPATH") is"
exit 1
fi
if [ -r "$FILEPATH" ]
then
echo -e "readable | \c"
fi
if [ -w "$FILEPATH" ]
then
echo -e "writable | \c"
fi
if [ -x "$FILEPATH" ]
then
echo -e "executable | \c"
fi
if [ "$1" = '-u' -a $# -eq 1 ]
then
USER=$LOGNAME
elif [ "$1" = '-u' -a $# -eq 2 ]
then
USER=$2
fi
USERINFO=$(grep "^$USER:" /etc/passwd)
if ! grep "^$USER:" /etc/passwd > /dev/null
then
echo "$USER cannot be found on this system"
exit 1
fi
if ! who | grep "^$USER " > /dev/null
then
echo "$USER is not currently logged on and last logged on"
echo "$(last -1 "$USER")"
exit 0
else
echo "$USER is currently logged in their home directory is"
echo "$(echo "$USERINFO" | awk -F":" '{print $6}')"
fi
You're not putting the processing of different options into different blocks; the code simply passes through everything for all options.
e.g. for the -f option, you have:
if [ "$1" = '-f' ]
then
FILEPATH=$2
fi
and then process all the options for filepath, without putting them into the if statement, so if you pass in either -f or -u, it always passes into the code:
if [ -L "$FILEPATH" ]
then
echo "$FILEPATH is a symbolic link"
exit 0
elif
If you don't want to break your program into functions, what you want to do is put all the code relating to processing the -f option into the same if-statement, somewhat like:
if [ "$1" = '-f' ]
then
FILEPATH=$2
if [ -L "$FILEPATH" ]
then
echo "$FILEPATH is a symbolic link"
exit 0
elif [ -d "$FILEPATH" ]
then
echo -e "$(basename "$FILEPATH") is a directory and it is \c"
elif [ -f "$FILEPATH" ]
then
echo -e "$(basename "$FILEPATH") is a file and it is \c"
else
echo "I cannot determine what $(basename "$FILEPATH") is"
exit 1
fi
if [ -r "$FILEPATH" ]
then
echo -e "readable | \c"
fi
if [ -w "$FILEPATH" ]
then
echo -e "writable | \c"
fi
if [ -x "$FILEPATH" ]
then
echo -e "executable | \c"
fi
fi # if [ "$1" = '-f' ]
Similarly for the -u option, you need to break it into multiple statements and then process all the options for the statement:
if [ "$1" = 'u' ]
then
if [ $# -eq 1 ]
then
USER=$LOGNAME
elif [ $# -eq 2 ]
then
USER=$2
fi
USERINFO=$(grep "^$USER:" /etc/passwd)
if ! grep "^$USER:" /etc/passwd > /dev/null
then
echo "$USER cannot be found on this system"
exit 1
fi
if ! who | grep "^$USER " > /dev/null
then
echo "$USER is not currently logged on and last logged on"
echo "$(last -1 "$USER")"
exit 0
else
echo "$USER is currently logged in their home directory is"
echo "$(echo "$USERINFO" | awk -F":" '{print $6}')"
fi
fi # if [ "$1" = '-u' ]
I would, however recommend putting the code that acts on the options into shell functions, which makes it much easier to read the code; e.g.
filepath() {
FILEPATH="$1"
if [ -L "$FILEPATH" ]
then
echo "$FILEPATH is a symbolic link"
exit 0
elif [ -d "$FILEPATH" ]
then
echo -e "$(basename "$FILEPATH") is a directory and it is \c"
elif [ -f "$FILEPATH" ]
then
echo -e "$(basename "$FILEPATH") is a file and it is \c"
else
echo "I cannot determine what $(basename "$FILEPATH") is"
exit 1
fi
if [ -r "$FILEPATH" ]
then
echo -e "readable | \c"
fi
if [ -w "$FILEPATH" ]
then
echo -e "writable | \c"
fi
if [ -x "$FILEPATH" ]
then
echo -e "executable | \c"
fi
}
And then for the processing code:
if [ "$1" = '-f' ]
then
filepath "$2"
fi
and something similar for the -u option.

Resources