2

Setup:

File a contains:

22

File b contains:

12

I have shell script 1.sh:

#!/bin/sh

a=$(< a)
b=$(< b)
echo $(($a*$b)) > c

The script should get values from file a and b, multiply them *, and save to file c. However after setting permission $ chmod a+rx 1.sh and running it $ ./1.sh it returns an error:

./1.sh: 5: ./1.sh: arithmetic expression: expecting primary: "*"

This error occurs because the variables $a and $b doesn't get value form files a and b.

  • If I echo $a and echo $b it returns nothing;
  • If I define a=22 and b=12 values in the script it works;
  • I also tried other ways of getting contents of files like a=$(< 'a'), a=$(< "a"), a=$(< "~/a"), and even a=$(< cat a). None of those worked.

Plot Twist:

However, if I change shebang line to #!/bin/bash so that Bash shell is used - it works.

Question:

How to properly get data from file in sh?

3 Answers3

1

Ignore everything from file a and b but numbers:

#!/bin/sh

a=$(tr -cd 0-9 < a)
b=$(tr -cd 0-9 < b)
echo $(($a*$b))

See: man tr

Cyrus
  • 84,225
  • 14
  • 89
  • 153
0

There are many ways. One obvious way is to pipe in a sub-process by Command Substitution:

A=$(cat fileA.txt)   # 22
B=$(cat fileB.txt)   # 12
echo $((A*B))
# <do it in your head!>

If there are any other problems with multiple lines, you need to look into how to use the Bash variable $IFS (Internal File Separator). Usually IFS is defined by: IFS=$' \t\n', so if you need to be able to reliably read lines endings from both Windows and Linux EOL's you may need to modify it.


ADDENDUM:

Process Substitution

Bash, Zsh, and AT&T ksh{88,93} (but not pdksh/mksh) support process substitution. Process substitution isn't specified by POSIX. You may use NamedPipes to accomplish the same things. Coprocesses can also do everything process substitutions can, and are slightly more portable (though the syntax for using them is not).

This also means that most Android OS does not allow process substitution, since their shells are most often based on mksh.

From man bash:

Process Substitution
   Process  substitution  allows  a process's input or output to be referred to using a filename.  It takes the form of <(list) or >(list).  The
   process list is run asynchronously, and its input or output appears as a filename.  This filename is passed as an  argument  to  the  current
   command  as  the result of the expansion.  If the >(list) form is used, writing to the file will provide input for list.  If the <(list) form
   is used, the file passed as an argument should be read to obtain the output of list.  Process substitution is supported on systems that  sup-
   port named pipes (FIFOs) or the /dev/fd method of naming open files.

   When  available, process substitution is performed simultaneously with parameter and variable expansion, command substitution, and arithmetic
   expansion.
not2qubit
  • 14,531
  • 8
  • 95
  • 135
0

If you're looking for "true" Bourne-Shell compatibility, as opposed to Bash's emulation, then you have to go old school:

#!/bin/sh
a=`cat a`
b=`cat b`
expr $a \* $b > c

I tried your original example under #!/bin/sh on both macOS and Linux (FC26), and it behaved properly, assuming a and b had UNIX line-endings. If that can't be guaranteed, and you need to run under #!/bin/sh (as emulated by bash), then something like this will work:

#!/bin/sh
a=$(<a)
b=$(<b)
echo $(( ${a%%[^0-9]*} * ${b%%[^0-9]*} )) > c
DjPadz
  • 36
  • 1
  • 4
  • 1
    Even the most ancient shells support command substitution with `$(...)` instead of backticks. You have to go back many decades to find one that doesn't. POSIX definitely supports it. On the other hand, even when `/bin/sh` is Bash, it does *not* support `$( – Benjamin W. Nov 23 '18 at 03:16
  • To your first point, try /sbin/sh on Solaris 2.10. To your second point, try /bin/sh on Fedora 26. – DjPadz Nov 23 '18 at 03:33