Why does this bash script not work as I want - bash

I have the following bash script
for s in $(ls -1 fig/*.py); do
name=`basename $s .py`
if [ -e "fig/$name.pdf" -o "fig/$name.pdf" -ot "fig/$name.data" -ot "fig/$name.py" ]; then
$s
fi
done
It is supposed to invoke a python script if the output pdf does not exist, or the pdf is older than the py or data file.
Unfortunaly, the script is now never invoked. What did I do wrong?
EDIT
Thanks Benoit! My final script is:
for s in fig/*.py ; do # */ fix highlighting
name="$(basename "$s" .py)"
if test ! -e "fig/$name.pdf" -o "fig/$name.pdf" -ot "fig/$name.data" -o "fig/$name.pdf" -ot "fig/$name.py"
then
"$s"
fi
done

Many mistakes. See bash pitfalls especially first one (never rely on ls to get a list of files).
It also seems that the test is not well-formed: Two -ot in a row seem strange to me. Using -o to perform an OR instead of an AND seems weird also.
for s in fig/*.py ; do # */ for code colouring on SO
name="$(basename "$s" .py)"
if test -e "fig/$name.pdf" -o "fig/$name.pdf" -ot "fig/$name.data" -ot "fig/$name.py"
then
"$s"
fi
done

Related

Check file size is larger than 1.6GB

I have the following step in my Bash code which checks to see if a file is bigger than 1.6GB:
#!/bin/sh
SIZE_THRESHOLD=1717986918
if [[ $(find /home/methuselah/file -type f -size +${SIZE_THRESHOLD}c 2>/dev/null) ]]; then
somecmdbecausefileisbigger
else
somecmdbecausefileissmaller
fi
The command somecmdbecausefileisbigger is never triggered even when the file size is greater than 1.6GB. Why is this?
I don't know why your find command doesn't work, but I do know a simpler way to do this:
if [ $(stat -f %z /home/methuselah/file) -gt ${SIZE_THRESHOLD} ]; then
Though unfortunately you have to replace -f %z with -c %s on Linux (the former works on BSD and MacOS).
Just use stat (note that your version of stat may differ; check your man page for details):
if [ "$(stat -c '%s' /home/methuselah/file)" -gt "$SIZE_THRESHOLD" ]; then

Check if image is optimized with optipng

i use optipng to optimize the images of my project.
And i want to automate this by checking if an image is not optimized to run a script.
if [[ $FILE == *.png ]]
then
BASEFILE=$(basename $FILE)
optipng -simulate -quiet $FILE -log $$.log
TEST=$(cat $$.log |grep "optimized" |wc -l)
....
The problem is that i cannot write my output to a new file and i need to do the check without creating a file.
Is there a way the output of the ptipng -simulate -quiet $FILE to be assigned in a variable and then make the check?
According to this link you just can:
TEST=$(optipng -simulate -quiet "$FILE" - | grep "optimized" | wc -l)
# or just handle grep return valud
if optipng -simulate -quiet "$FILE" - | grep -q "optimized"; then
echo "It is optimized"
fi
Substituting file name for - makes optipng output to standard output.
I have never used optipng and have no idea whether -log switch is necessary, but You can rewrite Your script like this:
if [ "${image}" = "*.png" ]; then
local baseName="$(basename "{image})" #I assume this code is executed inside a function, that's why I used local
local output
if ! output="$(optipng -simulate -quiet "${baseName})"; then #I assume that optipng returns error code in case of failure. In such case, when errior occures, "if" logic will be executed
printf "Failed to test file ${baseName}"
return
fi
if ! printf '%s' "${output}" | grep -qi "optimized"; then #i for case insensitive, q for quiet. I ommited the wc -l because I did not see any reason for counting
printf "Not optimized"
fi
fi

Shell scripting - if statement difference

This a question of an exercise:
What is the difference between the two "if" instructions?
#!/bin/bash
rm tmp
echo -n > tmp
for f in $*
do
if test ! -f $f
then
echo $f does not exist as a file
continue
fi
rm $f
if [ ! -f $f ]
then
echo $f has been deleted successfully
fi
ls $f >> tmp
done
x='cat tmp | grep -c ^.*$'
echo result: $x
The square brackets are a synonym for the test command, instead of if test ! -f $f we can use if [ ! -f $f ]. Note: test is a command which takes expression and test or evaluates.
No difference. test and [ are builtins in most (all?; definitely in dash, bash, yash, ksh, zsh, fish) shells now:
$ type [
[ is a shell builtin
$ type test
test is a shell builtin
There's also executable versions of them:
$ which [
/usr/bin/[
$ which test
/usr/bin/test
Unlike cd, test (or [) doesn't need to be a builtin (at least not for the common options -- some shells' extensions require it to be a builtin), but the fork+exec overhead of an external executable is too much for the little things that test tests.

Shell script to iterate files. Got error on line 3

#!/bin/bash
for dir in /home/username/git/*/
do
for file in "$dir"/*
do
if [[ -f $file ]]
then
echo "$file"
fi
done
done
When I try to run it. I got
syntax error near unexpected toke' `do
'rocTest.sh: line 3: `do
Why?
Use "$file" (with quotes) consistently to deal with "problematic" file names; in particular if [[ -f $file ]] should be
if [[ -f "$file" ]] ...
Note that bash is not always in /bin (e.g. FreeBSD places it in /usr/local/bin); for wider portability, either use
#!/usr/bin/env bash
or #!/bin/sh and make sure to remove bash-isms (e.g. use checkbashisms on Debian/Ubuntu). E.g. write if test -f "$file" instead of [[ -f "$file" ]]

Using $# properly

I am trying to write a tiny script that accepts any number of command line arguments that prints out the rwx permissions for a file (not directory)
What I have is
file=$#
if [ -f $file ] ; then
ls -l $file
fi
This accepts only one command line argument however. Thanks for any help.
Here is a demonstration of the some of the differences between $* and $#, with and without quotes:
#/bin/bash
for i in $*; do
echo "\$*: ..${i}.."
done; echo
for i in "$*"; do
echo "\"\$*\": ..${i}.."
done; echo
for i in $#; do
echo "\$#: ..${i}.."
done; echo
for i in "$#"; do
echo "\"\$#\": ..${i}.."
done; echo
Running it:
user#host$ ./paramtest abc "space here"
$*: ..abc..
$*: ..space..
$*: ..here..
"$*": ..abc space here..
$#: ..abc..
$#: ..space..
$#: ..here..
"$#": ..abc..
"$#": ..space here..
How about this one:
for file
do
test -f "$file" && ls -l "$file"
done
The for loop by default will work on $#, so you don't have to mention it. Note that you will need to quote "$file" in case if the file name has embedded space. For example, if you save your script to 'myll.sh':
$ myll.sh "My Report.txt" file1 file2
Then "My Report.txt" will be passed in as a whole token instead of 2 separate tokens: "My" and "Report.txt"
The variable you want is indeed $# - this contains all command-line arguments as separate words, each passed on intact (no expansion). ($* treats all of them as a single word - good luck sorting it out if you have spaces in filenames).
You can loop, if you like. This is easily expanded to more complex actions than ls.
for file in "$#"; do
if [ -f "$file" ]; then
ls -l "$file"
fi
done
Note: you should quote $# to protect any special characters inside! You should also quote $file for the same reason - especially inside the test. If there is an empty string in $#, file will also be empty, and without quotes, -f will attempt to act on the ']'. Errors ensue.
Also, if all you need to do is ls (skipping your if) you can just do this:
ls -l "$#"
You could usefully loop over any files specified like this:
for file in "$#"; do
ls -l "$file"
done
If you want to double-check that the name specified is not a directory, you could do this:
for file in "$#"; do
if [ ! -d "$file" ]; then
ls -l "$file"
fi
done
the bash variable for all arguments passed to a script is "$*". Try:
for file in $*; do
if [ -f $file ] ; then
ls -l $file
fi
done
(not tested)

Resources