3

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
  • Add output of hexdump -C a and hexdump -C b to your question. Commented Nov 22, 2018 at 18:41
  • 1
    $(< file) is a Bash extension. For POSIX sh, $(cat file) instead. Commented Nov 22, 2018 at 19:01
  • shellcheck.net would tell you "Warning (SC2039): In POSIX sh, $(<file) to read files is undefined." if you used the #!/bin/sh shebang. Commented Nov 22, 2018 at 19:02

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

1 Comment

Could also do just echo $((a*b)).
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 

2 Comments

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 $(<a).
To your first point, try /sbin/sh on Solaris 2.10. To your second point, try /bin/sh on Fedora 26.
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. 

1 Comment

Notice that process substitution (<(...)) isn't the same as command substitution ($(...)).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.