Core Problem
Unlike many other programming languages, Bourne shell syntax has certain peculiarities, especially regarding whitespace in certain places. And that's what's messing you up here:
It's important to remember that [
is a command, not a syntax feature (footnote 1), and that ]
is the final argument to that command.
In Bourne (and similar/derivative) shell, whitespace (footnote 2) separates commands and their arguments, and while
/if
/until
keywords actually just invoke whatever command comes after those keywords, and act based on the return value.
Another unusual nature of Bourne shell that looks like a problem in your code is variable assignments, which are also specific about whitespace: There must be no whitespace between the variable name, the =
sign, and the thing being assigned.
Also, the output of commands does not automatically go into variables like in most languages: in order to do that, you need to use "command substitution". The new, nestable, and more widely recommended syntax for that is $(command to run)
(footnote 3).
Examples In Your Code
So applying the above to your code:
while [ $key = "q"]; do
..the problem is the "q"]
. The shell sees that as one argument to the test
/[
command: q]
(the quotes also do nothing here: the shell doesn't have variable types: everything is a string, except in a few contexts, so q
is already a string - quotes are for escaping special characters). I would fix it like this:
while [ "$key" = q ]; do
..I also quoted $key
in order to make sure it's always interpreted correctly (footnote 4). The same problem (and one other) happens here:
if[$dateTex !eq $datePdf]
..remember that the shell relies on whitespace to tell where one thing ends and another begins (again, footnote 2): if you say if[$some_var
, it will first replace $some_var
with some_text
, getting if[some_text
, and then it will try to execute a command named if[some_text
. So first, you want if
separated by a space from the [
.
This is why your code prints the error that it does: bash expects a then
only after it sees an if
, but it never sees an if
, it sees an if[$dateTex
, which gets parsed as an entirely different command/token.
Same principle is why you want to separate [
from $dateTex
, and $datePdf
from ]
, with a space.
Finally, !eq
is not a valid argument/test that the [
command recognizes: -eq
interprets the arguments around it as integers (which are still strings as far as the shell is concerned going into the [
command, whether or not you quote them), and compares them for equality. You had the right idea with !
as the negation operator, but as with so many other things in the shell, it must be a separate argument to [
, before the others. So you'd want:
if [ ! "$dateTex" -eq "$datePdf" ]
..or you can use the shell's negation operator, instead of the [
command's (again, watch the spacing - it has to be a separate token/field when the shell syntax splits on whitespace):
if ! [ "$dateTex" -eq "$datePdf" ]
Also, per the other answer: =
just checks for the two argument strings being the same (as opposed to interpreting the argument strings as integers before comparing them, like -eq
does), and it also supports a special-case negated !=
operator to check for two strings being different. If a string comparison is sufficient in your usecase, then you can use one of these instead:
if [ "$dateTex" != "$datePdf" ] # != operator
if [ ! "$dateTex" = "$datePdf" ] # test ! operator with test = operator
if ! [ "$dateTex" = "$datePdf" ] # shell ! operator with test = operator
Now onto these lines with variable assignments:
dateTex = grep $1.tex| cut -b 43 - 54
datePdf = grep $1.pdf| cut -b 43 - 54
First, the way the shell will parse dateTex = grep $1.tex
is to try to run the command dateTex
with the arguments =
, grep
and one or more arguments depending on what $1
expands to (again, footnote 4). So, second, you should quote the $1
. And for the cut
command, the -b
option takes the list as one argument, so you want to combine 43-54
into one (no whitespace). Anyway, it would appear you actually wanted to use command substitution, so you want something like this:
dateTex=$(grep "$1".tex | cut -b 43-54)
..and do the same for the other line. Finally, grep filename
seems wrong: grep
's first argument (besides options) is supposed to be the pattern to search/match for. So as you have it written in your example code, it will read from and search stdin for whatever pattern "$1".tex
expands to. You likely want to search the file "$1".tex
for a pattern, so you want grep pattern "$1".tex
instead.
Having gone over all of this, I suspect that either your actual code is different than what you've posted, or it's printing more error messages than just that one.
Footnotes
[1] The command is test
, and [
is just another name the test
command can be called with, the only difference being is that it expects ]
to be the last argument when it's called as ]
. Technically, in most implementations it will be a shell builtin, but the syntax rules are the same.
[2] Technically it's whatever is in the IFS
(Internal Field Separator) variable, but that's usually spaces, tabs, and newlines (with newlines being slightly special for other reasons).
[3] The old, but not nestable syntax is `command to run`
- you'd use this if you wanted your script to run on very old or somewhat not-standard shells, like Solaris 10's /bin/sh
. Personally, I think this syntax is perfectly fine and I don't think nesting (the only real advantage of the new syntax) is ever necessary in scripts - though I can see how it makes interactive command-line use easier.
[4] When a variable is not quoted, whitespace (or whatever is in $IFS
, per footnote 1) in a variable is evaluated after the variable is substituted, and "field splittling" is performed on the entire line again. So if $key
is unset, blank, or just only whitespace characters, it will basically "disappear" from the command if it's not quoted. Add if there's a mix, e.g. $key
contains abc def
, it'll become two arguments abc
and def
. The same principle applies to the entire line with a substitution, so if $key
contains abc def
and the command line is foo$keybar
, the shell splits the string into fooabc defbar
(run command fooabc
with one argument defbar
). None of this happens if the variable is quoted: "$key"
.