4

I have a string as:

Str='This string has "a substring"'

The string has commas so if I print the string i see:

echo "${Str}"

This string has "a substring". 

If I input the command:

$ Tmp=( ${Str} )
$ echo "${Tmp[3]}"
"a
$ echo "${Tmp[4]}"
Substring"

Id like to print: a Substring Any advice? I can change the commas but it is essential that it will be printed from Str to Tmp

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Alon Kwart
  • 51
  • 1
  • 5
  • 1
    Which characters do you want to account for? Let's say Str contains ``'a bunch' of `various quotes` with possible "escape \"characters" or mismatched ' quotes, $metachars and $(maybe commands) .``? How should that be split up? – that other guy May 23 '16 at 00:47

3 Answers3

7

This problem requires the use of xargs (it retains quoted strings together):

$ Str='This string has "a substring"'
$ IFS=$'\n' arr=( $(xargs -n1 <<<"$Str") )
$ printf '<%s>\n' "${arr[@]}"
<This>
<string>
<has>
<a substring>

So, the element you need:

$ echo "${Tmp[3]}"
a substring

Please note that leading or trailing white space will be removed for "unquoted" items:

$ Str='  This    string    has "   a substring  "'
$ IFS=$'\n' arr=( $(xargs -n1 <<<"$Str") )
$ printf '<%s>\n' "${arr[@]}"
<This>
<string>
<has>
<   a substring  >
  • 2
    I might go with `while IFS read -r -d '' item; do arr+=( "$item" ); done < <(xargs printf '%s\0' <<<"$Str")` to allow newline literals within the items. That said, AFAICT this is the best available general answer without using something like Python's shlex module. – Charles Duffy May 23 '16 at 15:45
  • @CharlesDuffy Well, an string that has newlines inside quotes: `Str=$'string has "a \n substring"'` will fail with xargs: `xargs <<<"$Str"` with this message: `xargs: unmatched double quote;`. .... However, if the newlines are outside the quotes, it will work with your code. –  May 24 '16 at 02:09
  • Working OK, thanks you. Could you please explain why the `-n1` part? The XArgs Man Page ( http://man7.org/linux/man-pages/man1/xargs.1.html ) is not clear enough for me on that. – Sopalajo de Arrierez Jan 26 '20 at 14:09
  • @SopalajodeArrierez This is for `xargs` to call the default command `echo` individually for each quoted or unquoted item (*1* argument per call). – Kirill Bulygin Jun 04 '22 at 13:09
0

Give a try to this:

 Str='this string has "a substring"'
 eval Tmp=( "${Str}" )

 printf "%s\n" "${Str}"
 this string has "a substring"

 printf "%s\n" "${Tmp[3]}"
 a substring

 set | grep "^Tmp"
 Tmp=([0]="this" [1]="string" [2]="has" [3]="a substring")

I have to warn you regarding eval, see the comment from @charlesduffy: use it only if Str is generated earlier with your own lines of code.

Jay jargot
  • 2,745
  • 1
  • 11
  • 14
  • `declare -p Tmp` is a much easier way to print a variable's definition, btw. – Charles Duffy May 23 '16 at 01:12
  • ...the issue I have here is that using `eval` means you need to trust your content not to be malicious (and if any thing an attacker could control slips into your variable contents, you just handed over the keys to the kingdom). Consider behavior with `Str='Proxy for greater evil: $(touch /tmp/pwned.txt)'`. – Charles Duffy May 23 '16 at 01:14
  • @CharlesDuffy I knew it: this is critic when it is used with data provided by user when `read` is used or when positional parameters are used. We can also imagine other situations. Awarning has been added. – Jay jargot May 28 '16 at 09:46
-1

If you already know the index of the words you are looking for, converting a string to an array is as simple as using parentheses:

tmp=($(echo $Str))

Then you can just echo ${tmp[4]} ${tmp[5]} to print "a substring" without the commas.

However, if you already know what the substring is, why not grep it from the original?

echo $Str | grep -o "a substring"

will return the substring in the same manner, but you won't need to worry about the length of the substring or the index of the words inside the array.

Edit: By the way, if you just wanted to remove the first and last character of any string you could do (bash 4.2 and above):

echo ${Str:1:-1}
Ocab19
  • 499
  • 5
  • 14
  • Indexing numerically is defeating the point (of parsing in a way that honors strings syntactically). – Charles Duffy May 23 '16 at 01:11
  • I know, I just added the indexing part to follow the lines of the original question. And I even pointed out that it's not the best way to do this, and that the grep way is easier to handle. – Ocab19 May 23 '16 at 14:20