Cygwin: parsing whitespaced arguments using getopt - cygwin

I have a problem with a Cygwin script that uses getopt. I am able to reproduce the problem with the following code snippet.
Basically I want to process some command line arguments using getopt. My command line arguments have whitespace characters in them (spaces & tabs --- Windows filenames to be precise). I am able to read all the command line arguments in the script correctly (including the whitespace characters) but when I use getopt and loop through all the arguments, one by one, Cygwin incorrectly treats each whitespace as an argument separator.
Pointers appreciated.
Note:- I have played with using quotes, where I deemed appropriate, but it did not help my cause.
# Command line arguments passed are as follows:
# »script« -a "/A/B/C Directory/1.txt" -b "/A/B/D Directory/2.txt" -c "/A/B/E Directory/3.txt"
echo "Argument-1: " $1
echo "Argument-2: " $2
echo "Argument-3: " $3
echo "Argument-4: " $4
echo "Argument-5: " $5
echo "Argument-6: " $6
#
# So far no problems
#
echo
echo
set -- `getopt a:b:c: "$*"`
counter=1
while [ "$1" != "--" ]
do
echo "getopt-$counter: " $1
counter=$[$counter + 1]
shift 1
done
#
# Now there's a problem. The six command line arguments (three with blank spaces) are incorrectly
# interpreted as nine command line arguments
#

Related

How to create a string=$* without arguments $1 and $2

I have a script that takes in several arguments.
I need everything but $1 and $2 in a string.
I have tried this:
message="$*"
words= $(grep -v "$2"|"$3" $message)
but it doesn't work, it gives me the error:
./backup: line 26: First: command not found
Use shift 2 to shift the arguments along (it drops the first n arguments).
If you need "$1" and "$2" for later, save them in variables first.
Note that in shell, assignments to variables cannot have whitespace either side of the =.
First=$1
Second=$2
shift 2
Message=$#
Maybe something like this?
[root#tsekmanrhel771 ~]# cat ./skip1st2.sh
#!/bin/bash
COUNT=0
for ARG in "$#"
do
COUNT=$[COUNT + 1]
if [ ${COUNT} -gt 2 ]; then
RESULT="${RESULT} ${ARG}"
fi
done
echo ${RESULT}
[root#tsekmanrhel771 ~]# ./skip1st2.sh first second third 4 5 6 7
third 4 5 6 7
You can use a subarray:
$ set -- arg1 arg2 arg3 arg4
$ str=${*:3}
$ echo "$str"
arg3 arg4
More often than not, it's good practice to preserve the arguments as separate elements, though, which you can do by using $# and assigning to a new array:
$ arr=("${#:3}")
$ declare -p arr
declare -a arr=([0]="arg3" [1]="arg4")
Notice that in str=${*:3}, quoting isn't necessary, but in arr=("${#:3}"), it is (or the arguments would be split on whitespace).
As for your error message: your command
words= $(grep -v "$2"|"$3" $message)
does the following:
It sets a variable words to the empty string for the environment of the command (because there is a blank after =).
It tries to set up a pipeline consisting of two commands, grep -v "$2" and "$3" $message. The first of these commands would just hang and wait for input; the second one tries to run the contents of $3 as a command; presumably, based on your error message, $3 contains First.
If the pipeline would actually run, its output would be run as a command (again because of the blank to the right of =).

Bash: highlight command before execution (set -x)

I have a bash script which executes about 20 commands and for debugging purposes I find myself scrolling through the output a lot.
Unfortunately bash doesn't tell me which part of the output is part of what command.
When I use "set -x" in the script it at least prints some information on what it just executed, but I don't really like the output it generates.
For instance, if I have this script:
#!/bin/bash
set -x
echo "foo"
if [ "$ASD" == "QWE" ] ; then
echo "bar"
fi
I would like the output to be something like this:
echo "foo"
foo
echo "bar"
bar
Or maybe:
echo "foo"
foo
if [ "value_of_ASD" == "QWE" ] ; then
echo "bar"
bar
fi
Instead of printing the commands in bold, highlighting with a color would also be okay. But I don't just want to have "+" characters in front of the commands and I also don't like the if statements showing up like '[' value_of_ASD == QWE ']'.
How can I accomplish that with bash?
At the moment the output looks like this btw:
+ echo foo
foo
+ '[' value_of_ASD == QWE ']'
+ echo bar
bar
Edit:
One idea I had was to write a script that I would source in the very beginning of the main script and then let the sourced script parse the main one. Something like this:
source_me.sh
#!/bin/bash
SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/$(basename $0)"
FORMAT_SET_BOLD='\033[0;1m'
FORMAT_RESET='\033[0m'
cat $SCRIPT_PATH | tail -n "+$START_LINE" | while read line; do
printf "${FORMAT_SET_BOLD}${line}${FORMAT_RESET}\n"
eval "${line}"
done
exit 0;
main.sh
#!/bin/bash
START_LINE=$((LINENO+1)) source ./source_me.sh
echo "Foo"
echo "Bar"
echo "+Hello"
The output in that case is:
echo "Foo"
Foo
echo "Bar"
Bar
echo "+Hello"
+Hello
But this method will fail if I use more complex code that goes over multiple lines (if statements, loops etc):
#!/bin/bash
START_LINE=$((LINENO+1)) source ./source_me.sh
echo "Foo"
if [ "$FOOBAR" == "" ] ; then
echo "Bar"
fi
echo "+Hello"
In this case I get:
echo "Foo"
Foo
if [ "$FOOBAR" == "" ] ; then
./source_me.sh: eval: line 9: syntax error: unexpected end of file
echo "Bar"
Bar
fi
./source_me.sh: eval: line 8: syntax error near unexpected token ´fi'
./source_me.sh: eval: line 8: ´fi'
echo "+Hello"
+Hello
I would like to extend rubo77's answer with a few examples that I think deserve a separate answer:
Plain text prefix
So the basic example is to set PS4 to some plain text, e.g.:
PS4="# "; set -x
Which will result in:
Color & extra line text prefix
But because you can use special characters and ANSI escape codes you can for example add a new line before each new command and print the prefix in a color, e.g.:
PS4="\n\033[1;33m>>>\033[0m "; set -x
Result:
Dynamic color prefix
Finally you can make the command prefix call other programs with each use, which you can use to add a timestamp, e.g.:
# yes, there have to be single quotes below, not double!
PS4='\033[1;34m$(date +%H:%M:%S)\033[0m '; set -x
Result:
You can change the + to a string of your desire by setting PS4, e.g.:
PS4="# "; set -x
Note that set -x has no innate knowledge of your terminal, which would be required to send the correct characters to create bold or coloured text.
If you can capture all output through a single file handle, you can probably do this with a pipe:
$ /path/to/your/script 2>&1 | sed "/^+ /s/.*/$(tput bold)&$(tput sgr0)/"
You might want to man tput for more information about the tool we're using to get bold type.
The viability of this depends on many factors I don't know about your environment. YMMV, may contain nuts.
Note:
You might think it was better to capture stderr separately with a line like this:
$ /path/to/your/script 2> >(sed "/^+ /s/.*/$(tput bold)&$(tput sgr0)/")
Alas, this doesn't behave consistently, because stderr passing through the pipe may not be submitted to the terminal before subsequent stdout from the script itself. Note also that this solution, because it uses process substitution, is a bashism and is not portable to POSIX.
What you are looking for is set -v.
-v prints out the line the interpreter just read.
-x prints out the post-parsing results of the line that the interpreter just read.
file x:
set -vx
foo=1
echo $foo
set +vx
Executing:
$: . x
foo=1
++ foo=1
echo $foo
++ echo 1
1
set +vx
++ set +vx

shell script program for count num of line

Write a shell script to count the number of lines, characters, words in a file (without the use of commands). Also delete the occurrence of word “Linux” from the file wherever it appears and save the results in a new file.
This is the nearest I could get without using any third party packages...
#!/bin/bash
count=0
while read -r line
do
count=$((count + 1))
done < "$filename"
echo "Number of lines: $count"
Sachin Bharadwaj gave a script that counts the lines.
Now, to count the words, we can use set to split the line into $# positional parameters.
And to count the characters, we can use the parameter length: ${#line}.
Finally, to delete every “Linux”, we can use pattern substitution: ${line//Linux}.
(Cf. Shell Parameter Expansion.)
All taken together:
while read -r line
do
((++count))
set -- $line
((wordcount+=$#))
((charcount+=${#line}+1)) # +1 for the '\n'
echo "${line//Linux}"
done < "$filename" >anewfile
echo "Number of lines: $count"
echo "Number of words: $wordcount"
echo "Number of chars: $charcount"

Bash: how do you echo "hey" and ", how are you?" with two separate echo commands?

I'm new to Bash and I'm trying to learn how to firstly execute two commands using the | symbol, and secondly how to join two commands into a compound command.
When writing two commands in one line the pipeline symbol | is meant to connect the output of the first command to the input of the second. So I don't quite understand how it fails to join "Hey" and ", how are you?".
echo "hey" | echo ", how are you?"
When writing a compound command, commands should be between the opening and closing braces separated by a semicolon. In this case
{ echo "Hey"; echo ", how are you" }
, but it doesn't seem to work as expected.
What could I be missing out here, for these two cases?
Short version: use printf instead of echo.
echo does not read from its standard input, so the pipe is effectively the same as
echo "hey"; echo ", how are you?"
which are two separate commands, each of which prints an implicit newline after the last argument.
Your brace command fails because a semicolon or newline is required after the last command: { echo "Hey"; echo ", how are you"; }.
The answer to your question, though, is that you need to suppress the newline that echo prints.
echo -n "hey"; echo ", how are you?"
This will work for bash, but not all shells, as the -n option is non-standard. printf is more reliable:
printf "hey"; printf ", how are you?\n"

what does echo ${1+"$#"} mean

From /usr/local/bin/erl
ROOTDIR=/usr/local/lib/erlang
BINDIR=$ROOTDIR/erts-5.9.1/bin
EMU=beam
PROGNAME=`echo $0 | sed 's/.*\///'`
export EMU
export ROOTDIR
export BINDIR
export PROGNAME
exec $BINDIR/erlexec ${1+"$#"}
I know "$#" meams arguments. But {1+"$#"} means what?
From IEEE Std 1003.1 (POSIX 2013), Shell command language:
${parameter:+[word]}
Use Alternative Value. If parameter is unset or null, null shall be substituted; otherwise, the expansion of word (or an empty string if word is omitted) shall be substituted.
I.e., ${1+"$#"} expands to the value of "$#", the command line arguments, except when $1 is not set, i.e. there are no command line arguments, in which case the expression expands to nothing. A simpler script that shows how this works is
echo '"' ${1+"$#"} '"'
If you store this in a file test.sh and run it, you get:
/tmp$ sh test.sh
" "
/tmp$ sh test.sh 1
" 1 "
/tmp$ sh test.sh 1 2
" 1 2 "
(The spaces at the begin and end come from echo.)
larsmans provides an explanation of the semantics, but doesn't clarify why ${1+"$#"} is different than simply "${#}". In a properly behaving shell, "${#}" expands to nothing. That is: foo "$#" should call foo with no arguments if "$#" is empty. In an incorrectly behaving shell, foo "$#" will be invoked with one argument (the empty string). Many historical shells expanded "$#" to the empty string rather than to nothing, but ${1+"$#"} correctly expands to nothing.

Resources