Multiple errors unix shell - shell

I need to write a shell script that for each file from the command line will output the number of words that are longer than a number k read from keyboard and he output must be ordered by the number of words. I don't know if what I've written so far solves the problem because I get these errors and I don't know how to fix them:
./b.sh: command substitution: line 19: unexpected EOF while looking for matching `''
./b.sh: command substitution: line 20: syntax error: unexpected end of file
./b.sh: command substitution: line 19: unexpected EOF while looking for matching `''
./b.sh: command substitution: line 20: syntax error: unexpected end of file
./b.sh: line 19: 5: command not found
#!/bin/bash
case $# in
0)
echo "The script cannot be executed due to missing arguments!"
exit 1
;;
*)
read -p "Enter the length: " k
n=$k
for file in $#; do
if [ `file $file | egrep "exec|data|empty|reloc|cannot open" > /dev$
continue
else
var=`tr ' ' '\n' < $file | grep -c '^.\{`expr $n`\}$'`
echo $file": "$var
fi
done | sort -n
;;
esac

Like the error message tells you, you are missing the pair for a ` delimiter. Also, you have attempted to nest backticks, but done it incorrectly.
In more detail,
#!/bin/bash
case $# in
0)
echo "The script cannot be executed due to missing arguments!"
exit 1
;;
(Strictly, error messages should go to stderr; echo "error" >&2. Also, it is good form to include $0 in the error message; echo "$0: error" >&2 so that you can tell from nested scripts which one is having a problem.)
*)
read -p "Enter the length: " k
n=$k
(Why do you need to copy the value to n?)
for file in $#; do
(This should properly use "$#" in order for file names with spaces etc to work correctly.)
if [ `file $file | egrep "exec|data|empty|reloc|cannot open" > /dev$
continue
You are mixing syntax here. The general syntax is if command; then action; fi. One commonly used command is [ in which case its argument list must end with a closing ], but here, it seems that if should simply examine the exit code from grep. Also, /dev$ looks like an erroneous transcription. Assuming this was meant to be /dev/null, better simply use grep -q.
Also, this is where you have a backtick which starts a command sequence, but no closing backtick to end it.
Because "cannot open" is an error message, it wil not be in standard output. Either copy stderr to stdout, or handle separately. One option is to invert the logic, so that both a missing file and the absence of the strings you are looking for translates to failure.
So,
if ! file "$file" | grep -vEq "exec|data|empty|reloc"; then
continue
Notice also how "$file" needs to be double quoted, again to cope correctly with file names containing wildcard characters, whitespace, etc.
else
var=`tr ' ' '\n' < $file | grep -c '^.\{`expr $n`\}$'`
This is where the nesting is supposed to happen, but your syntax is incorrect. echo `echo foo`echo `echo bar` evaluates to echo fooecho bar, and nested evaluation would be echo `echo foo\`echo \`echo bar` -- or more readably and portably, use echo $(echo foo$(echo )echo bar) where the nesting is obvious and unambiguous. But anyway, your expr is completely superfluous; just interpolate the value if $n (or $k!) directly. So;
var=$(tr ' ' '\n` <"$file" | grep -c "^\{$k\}")
The rest if the script looks correct.
echo $file": "$var
fi
done | sort -n
;;
esac

Related

Check if greps return containsa certain word

When I execute a command like this:
~/foo/bar | grep <someID>
the output is in this format:
"<someID>": "ENABLED",
The status "ENABLED" can also be something different but i want to check if my ID is ENABLED
I tried it with this:
output= ~/foo/bar | grep <someID>
echo $output
if [[ $output =~ .*ENABLED.* ]]; then // Method 1
echo "is enabled"
else
echo "not enabled"
fi
case "$output" in // Method 2
*ENABLED*)
echo "is enabled"
;;
esac
but i always get that it is not enabled.
Basically what i want to do is to check if the return of grep contains the word "ENABLED".
Any ideas?
In straight-forward way:
some_id=2 # for example
if ~/foo/bar | grep -q "$some_id"'": *"ENABLED"' ; then
echo "is enabled"
else
echo "not enabled"
fi
you need to put backticks in your first line of code. if you did not do that, then that might be your mistake, because $output is undefined therefore empty string.
output=`~/foo/bar | grep <someID>`
Both your strategies for checking the content of $output would work if you just got the appropriate data into the variable. There are multiple ways to get command output into a variable. The following are all functionally equivalent.
output=`~/bin/foo | grep "\"<someID>\""` # deprecated -- use $(...) instead
output="$(~/bin/foo | grep "\"<someID>\"")" # may result in multiple lines of output
read -r output < <(~/bin/foo | grep "\"<someID>\"") # uses "Process Substitution"
The first two of these will work in any old POSIX shell. The first one even works in csh/tcsh. The last one, using Process Substitution, is bash-only (or at least, not POSIX).
But as you've seen in another answer, you probably don't need a variable. You can use a regex to match the whole line that you're interested in:
if ~/foo/bar | grep -q "\"$someID\""'": *"ENABLED"'; then
echo "enabled"
else
echo "disabled"
fi
This construct is safer if there's the possibility that $someID might appear multiple times in your output, with different values.
If you use a case statement, then you'd want to make sure you know what to do if $someID turns up multiple times.
while read -r output; do
case "$output" in
*ENABLED*) echo "enabled" ;;
*DISABLED) echo "disabled" ;;
*) echo "ERROR: Unknown state." >&2 ;;
esac
done < <(~/foo/bar | grep "\"$someID\"")
Try something like this:
some_command > /tmp/some_file.tmp
if grep -q "Some_Text" "/tmp/some_file.tmp";
then
echo "These are not the stormtroopers you are looking for."
else
echo "Stay gold, ponyboy."
fi
# Removing the tmp file is optional, but I'm OCD, so...
rm /tmp/some_file.tmp
This command will for-sure work on a Ubuntu system out of the box, or any *nix system with BASH terminal. You can adapt for your own usage.
Inspired by https://stackoverflow.com/a/28360230/4272202

bash: dealing with strange filenames tail invalid option --1

I want my script to find a file (in the current directory) with the first line equal to START. Then that file should have FILE <file_name> as the last line. So I want to extract the <file_name> - I use tail for this. It works ok for standard file names but cracks for nonstandard file names like a a or a+b-c\ = e with tail reporting tail option used in invalid context -- 1
Here is the beginning of the script:
#!/bin/bash
next_stop=0;
# find the first file
start_file=$(find . -type f -exec sed '/START/F;Q' {} \;)
mv "$start_file" $start_file # << that trick doesn't work
if [ ! -f "$start_file" ]
then
echo "File with 'START' head not found."
exit 1
else
echo "Found $start_file"
fi
# parse the last line of the start file
last_line=$(tail -1 $start_file) # << here it crashes for hacky names
echo "last line: $last_line"
if [[ $last_line == FILE* ]] ; then
next_file=${last_line#* }
echo "next file from last line: $next_file"
elif [[ $last_line == STOP ]] ; then
next_stop=true;
else
echo "No match for either FILE or STOP => exit"
exit 1
fi
I tried to embrace the find output with braces this way
mv "$start_file" $start_file
but it doesn't help
This error is occur to the character of the escape.
You should write it start_file variable in quotes.
last_line=$(tail -1 $start_file) --> last_line=$(tail -1 "$start_file")
For you two examples, you need to escape space and egual in file name (with \ character), and escape escape character too.
So a a have to be a\ a when passing to tail, and a+b-c\ = e have to be a+b-c\\\ \=\ e.
You can use sed to make this replacement.
This example give you an better and easier way to make this replacement :
printf '%q' "$Strange_filename"

How can I get the return value and matched line by grep in bash at once?

I am learning bash. I would like to get the return value and matched line by grep at once.
if cat 'file' | grep 'match_word'; then
match_by_grep="$(cat 'file' | grep 'match_word')"
read a b <<< "${match_by_grep}"
fi
In the code above, I used grep twice. I cannot think of how to do it by grep once. I am not sure match_by_grep is always empty even when there is no matched words because cat may output error message.
match_by_grep="$(cat 'file' | grep 'match_word')"
if [[ -n ${match_by_grep} ]]; then
# match_by_grep may be an error message by cat.
# So following a and b may have wrong value.
read a b <<< "${match_by_grep}"
fi
Please tell me how to do it. Thank you very much.
You can avoid the double use of grep by storing the search output in a variable and seeing if it is not empty.
Your version of the script without double grep.
#!/bin/bash
grepOutput="$(grep 'match_word' file)"
if [ ! -z "$grepOutput" ]; then
read a b <<< "${grepOutput}"
fi
An optimization over the above script ( you can remove the temporary variable too)
#!/bin/bash
grepOutput="$(grep 'match_word' file)"
[[ ! -z "$grepOutput" ]] && (read a b <<< "${grepOutput}")
Using double-grep once for checking if-condition and once to parse the search result would be something like:-
#!/bin/bash
if grep -q 'match_word' file; then
grepOutput="$(grep 'match_word' file)"
read a b <<< "${grepOutput}"
fi
When assigning a variable with a string containing a command expansion, the return code is that of the (rightmost) command being expanded.
In other words, you can just use the assignment as the condition:
if grepOutput="$(cat 'file' | grep 'match_word')"
then
echo "There was a match"
read -r a b <<< "${grepOutput}"
(etc)
else
echo "No match"
fi
Is this what you want to achieve?
grep 'match_word' file ; echo $?
$? has a return value of the command run immediately before.
If you would like to keep track of the return value, it will be also useful to have PS1 set up with $?.
Ref: Bash Prompt with Last Exit Code

Why does my script report ls: not found

I have the following korn script:
#!/bin/ksh
TAPPDATADIR=/hp/qa02/App/IPHSLDI/Data
echo $TAPPDATADIR
if [[ls $TAPPDATADIR/zip_file_MD5_checksum*.txt | wc -l > 1]]
then
exit "asdf"
fi
When I attempt to run it I get:
/hp/qa02/App/IPHSLDI/Data
./iftest.ksh: line 7: [[ls: not found
Why isn't my if statement working?
I'm trying to see if there are multiple checksum files in the Data directory. If there are I want to exit the script.
There are several problems:
There shouldn't be any spaces around = in the assignment.
You need spaces around [[ and ]] in the if statement.
To substitute the result of a command into the test expression, you need to use backticks or $(...).
The parameter to exit should be a number, I think you just want to echo the string.
> performs string comparison, you have to use -gt to perform numeric comparison.
So the full script should be:
#!/bin/ksh
TAPPDATADIR=/hp/qa02/App/IPHSLDI/Data
echo $TAPPDATADIR
if [[ $(ls $TAPPDATADIR/zip_file_MD5_checksum*.txt | wc -l) -gt 1 ]]
then
echo "asdf"
fi

How to handle "--" in the shell script arguments?

This question has 3 parts, and each alone is easy, but combined together is not trivial (at least for me) :)
Need write a script what should take as its arguments:
one name of another command
several arguments for the command
list of files
Examples:
./my_script head -100 a.txt b.txt ./xxx/*.txt
./my_script sed -n 's/xxx/aaa/' *.txt
and so on.
Inside my script for some reason I need distinguish
what is the command
what are the arguments for the command
what are the files
so probably the most standard way write the above examples is:
./my_script head -100 -- a.txt b.txt ./xxx/*.txt
./my_script sed -n 's/xxx/aaa/' -- *.txt
Question1: Is here any better solution?
Processing in ./my_script (first attempt):
command="$1";shift
args=`echo $* | sed 's/--.*//'`
filenames=`echo $* | sed 's/.*--//'`
#... some additional processing ...
"$command" "$args" $filenames #execute the command with args and files
This solution will fail when the filenames will contain spaces and/or '--', e.g.
/some--path/to/more/idiotic file name.txt
Question2: How properly get $command its $args and $filenames for the later execution?
Question3: - how to achieve the following style of execution?
echo $filenames | $command $args #but want one filename = one line (like ls -1)
Is here nice shell solution, or need to use for example perl?
First of all, it sounds like you're trying to write a script that takes a command and a list of filenames and runs the command on each filename in turn. This can be done in one line in bash:
$ for file in a.txt b.txt ./xxx/*.txt;do head -100 "$file";done
$ for file in *.txt; do sed -n 's/xxx/aaa/' "$file";done
However, maybe I've misinterpreted your intent so let me answer your questions individually.
Instead of using "--" (which already has a different meaning), the following syntax feels more natural to me:
./my_script -c "head -100" a.txt b.txt ./xxx/*.txt
./my_script -c "sed -n 's/xxx/aaa/'" *.txt
To extract the arguments in bash, use getopts:
SCRIPT=$0
while getopts "c:" opt; do
case $opt in
c)
command=$OPTARG
;;
esac
done
shift $((OPTIND-1))
if [ -z "$command" ] || [ -z "$*" ]; then
echo "Usage: $SCRIPT -c <command> file [file..]"
exit
fi
If you want to run a command for each of the remaining arguments, it would look like this:
for target in "$#";do
eval $command \"$target\"
done
If you want to read the filenames from STDIN, it would look more like this:
while read target; do
eval $command \"$target\"
done
The $# variable, when quoted will be able to group parameters as they should be:
for parameter in "$#"
do
echo "The parameter is '$parameter'"
done
If given:
head -100 test this "File name" out
Will print
the parameter is 'head'
the parameter is '-100'
the parameter is 'test'
the parameter is 'this'
the parameter is 'File name'
the parameter is 'out'
Now, all you have to do is parse the loop out. You can use some very simple rules:
The first parameter is always the file name
The parameters that follow that start with a dash are parameters
After the "--" or once one doesn't start with a "-", the rest are all file names.
You can check to see if the first character in the parameter is a dash by using this:
if [[ "x${parameter}" == "x${parameter#-}" ]]
If you haven't seen this syntax before, it's a left filter. The # divides the two parts of the variable name. The first part is the name of the variable, and the second is the glob filter (not regular expression) to cut off. In this case, it's a single dash. As long as this statement isn't true, you know you have a parameter. BTW, the x may or may not be needed in this case. When you run a test, and you have a string with a dash in it, the test might mistake it for a parameter of the test and not the value.
Put it together would be something like this:
parameterFlag=""
for parameter in "$#" #Quotes are important!
do
if [[ "x${parameter}" == "x${parameter#-}" ]]
then
parameterFlag="Tripped!"
fi
if [[ "x${parameter}" == "x--" ]]
then
print "Parameter \"$parameter\" ends the parameter list"
parameterFlag="TRIPPED!"
fi
if [ -n $parameterFlag ]
then
print "\"$parameter\" is a file"
else
echo "The parameter \"$parameter\" is a parameter"
fi
done
Question 1
I don't think so, at least not if you need to do this for arbitrary commands.
Question 3
command=$1
shift
while [ $1 != '--' ]; do
args="$args $1"
shift
done
shift
while [ -n "$1" ]; do
echo "$1"
shift
done | $command $args
Question 2
How does that differ from question 3?

Resources