3

Well, this apparently is not possible the way I'm trying it.

This alternate approach to obtain bar as a resulting string works, though:

#!/bin/bash

path1=/usr/tmp/foo/bar/baz

awk -F/ '{print $5}' <<< "$path1"

So far so good, but what if I want to do without the <<< operator as well as those notorious echo | ... pipes? In a nutshell, what I'm trying to do is passing path1 as a variable with the -v pa="$path1" directive and using both the field separator -F/ and the field identifiers (e. g. $5) to parse the awk-internal pa variable, which got its value assigned from the external path1 shell variable. Can this be done "inside" awk, too?

7

The problem with the -v option or with the var=value arguments to awk is that they can't be used for arbitrary data since ANSI C escape sequences (like \n, \b...) are expanded in them.

The alternative is to use the ARGV or ENVIRON awk arrays:

awk -F / 'BEGIN{$0 = ARGV[1]; print $5}' "$path1"

Or:

export path1
awk -F / 'BEGIN{$0 = ENVIRON["path1"]; print $5}'

Or:

path1="$path1" awk -F / 'BEGIN{$0 = ENVIRON["path1"]; print $5}'

Now, if all you want is split a shell variable, you may not need awk.

In all POSIX shells:

IFS=/; set -f
set -- $path1
printf '%s\n' "$5"
  • Thank you, this looks interesting as well! Just one thing always confuses me again: awk expects a file after the '...', right? So why does it accept a string in this case without complaining? -- To your second approach: As I normally use local variables in my bash scripts, I normally do not make them available from outside using export. And not to be forgotten: these variables will have to be unexported again to reduce clutter, whilst local variables are destroyed after the script ends (or exits prematurely). – syntaxerror Aug 19 '13 at 20:04
  • 2
    @syntaxerror, See the 3rd variant then, or use ARGV. If there's only a BEGIN section, files or standard input are not read. – Stéphane Chazelas Aug 19 '13 at 20:10
  • Nice, thanks, I'll remember that from now on! Besides, your logic also works the other way round: that is to say, if you do NOT set a BEGIN section and use a variable like this here (and not a file), awk may "stall" and you have to CTRL-C it to get back to your shell prompt. Didn't happen to me only once when I did my first awk steps some moons ago. – syntaxerror Aug 19 '13 at 20:16
  • Thanks for showing how to use ENVIRON. I tried that but didn't realize to drop the $ bit from the variable name. Very nice and elegant solutions! – slm Aug 19 '13 at 20:22
3

You could split pa into an array

awk -F'/' -v pa=$path1 'BEGIN{split(pa, arr, FS); print(arr[5]); exit}'
  • 1
    That fails if $path1 contains blanks. May fail if it contains wildcard characters or backslashes. – Stéphane Chazelas Aug 19 '13 at 20:11
  • @StephaneChazelas, good point. And +1 for your solution that addresses these issues – iruvar Aug 19 '13 at 20:35
1

There are a couple of ways you can pass environment variables into an awk script/command:

Method #1

This gets the shell to expand the variable, $path1, prior to running the awk command.

$ echo $path1
/usr/tmp/foo/bar/baz
$ awk -F'/' 'END{a="'$path1'"; split(a,arr,FS); print(arr[5])}' /dev/null
bar

Method #2

Pass env. variable in as a awk variable, a.

$ awk -F '/' 'END{split(a,arr,FS); print(arr[5])}' a=$path1 /dev/null
bar

Method #3

Explicitly pass awk a variable using the -v switch.

$ awk -F'/' -v a=$path1 'END{split(a,arr,FS); print(arr[5])}' /dev/null
bar

Debugging

You can enable the shell's verbosity by setting set -x before running any of these commands to see what's going on. Here's method #2 as an example:

$ set -x
$ awk -F '/' 'END{split(a,arr,FS); print(arr[5])}' a=$path1 /dev/null
+ awk -F / 'END{split(a,arr,FS); print(arr[5])}' a=/usr/tmp/foo/bar/baz /dev/null
bar

The above shows set -x being executed followed by the awk line. You can see that when executed the variable a has the value of $path1 already expanded when it executes.

Another example, this time method #1:

$ awk -F'/' 'END{a="'$path1'";split(a,arr,FS); print(arr[5])}' /dev/null
+ awk -F/ 'END{a="/usr/tmp/foo/bar/baz";split(a,arr,FS); print(arr[5])}' 
bar

Here you can see that the shell is definitely expanding the $path1 variable prior to executing awk.

  • Thanks a ton for your efforts! Indeed, but there is obviously no way you can make direct use of those $3, $4 field identifiers. You will always have to work around it by using split() and arrays. -- However, if I had the possibility of using field identifiers, it would make the line appear significantly shorter for sure (which is always a criteria to care upon :)) – syntaxerror Aug 19 '13 at 19:13
  • 1
    Method 1: not sure why you use both a BEGIN and END section, may fail (possibly badly) if $path1 contains double quotes, blanks, backslashes, globbing characters. For instance, try with path1='";system("rm\040-rf\040~");"' – Stéphane Chazelas Aug 19 '13 at 20:06
  • Method2 2 and 3: may fail if $path1 contains blanks or wildcards (as $path1 is still not quoted) or backslashes. – Stéphane Chazelas Aug 19 '13 at 20:08
  • @StephaneChazelas - thanks I've removed the BEGIN section. – slm Aug 19 '13 at 20:18
0

How about:

basename "$(dirname "$path1")"

or, if your shell supports process substitution and you need to use awk:

awk -F'/' '{print $5}' <(printf '%s\n' "$path1")
  • 1
    To your first line: OK for this purpose, but these examples should not be taken too literally. I usually use these with the intention to do something way more complex later. I was just asking because I needed the right technique to get string variables from outside parsed inside awk. -- To your second line: This is not much different from my <<< approach. This will as well have to be done from outside awk. But thanks anyway for your input. – syntaxerror Aug 19 '13 at 19:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.