How to force the read command to correctly parse array arguments within quotation marks? - bash

I have this script:
#!/usr/bin/env bash
read -p "Provide arguments: " -a arr <<< "foo \"bar baz\" buz"
for i in ${arr[#]}
do
echo $i
done
which incorrectly outputs:
foo
"bar
baz"
buz
How can I make it interpret user input so that parameters within quotation marks would make a single array element? Like this:
foo
bar baz
buz
EDIT:
To be clear: I don't want the user to input each element in separate line, so read-ing in loop is not what I'm looking for.

You're better off supplying user input using a different delimiter, e.g. ;.
OLDIFS=$IFS
IFS=$';'
read -p "Provide arguments: " -a var
for i in "${!var[#]}"
do
echo Argument $i: "${var[i]}"
done
IFS=$OLDIFS
Upon execution:
Provide arguments: foo;bar baz;buz
Argument 0: foo
Argument 1: bar baz
Argument 2: buz
Plus a modification to trim the variables:
echo Argument $i: $(echo "${var[i]}" | sed -e 's/^ *//g' -e 's/ *$//g')

Related

How to parse multiple line output as separate variables

I'm relatively new to bash scripting and I would like someone to explain this properly, thank you. Here is my code:
#! /bin/bash
echo "first arg: $1"
echo "first arg: $2"
var="$( grep -rnw $1 -e $2 | cut -d ":" -f1 )"
var2=$( grep -rnw $1 -e $2 | cut -d ":" -f1 | awk '{print substr($0,length,1)}')
echo "$var"
echo "$var2"
The problem I have is with the output, the script I'm trying to write is a c++ function searcher, so upon launching my script I have 2 arguments, one for the directory and the second one as the function name. This is how my output looks like:
first arg: Projekt
first arg: iseven
Projekt/AX/include/ax.h
Projekt/AX/src/ax.cpp
h
p
Now my question is: how do can I save the line by line output as a variable, so that later on I can use var as a path, or to use var2 as a character to compare. My plan was to use IF() statements to determine the type, idea: IF(last_char == p){echo:"something"}What I've tried was this question: Capturing multiple line output into a Bash variable and then giving it an array. So my code looked like: "${var[0]}". Please explain how can I use my line output later on, as variables.
I'd use readarray to populate an array variable just in case there's spaces in your command's output that shouldn't be used as field separators that would end up messing up foo=( ... ). And you can use shell parameter expansion substring syntax to get the last character of a variable; no need for that awk bit in your var2:
#!/usr/bin/env bash
readarray -t lines < <(printf "%s\n" "Projekt/AX/include/ax.h" "Projekt/AX/src/ax.cpp")
for line in "${lines[#]}"; do
printf "%s\n%s\n" "$line" "${line: -1}" # Note the space before the -1
done
will display
Projekt/AX/include/ax.h
h
Projekt/AX/src/ax.cpp
p

Unix read command with option -d and IFS variable combination

Looking forward to understand the behavior of read -d when it comes along with IFS variable
$ cat f1
a:b:c
d:e:f
$ while IFS= read -d: ; do echo $REPLY; done < f1
a
b
c d
e
$ while IFS=: read; do echo $REPLY; done < f1
a:b:c
d:e:f
IFS is used when you're reading several variables with read:
$ echo foo:bar:baz | (IFS=: read FOO BAR BAZ; echo $FOO; echo $BAR; echo $BAZ)
foo
bar
baz
Whereas, the -d option specifies what your line separator for read is; read won't read beyond a single line:
$ echo foo:bar:baz%baz:qux:quux% | while IFS=: read -d% FOO BAR BAZ; do echo ---; echo $FOO; echo $BAR; echo $BAZ; done
---
foo
bar
baz
---
baz
qux
quux
IFS is inter field separator.
You say to shell which symbols is used to split fields.
It is used in two directions, not only when reading.
One special case that you use here is IFS=. You use IFS= here to correctly handle input starting with spaces.
You can compare:
echo " a" | IFS= read a
echo " a" | read a
That is important when you handle files and they can contain leading spaces in their names.
Please compare:
$ echo " a" | ( IFS= read a; echo .$a. )
. a.
$ echo " a" | ( read a; echo .$a. )
.a.
UPDATE. As you probably already noted,
this construction
$ echo a | read a
doesn't work. Because shell creates a subshell for the '''read''', and you can see the value of $a only inside it.
You can also use while, what is more often:
$ echo a | while read a; do echo $a; done

How to append strings to the same line instead of creating a new line?

Redirection to a file is very usefull to append a string as a new line to a file, like
echo "foo" >> file.txt
echo "bar" >> file.txt
Result:
foo
bar
But is it also possible to redirect a string to the same line in the file ?
Example:
echo "foo" <redirection-command-for-same-line> file.txt
echo "bar" <redirection-command-for-same-line> file.txt
Result:
foobar
The newline is added by echo, not by the redirection. Just pass the -n switch to echo to suppress it:
echo -n "foo" >> file.txt
echo -n "bar" >> file.txt
-n do not output the trailing newline
An alternate way to echo results to one line would be to simply assign the results to variables. Example:
j=$(echo foo)
i=$(echo bar)
echo $j$i
foobar
echo $i $j
bar foo
This is particularly useful when you have more complex functions, maybe a complex 'awk' statement to pull out a particular cell in a row, then pair it with another set.

Pipe string with newline to command in bash?

I am trying to pass in a string containing a newline to a PHP script via BASH.
#!/bin/bash
REPOS="$1"
REV="$2"
message=$(svnlook log $REPOS -r $REV)
changed=$(svnlook changed $REPOS -r $REV)
/usr/bin/php -q /home/chad/www/mantis.localhost/scripts/checkin.php <<< "${message}\n${changed}"
When I do this, I see the literal "\n" rather than the escaped newline:
blah blah issue 0000002.\nU app/controllers/application_controller.rb
Any ideas how to translate '\n' to a literal newline?
By the way: what does <<< do in bash? I know < passes in a file...
try
echo -e "${message}\n${changed}" | /usr/bin/php -q /home/chad/www/mantis.localhost/scripts/checkin.php
where -e enables interpretation of backslash escapes (according to man echo)
Note that this will also interpret backslash escapes which you potentially have in ${message} and in ${changed}.
From the bash manual:
Here Strings
A variant of here documents, the format is:
<<<word
The word is expanded and supplied to the command on its standard input.
So I'd say
the_cmd <<< word
is equivalent to
echo word | the_cmd
newline=$'\n'
... <<< "${message}${newline}${changed}"
The <<< is called a "here string". It's a one line version of the "here doc" that doesn't require a delimiter such as "EOF". This is a here document version:
... <<EOF
${message}${newline}${changed}
EOF
in order to avoid interpretation of potential escape sequences in ${message} and ${changed}, try concatenating the strings in a subshell (a newline is appended after each echo unless you specify the -n option):
( echo "${message}" ; echo "${changed}" ) | /usr/bin/php -q /home/chad/www/mantis.localhost/scripts/checkin.php
The parentheses execute the commands in a subshell (if no parentheses were given, only the output of the second echo would be piped into your php program).
It is better to use here-document syntax:
cat <<EOF
copy $VAR1 $VAR2
del $VAR1
EOF
You can use magical Bash $'\n' with here-word:
cat <<< "copy $VAR1 $VAR2"$'\n'"del $VAR1"
or pipe with echo:
{ echo copy $VAR1 $VAR2; echo del $VAR1; } | cat
or with printf:
printf "copy %s %s\ndel %s" "$VAR1" "$VAR2" "$VAR1" | cat
Test it:
env VAR1=1 VAR2=2 printf "copy %s %s\ndel %s" "$VAR1" "$VAR2" "$VAR1" | cat

Printf example in bash does not create a newline

Working with printf in a bash script, adding no spaces after "\n" does not create a newline, whereas adding a space creates a newline, e. g.:
No space after "\n"
NewLine=`printf "\n"`
echo -e "Firstline${NewLine}Lastline"
Result:
FirstlineLastline
Space after "\n "
NewLine=`printf "\n "`
echo -e "Firstline${NewLine}Lastline"
Result:
Firstline
Lastline
Question: Why doesn't 1. create the following result:
Firstline
Lastline
I know that this specific issue could have been worked around using other techniques, but I want to focus on why 1. does not work.
Edited:
When using echo instead of printf, I get the expected result, but why does printf work differently?
NewLine=`echo "\n"`
echo -e "Firstline${NewLine}Lastline"
Result:
Firstline
Lastline
The backtick operator removes trailing new lines. See 3.4.5. Command substitution at http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html
Note on edited question
Compare:
[alvaro#localhost ~]$ printf "\n"
[alvaro#localhost ~]$ echo "\n"
\n
[alvaro#localhost ~]$ echo -e "\n"
[alvaro#localhost ~]$
The echo command doesn't treat \n as a newline unless you tell him to do so:
NAME
echo - display a line of text
[...]
-e enable interpretation of backslash escapes
POSIX 7 specifies this behaviour here:
[...] with the standard output of the command, removing sequences of one or more characters at the end of the substitution
Maybe people will come here with the same problem I had:
echoing \n inside a code wrapped in backsticks. A little tip:
printf "astring\n"
# and
printf "%s\n" "astring"
# both have the same effect.
# So... I prefer the less typing one
The short answer is:
# Escape \n correctly !
# Using just: printf "$myvar\n" causes this effect inside the backsticks:
printf "banana
"
# So... you must try \\n that will give you the desired
printf "banana\n"
# Or even \\\\n if this string is being send to another place
# before echoing,
buffer="${buffer}\\\\n printf \"$othervar\\\\n\""
One common problem is that if you do inside the code:
echo 'Tomato is nice'
when surrounded with backsticks will produce the error
command Tomato not found.
The workaround is to add another echo -e or printf
printed=0
function mecho(){
#First time you need an "echo" in order bash relaxes.
if [[ $printed == 0 ]]; then
printf "echo -e $1\\\\n"
printed=1
else
echo -e "\r\n\r$1\\\\n"
fi
}
Now you can debug your code doing in prompt just:
(prompt)$ `mySuperFunction "arg1" "etc"`
The output will be nicely
mydebug: a value
otherdebug: whathever appended using myecho
a third string
and debuging internally with
mecho "a string to be hacktyped"
$ printf -v NewLine "\n"
$ echo -e "Firstline${NewLine}Lastline"
Firstline
Lastline
$ echo "Firstline${NewLine}Lastline"
Firstline
Lastline
It looks like BASH is removing trailing newlines.
e.g.
NewLine=`printf " \n\n\n"`
echo -e "Firstline${NewLine}Lastline"
Firstline Lastline
NewLine=`printf " \n\n\n "`
echo -e "Firstline${NewLine}Lastline"
Firstline
Lastline
Your edited echo version is putting a literal backslash-n into the variable $NewLine which then gets interpreted by your echo -e. If you did this instead:
NewLine=$(echo -e "\n")
echo -e "Firstline${NewLine}Lastline"
your result would be the same as in case #1. To make that one work that way, you'd have to escape the backslash and put the whole thing in single quotes:
NewLine=$(printf '\\n')
echo -e "Firstline${NewLine}Lastline"
or double escape it:
NewLine=$(printf "\\\n")
Of course, you could just use printf directly or you can set your NewLine value like this:
printf "Firstline\nLastline\n"
or
NewLine=$'\n'
echo "Firstline${NewLine}Lastline" # no need for -e
For people coming here wondering how to use newlines in arguments to printf, use %b instead of %s:
$> printf "a%sa" "\n"
a\na
$> printf "a%ba" "\n"
a
a
From the manual:
%b expand backslash escape sequences in the corresponding argument
We do not need "echo" or "printf" for creating the NewLine variable:
NewLine="
"
printf "%q\n" "${NewLine}"
echo "Firstline${NewLine}Lastline"
Bash delete all trailing newlines in commands substitution.
To save trailing newlines, assign printf output to the variable with printf -v VAR
instead of
NewLine=`printf "\n"`
echo -e "Firstline${NewLine}Lastline"
#FirstlineLastline
use
printf -v NewLine '\n'
echo -e "Firstline${NewLine}Lastline"
#Firstline
#Lastline
Explanation
According to bash man
3.5.4 Command Substitution
$(command)
or
`command`
Bash performs the expansion by executing command and replacing the command substitution with the standard output of the command, with any trailing newlines deleted. Embedded newlines are not deleted, but they may be removed during word splitting.
So, after adding any trailing newlines, bash will delete them.
var=$(printf '%s\n%s\n\n\n' 'foo' 'bar')
echo "$var"
output:
foo
bar
According to help printf
printf [-v var] format [arguments]
If the -v option is supplied, the output is placed into the value of the shell variable VAR rather than being sent to the standard output.
In this case, for safe copying of formatted text to the variable, use the [-v var] option:
printf -v var '%s\n%s\n\n\n' 'foo' 'bar'
echo "$var"
output:
foo
bar
Works ok if you add "\r"
$ nl=`printf "\n\r"` && echo "1${nl}2"
1
2

Resources