Program tester, bash - c

I'm trying to test a program (tp3) with several input files and printing the output in another file. So I've designed the following bash script name runner to do everything at the same time:
#!/bin/bash
rm $2
clear
FILES=(`ls ${1}`)
cmd='./tp3'
for f in ${FILES[*]}
do
echo "$f"
echo "--------------<$f>--------------" >> $2
$cmd < $1$f 2>> $2 >> $2
done
Everytime I run this script I get the following error:
./runner: line 10: $2: ambiguous redirect
./runner: line 11: testtest: No such file or directory
To run the bash script I do:
./runner test
What is wrong in the script?
Modifications to make it work:
First of all I've quoted the variables, then I've replaced the second argument "$2" for a file named "TEST" and now everything is working just fine.
New code:
#!/bin/bash
rm TEST
clear
FILES=(`ls *.in`)
cmd='./tp3'
for f in ${FILES[*]}
do
echo "$f"
echo "--------------<"$f">--------------" >> "TEST"
"$cmd" < "$1$f" >> "TEST" 2>> "TEST"
done
Thanks everyone for your help.

You are running ./runner test in which test is $1 and $2 is empty. Your redirection is therefor illegal. Also try to couple stdout and stderr when pointing to the same output. This can be done as follows: command arguments > output 2>&1. This will send stderr output to where ever the stdout output is sent.
Also, as Wintermute pointed out: quote variables. Spaces in variables will make it be interpreted as separate arguments. e.g. command $1 supplies two arguments to command if $1 equals some string for example.
This translates into the following: you use $f if this contains a space it will split the argument and everything after the space will be treated as extra arguments or commands rather than one single argument.

Related

How to write bash function to print and run command when the command has arguments with spaces or things to be expanded

In Bash scripts, I frequently find this pattern useful, where I first print the command I'm about to execute, then I execute the command:
echo 'Running this cmd: ls -1 "$HOME/temp/some folder with spaces'
ls -1 "$HOME/temp/some folder with spaces"
echo 'Running this cmd: df -h'
df -h
# etc.
Notice the single quotes in the echo command to prevent variable expansion there! The idea is that I want to print the cmd I'm running, exactly as I will type and run the command, then run it!
How do I wrap this up into a function?
Wrapping the command up into a standard bash array, and then printing and calling it, like this, sort-of works:
# Print and run the passed-in command
# USAGE:
# cmd_array=(ls -a -l -F /)
# print_and_run_cmd cmd_array
# See:
# 1. My answer on how to pass regular "indexed" and associative arrays by reference:
# https://stackoverflow.com/a/71060036/4561887 and
# 1. My answer on how to pass associative arrays: https://stackoverflow.com/a/71060913/4561887
print_and_run_cmd() {
local -n array_reference="$1"
echo "Running cmd: ${cmd_array[#]}"
# run the command by calling all elements of the command array at once
${cmd_array[#]}
}
For simple commands like this it works fine:
Usage:
cmd_array=(ls -a -l -F /)
print_and_run_cmd cmd_array
Output:
Running cmd: ls -a -l -F /
(all output of that cmd is here)
But for more-complicated commands it is broken!:
Usage:
cmd_array=(ls -1 "$HOME/temp/some folder with spaces")
print_and_run_cmd cmd_array
Desired output:
Running cmd: ls -1 "$HOME/temp/some folder with spaces"
(all output of that command should be here)
Actual Output:
Running cmd: ls -1 /home/gabriel/temp/some folder with spaces
ls: cannot access '/home/gabriel/temp/some': No such file or directory
ls: cannot access 'folder': No such file or directory
ls: cannot access 'with': No such file or directory
ls: cannot access 'spaces': No such file or directory
The first problem, as you can see, is that $HOME got expanded in the Running cmd: line, when it shouldn't have, and the double quotes around that path argument were removed, and the 2nd problem is that the command doesn't actually run.
How do I fix these 2 problems?
References:
my bash demo program where I have this print_and_run_cmd function: https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/bash/argument_parsing__3_advanced__gen_prog_template.sh
where I first documented how to pass bash arrays by reference, as I do in that function:
Passing arrays as parameters in bash
How to pass an associative array as argument to a function in Bash?
Follow-up question:
Bash: how to print and run a cmd array which has the pipe operator, |, in it
If you've got Bash version 4.4 or later, this function may do what you want:
function print_and_run_cmd
{
local PS4='Running cmd: '
local -
set -o xtrace
"$#"
}
For example, running
print_and_run_cmd echo 'Hello World!'
outputs
Running cmd: echo 'Hello World!'
Hello World!
local PS4='Running cmd: ' sets a prefix for commands printed by the shell when the xtrace option is on. The default is + . Localizing it means that the previous value of PS4 is automatically restored when the function returns.
local - causes any changes to shell options to be reverted automatically when the function returns. In particular, it causes the set -o xtrace on the next line to be automatically undone when the function returns. Support for local - was added in Bash 4.4.
From man bash, under the local [option] [name[=value] ... | - ] section (emphasis added):
If name is -, the set of shell options is made local to the function in which local is invoked: shell options changed using the set builtin inside the function are restored to their original values when the function returns.
set -o xtrace (which is equivalent to set -x) causes the shell to print commands, preceded by the expanded value of PS4, before running them.
See help set.
Check your scripts with shellcheck:
Line 2:
local -n array_reference="$1"
^-- SC2034 (warning): array_reference appears unused. Verify use (or export if used externally).
Line 3:
echo "Running cmd: ${cmd_array[#]}"
^-- SC2145 (error): Argument mixes string and array. Use * or separate argument.
^-- SC2154 (warning): cmd_array is referenced but not assigned.
Line 5:
${cmd_array[#]}
^-- SC2068 (error): Double quote array expansions to avoid re-splitting elements.
You might want to research https://github.com/koalaman/shellcheck/wiki/SC2068 . We fix all errors and we get:
print_and_run_cmd() {
local -n array_reference="$1"
echo "Running cmd: ${array_reference[*]}"
# run the command by calling all elements of the command array at once
"${array_reference[#]}"
}
For me it's odd to pass an array by reference in this case. I would pass the actual values. I often do:
prun() {
# in the style of set -x
# print to stderr, so output can be captured
echo "+ $*" >&2
# or echo "+ ${*#Q}" >&2
# or echo "+$(printf " %q" "$#")" >&2
# or echo "+$(/bin/printf " %q" "$#")" >&2
"$#"
}
prun "${cmd_array[#]}"
How do I fix these 2 problems?
Incorporate into your workflow linters, formatters and static analysis tools, like shellcheck, and check the problems they point out.
And quote variable expansion. It's "${array[#]}".
You can achieve what you want with DEBUG trap :
#!/bin/bash
set -T
trap 'test "$FUNCNAME" = print_and_run_cmd || trap_saved_command="${BASH_COMMAND}"' DEBUG
print_and_run_cmd(){
echo "Running this cmd: ${trap_saved_command#* }"
"$#"
}
outer(){
print_and_run_cmd ls -1 "$HOME/temp/some folder with spaces"
}
outer
# output ->
# Running this cmd: ls -1 "$HOME/temp/some folder with spaces"
# ...
I really like #pjh's answer, so I've marked it as correct. It doesn't fully answer my original question though, so if another answer comes along that does, I may have to change that. Anyway, see #pjh's answer or a full explanation of how the below code works, and what all those lines mean. I've helped edit that answer with some of the sources from man bash and help set.
I'd like to change the formatting and provide some more examples, however, to show that variable expansion does take place within the command. I'd also like to provide one version which passes by reference, and one which does not, so you can choose the call style which you like best.
Here are my examples, showing both call styles (print_and_run1 cmd_array and print_and_run2 "${cmd_array[#]}"):
#!/usr/bin/env bash
# Print and run the passed-in command, which is passed in as an
# array **by reference**.
# See here for a full explanation: https://stackoverflow.com/a/71151669/4561887
# USAGE:
# cmd_array=(ls -a -l -F /)
# print_and_run1 cmd_array
print_and_run1() {
local -n array_reference="$1"
local PS4='Running cmd: '
local -
set -o xtrace
# Call the cmd
"${array_reference[#]}"
}
# Print and run the passed-in command, which is passed in as members
# of an array **by value**.
# See here for a full explanation: https://stackoverflow.com/a/71151669/4561887
# USAGE:
# cmd_array=(ls -a -l -F /)
# print_and_run2 "${cmd_array[#]}"
print_and_run2() {
local PS4='Running cmd: '
local -
set -o xtrace
# Call the cmd
"$#"
}
cmd_array=(ls -1 "$HOME/temp/some folder with spaces")
print_and_run1 cmd_array
echo ""
print_and_run2 "${cmd_array[#]}"
echo ""
Sample run and output:
eRCaGuy_hello_world/bash$ ./print_and_run.sh
Running cmd: ls -1 '/home/gabriel/temp/some folder with spaces'
file1.txt
file2.txt
Running cmd: ls -1 '/home/gabriel/temp/some folder with spaces'
file1.txt
file2.txt
This seems to work too:
print_and_run_cmd() {
echo "Running cmd: $1"
eval "$cmd"
}
cmd='ls -1 "$HOME/temp/some folder with spaces"'
print_and_run_cmd "$cmd"
Output:
Running cmd: ls -1 "$HOME/temp/some folder with spaces"
(result of running the cmd is here)
But now the problem is, if I want to print an expanded version of the cmd too, to verify that part worked properly, I can't, or at least, don't know how.

Using bash script to use return status of one C program as arg to another

I have two compiled .c files and I am trying to take the exit status of the first and use it as an arg to the next one.
#!/bash/bin
./decipher $1
key = $?
./cipher $key $1 $2
This is what I am typing on the command line
$ ./decryption_tool.bs ceaser1.txt output.txt
.ceaser1.txt is an encrypted message and decipher will return a key and cipher should take that key and unencrypt it. Both files work outside of the script as well, but when i use the script I get this error.
./decryption_tool.bs: line 4: key: command not found
./decryption_tool.bs: line 5: 5937 Segmentation fault (core dumped) ./cipher $key $1 $2
Thanks for any help.
Few things wrong:
You cannot have spaces when you set variables in shell script - it should be key=$? instead of key = $?
Invalid shebang: not #!/bash/bin, but #!/bin/bash or even better #!/usr/bin/env bash
It would be good to surround variables in double quotes: ./cipher "$key" "$1" "$2"
Why file extension is .bs? For shell script it's .sh and Bash doesn't have its own separate version

Write a Shell script to check if a C program print Hello World

I am trying to write a Shell script to compile a c program and check if it prints "Hello World!" to stdout.
This is what I have now.
The second argument check in the diff is a file with only "Hello World!" in it.
#!/bin/sh
gcc -Wall hello.c -o hello
./hello > outfile
if diff outfile check >/dev/null ; then
echo Same
else
echo Different
fi
My Ubuntu keeps showing this error after I try to run the script.
"./build.sh: 8: ./build.sh: Syntax error: "fi" unexpected (expecting "then")"
Please let me know what is wrong with my code.
As I said in a comment, I suspect your script file has DOS/Windows line endings; see this question.
Also, since the comparison you're making is just a single line, it's easy to do as a string comparison (rather than saving the strings to files, and using a file comparer like diff or cmp):
#!/bin/sh
gcc -Wall hello.c -o hello
if [ "$(./hello)" = 'Hello World!' ]; then
echo Same
else
echo Different
fi
Notes: the $( ) is a "command substitution"; it runs the contents as a command, captures its output, and substitutes it into the command line at that point. The double-quotes around that prevent it from being split into "words" (i.e. "Hello" and "World!" being treated as separate words) and things that looks like filename wildcards being expanded into lists of matching files. (You should almost always put double-quotes around command and variable substitutions, because these effects tend to cause weird problems.)
You can use cmp program to check whether the output file is what you've expected. Here is an example from my test script:
cmp "file.output" "file_cmp.output"
if [[ $? -eq 0 ]] ; then # this line checks if cmp exit value is 0 (everything is fine)
echo "OK!"
else
echo "ERROR!"
fi
Use cmp instead of diff in scripts.
The simplest, using <(...) bash extension:
if cmp -s <(./hello) <(echo "Hello world!"); then
echo Same
else
echo Different
fi

Bash: Read args from stdin into array

Problem Description
Given a plaintext file args.in containing one line of command line arguments, read them into an array.
Problem Formulation
We have 4 files:
args.in:
"ab" c
refimpl.sh:
read -r line
bash -c "bash showargs.sh $line"
arrayimpl.sh:
arr=()
# BEGIN-------------------------
# Input comes from stdin.
# You need to set arr here.
# END---------------------------
echo "${#arr[#]}"
for i in "${arr[#]}"; do
echo "$i"
done
showargs.sh:
echo "$#"
for i in "$#"; do
echo "$i"
done
Put them into the same folder. We want you to implement arrayimpl.sh so that
bash refimpl.sh < args.in
and
bash arrayimpl.sh < args.in
give the same output.
Your solution should only contain a single file arrayimpl.sh.
Output Example
2
ab
c
This problem is a better formulation of this but not a dup of this. Some solutions work there but not here. For example, when we have the following input:
args.in:
"a\"b" c
There is no known solution yet.
The expected solution for this assignment is something equivalent to:
eval "arr=( $(cat) )"
This evaluates input as shell words, which is what refimpl.sh also does.
This is for toy problems and homework assignments only. Real software should not use executable code as a data format.

Why does read -a fail in zsh

If I type:
echo "1 the
dquote> 2 quick
dquote> 3 brown" | while read -a D; do echo "${D[1]}--${D[0]}"; done
in bash it says:
the--1
quick--2
brown--3
but in zsh it says:
zsh: bad option: -a
Why? And what should I do instead?
In both shells read is a builtin. It shares the same purpose, but the implementation and options differ.
In order to read in an array in zsh, read requires the option -A (instead of -a):
echo "1 the
2 quick
3 brown" | while read -A D; do echo $D[2]--$D[1]; done
Note: There are many more differences between zsh and bash:
In zsh arrays are numbered from one by default, in bash they start from zero.
echo $ARRAY prints outputs all elements in zsh but only the first element in bash
To print the third element of an array in sh you can use echo $ARRAY[3]. In bash braces are needed to delimit the subscript, also the subscript for the third element is 2: echo ${ARRAY[2]}.
In zsh you usually do not need to quote parameter expansions in order to handle values with white spaces correctly. For example
FILENAME="no such file"
cat $FILENAME
will print only one error message in zsh:
cat: 'no such file': No such file or directory
but three error messages in bash:
cat: no: No such file or directory
cat: such: No such file or directory
cat: file: No such file or directory
In zsh the builtin echo evaluates escape codes by default. In bash you need to pass the -e argument for that.
echo 'foo\tbar'
zsh:
foo bar
bash:
foo\tbar
…
Generally, it is important to keep in mind that, while zsh and bash are similar, they are far from being the same.

Resources