A recent test I took had a question on the output of the following bash command:
var=; [ -n $var ]; echo $?; [ -z $var ]; echo $?
The results are 0 and 0, indicating the return codes for both unary operators had no errors. This means $var resolves to both null (empty) and 'non-null' (not empty), correct?
How is this possible?
No, it means that [ is unfixably broken. In both cases $var evaluates to nothing, and the commands simply execute [ -n ] and [ -z ] respectively, both of which result in true. If you want to test the value in the variable itself then you must quote it to have it handled properly.
$ var=; [ -n "$var" ]; echo $?; [ -z "$var" ]; echo $?
1
0
You will need to surround $var:
$ [ -n "$var" ]; echo $?
1
Remember that the closing square bracket is just syntactic sugar: you don't need it. That means your line:
$ [ -n $var ]; echo $?
will expand to (since $var is empty):
$ [ -n ]; echo $?
The above asks: "is the string ']' non-empty?" And the answer is yes.
It's surprising indeed. If you were to try the same with the bashism [[ syntax, you'd get 1 and 0 as results. I reckon this is a bug.
var=; [[ -n $var ]]; echo $?; [[ -z $var ]]; echo $?
or, as Ignacio points out and as in fact I have always been doing intuitively, with defensive coding and quoting:
var=; [[ -n "$var" ]]; echo $?; [[ -z "$var" ]]; echo $?
It's surprising to me that [ behaves this way, because it's a builtin:
$ type [
[ is a shell builtin
Just did a little test and the system command [ behaves in the same broken way as the builtin. So probably it's buggy for compatibility:
var=; /usr/bin/\[ -n $var ]; echo $?; /usr/bin/\[ -z $var ]; echo $?
Related
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
I'm trying to write recursive scripts in bash that receive as an argument a single path and prints the depth of the directory tree rooted at this path.
This is the list_dirs.sh script:
ls -l $dir | grep dr..r..r.. | sed 's/.*:...\(.*\)/\1/'
And this is the isdir.sh script:
if [ -d $1 ]; then
echo 1
elif [ -e $1 ]; then
echo 0
else
echo -1
fi
They both work good.
This is the script dir_depth.sh that I wrote that doesn't work:
if [ $# -eq 0 ]; then
echo "Usage: ./dir_depth.sh <path>"
exit1
fi
x=`source isdir.sh $1`
if [ $x -eq -1 ]; then
echo "no such path $1"
fi
dir=$1
maxD=0
dirs=`source list_dirs.sh`
for f in $dirs
do
if [ $x -ne 0 ]; then
x=`dir_depth.sh $f`
if [ "$x" -eq "$maxD" ]; then
maxD=x;
fi
fi
echo $f
done
echo $((maxD++))
I'm really new to bash scripting and I don't know how to debug or what's wrong in my script.
Some missing items are:
If you have a directory parent/child/ and run list_dirs.sh parent/, it will output child. You then try to look up child/ in the current directory instead of parent/child/.
You do echo $f for debug purposes and echo $((maxD++)) to return a result. They are being confused for each other. Use >&2 to write errors and debug messages to stderr.
echo $((maxD++)) is a classic error equivalent to return x++. You return the number, and then increment a variable that's no longer used.
[ "$x" -eq "$maxD" ] makes no sense. Use -ge since you're trying to find the max.
Here's dir_depth.sh with these changes in place:
if [ $# -eq 0 ]; then
echo "Usage: ./dir_depth.sh <path>" >&2
exit 1
fi
x=`source ./isdir.sh $1`
if [ $x -eq -1 ]; then
echo "no such path $1" >&2
fi
dir=$1
dirs=`source ./list_dirs.sh`
maxD=0
for f in $dirs
do
if [ $x -ne 0 ]; then
x=`./dir_depth.sh "$1/$f"`
if [ "$x" -ge "$maxD" ]; then
maxD="$x";
fi
fi
echo $f >&2
done
echo $((maxD+1))
So I'm beginning making bash scripts. I can do basic stuff, but that's it.
I want to make something so when I type in:
./myprogram -t
It will do "echo true"
And if I type in:
./myprogram -f
It will do "echo false"
Thanks in advance
The positional parameters are available through the variables $1 $2 etc.
There are many ways to implement the contition. You could use an if statement:
#!/bin/bash
if [ "$1" = -t ]
then
echo true
elif [ "$1" = -f ]
then
echo false
fi
A case statement:
#!/bin/bash
case "$1" in
-t) echo true ;;
-f) echo false ;;
esac
Or a short-circuit:
#!/bin/bash
[ "$1" = -t ] && echo true
[ "$1" = -f ] && echo false
For more complex cases consider using the getopt or the getopts libraries.
The word for what you are calling an "option" is typically referred to as an argument in programming. You should read more about how to handle arguments in bash by reading everything at http://tldp.org/LDP/abs/html/othertypesv.html . To answer your direct question the script might look like this:
#!/bin/bash
if [[ $# -eq 0 ]]; then
echo 'No Arguments'
exit 0
fi
if [ $1 = "-f" ]; then
echo false
elif [ $1 = "-t" ]; then
echo true
fi
I'm trying to fit a script for linux onto my WD world edition drive.
The script is written for Bash (debian) but my WD only runs busybox (with ash). Despite this, I have gotten most functionality in there just from using Google. There is only one operator i have not found a counterpart to, the =~ operator
How can i port the functionality of the =~ operator from the old script to ash?
Script:
#! /bin/bash
# posttorrent.sh by Killemov
{
# Log file, file where we tell what events have been processed.
LOG_FILE=/var/log/posttorrent.log
# Username for transmission remote.
TR_USERNAME="username"
# Password for transmission remote.
TR_PASSWORD="password"
# Get current time.
NOW=$(date +%Y-%m-%d\ %H:%M:%S)
# Source directory, should not be changed.
SRC_DIR="${TR_TORRENT_DIR}/${TR_TORRENT_NAME}"
# Directory to store the un-compressed files in..
DEST_DIR="${TR_TORRENT_DIR}/${TR_TORRENT_NAME}/"
# This parameter string could be passed from Transmission in the future.
TR_TORRENT_PARAMETER="EXTRACT SLEEP1h"
echo "text"
if [ -e "$SRC_DIR/keep" ]; then
TR_TORRENT_PARAMETER="$TR_TORRENT_PARAMETER KEEP"
fi
if [ -e "$SRC_DIR/exit" ]; then
TR_TORRENT_PARAMETER="EXIT"
fi
# Actual processing starts here.
if [[ "$TR_TORRENT_PARAMETER" =~ "EXIT" ]]; then
echo $NOW "Exiting $TR_TORRENT_NAME" >> $LOG_FILE
exit 0
fi
echo "text2"
if [[ "$TR_TORRENT_PARAMETER" =~ "EXTRACT" ]]; then
cd $TR_TORRENT_DIR
if [ -d "$SRC_DIR" ]; then
IFS=$'\n'
unset RAR_FILES i
for RAR_FILE in $( find "$SRC_DIR" -iname "*.rar" ); do
if [[ $RAR_FILE =~ .*part.*.rar ]]; then
if [[ $RAR_FILE =~ .*part0*1.rar ]]; then
RAR_FILES[i++]=$RAR_FILE
fi
else
RAR_FILES[i++]=$RAR_FILE
fi
done
unset IFS
if [ ${#RAR_FILES} -gt 0 ]; then
for RAR_FILE in "$(eval \$$RAR_FILES[#])"; do
unrar x -inul "$RAR_FILE" "$DEST_DIR"
if [ $? -gt 0 ]; then
echo $NOW "Error unrarring $TR_TORRENT_NAME" >> $LOG_FILE
transmission-remote -n $TR_USERNAME:$TR_PASSWORD -t$TR_TORRENT_ID --verify --start
exit 0
fi
done
if [[ ! "$TR_TORRENT_PARAMETER" =~ "KEEP" ]]; then
SLEEP=$(expr match "$TR_TORRENT_PARAMETER" '.*SLEEP\([0-9a-zA-Z]*\)')
if [ ${#SLEEP} -gt 0 ]; then
sleep $SLEEP
fi
transmission-remote -n $TR_USERNAME:$TR_PASSWORD -t$TR_TORRENT_ID --remove-and-delete
fi
echo $NOW "Unrarred $TR_TORRENT_NAME" >> $LOG_FILE
fi
fi
fi
} &
(i had some trouble with indirect references, i hoped i fixed that correctly)
Well for the $VARIABLE =~ PATERN you should be able to use the:
echo "$VARIABLE" | grep -E PATTERN
But I think you will have a little bit of trouble with the arithmetical expressions i++ as well - if it's implemented, then you still need to use the i=$(($i + 1)) syntax, if it's not implemented, then the i=$(expr $i + 1) syntax.
I presume you're reason for the IFS=$'\n' is to split the find on newlines, but you're probably better off with issuing the find into a temporary file, and then doing a while read line; do ... done <$tmpfile,
Additionally, I'm not certain if all versions of busybox ash support arrays, so you may have a problem there as well.
#! /bin/bash
echo "Please input 2 nums: "
read a b
if [ -z $b ]; then
echo b is zero !
fi
if [ -n $b ]; then
echo b is non-zero !
fi
when run the script, only input 1 number, and leave the other empty, then b is supposed to be null. but the result is both echo is printed.
-laptop:~$ ./test.sh
Pleaes input 2 nums:
5
b is zero !
b is non-zero !
b is both null and non-null ?! Could anyone comment on this ? Thanks !
~
Replace
if [ -z $b ]; then
with
if [ -z "$b" ]; then
And do the same in the other if condition as well.
See http://tldp.org/LDP/abs/html/testconstructs.html for some interesting tests.
It's all in the quotes. I don't remember where, but someone explained this recently on SO or USE - Without the quotes it doesn't actually do an empty/non-empty string test, but just checks that -n or -z are non-empty strings themselves. It's the same test that makes this possible:
$ var=-n
$ if [ "$var" ]
then
echo whut
fi
Returns whut.
This means you can also have a sort of functional programming:
$ custom_test() {
if [ "$1" "$2" ]
then
echo true
else
echo false
fi
}
$ custom_test -z "$USER"
false
$ custom_test -n "$USER"
true
The -n test requires that the string be quoted within the test brackets. Using an unquoted string with ! -z, or even just the unquoted string alone within test brackets normally works, however, this is an unsafe practice. Always quote a tested string.
$ b=''
$ [ -z $b ] && echo YES # after expansion: `[ -z ] && echo YES` <==> `test -z && echo YES`
YES
$ [ -n $b ] && echo YES # after expansion: `[ -n ] && echo YES` <==> `test -n && echo YES`
YES
test against nothing, yield true.