1

I am trying to use bc in an awk script. In the code below, I am trying to convert hexadecimal number to binary and store it in a variable.

#!/bin/awk -f

{
  binary_vector = $(bc <<< "ibase=16;obase=2;FF") 
}

Where do I go wrong?

valar_m
  • 35
  • 1
  • 5
  • 3
    You're trying to use awk as you would use bash. They are two different languages, designed for two different things, so you can't borrow syntax from one and use it in the other. Please [edit] your question to show us exactly what you're trying to do. Some input and desired output is always useful. – Tom Fenech Mar 23 '15 at 17:21
  • What you've put in the post so far is using nothing of what `awk` does. Why is `awk` involved here at all? – Etan Reisner Mar 23 '15 at 17:27
  • I want to learn from the accepted answer of this post [link] (http://stackoverflow.com/questions/18870209/convert-a-decimal-number-to-hexadecimal-and-binary-in-a-shell-script) . In my case, I want to store the value in a variable. – valar_m Mar 23 '15 at 17:28
  • The intention is to convert hexadecimal to binary in awk and store it in a variable. – valar_m Mar 23 '15 at 17:35
  • If you have gnu awk, then this answer from that post could work for you: http://stackoverflow.com/a/18870379/1259917, but you'd have to copy the `bit2str` function into your script. If you don't have it, you won't have access to the `and()` etc functions, and would need a separate conversion step to create an input file with the `bc`'d data elements. – n0741337 Mar 23 '15 at 17:41

3 Answers3

3

Not saying it's a good idea but:

$ awk 'BEGIN {
    cmd = "bc <<< \"ibase=16;obase=2;FF\""
    rslt = ((cmd | getline line) > 0 ? line : -1)
    close(cmd)
    print rslt
}'
11111111

Also see http://gnu.org/software/gawk/manual/gawk.html#Bitwise-Functions and http://gnu.org/software/gawk/manual/gawk.html#Nondecimal-Data

Ed Morton
  • 188,023
  • 17
  • 78
  • 185
  • 1
    Solid, but since the shell command is passed to `sh`, where `<<<` may not work, it's better to use `cmd = "printf \"ibase=16;obase=2;FF\n\" | bc"`. (I know that you simply reused the OP's shell command, but I think this change is still worth making.) – mklement0 Mar 23 '15 at 21:07
  • I'm just showing the OP the right way to point the gun at his foot after he's loaded the bullets :-). Feel free to edit my answer to add the printf syntax if you like. – Ed Morton Mar 23 '15 at 21:12
  • 2
    :) Fair enough. I'll let these comments tell the story. – mklement0 Mar 23 '15 at 21:16
1

The following one-liner Awk script should do what you want:

awk -vVAR=$(read -p "Enter number: " -u 0 num; echo $num) \
 'BEGIN{system("echo \"ibase=16;obase=2;"VAR"\"|bc");}'

Explanation:

-vVAR Passes the variable VAR into Awk

-vVAR=$(read -p ... ) Sets the variable VAR from the shell to the user input.

system("echo ... |bc") Uses the Awk system built in command to execute the shell commands. Notice how the quoting stops at the variable VAR and then continues just after it, thats so that Awk interprets VAR as an Awk variable and not as part of the string put into the system call.

Update - to use it in an Awk variable:

awk -vVAR=$(read -p "Enter number: " -u 0 num; echo $num) \
'BEGIN{s="echo \"ibase=16;obase=2;"VAR"\"|bc"; s | getline awk_var;\ 
close(s); print awk_var}'

s | getline awk_var will put the output of the command s into the Awk variable awk_var. Note the string is built before sending it to getline - if not (unless you parenthesize the string concatenation) Awk will try to send it to getline in separate pieces %s VAR %s.

The close(s) closes the pipe - although for bc it doesn't matter and Awk automatically closes pipes upon exit - if you put this into a more elaborate Awk script it is best to explicitly close the pipe. According to the Awk documentation some commands such as mail will wait on the pipe to close prior to completion.

http://www.staff.science.uu.nl/~oostr102/docs/nawk/nawk_39.html

  • The - admittedly unusual - requirement is to read the output of the `bc` command into an _awk_ variable from _inside_ an awk program. Without that, you could just invoke `bc` inside your command substitution and (also) pass the result to `awk`. As an aside: did you add `-u 0` to your `read` command just to make it explicit that you're reading from stdin, or is there another reason? – mklement0 Mar 23 '15 at 19:56
  • Thanks, I went straight to semantics I have updated my answer - and yes I use `-u 0` on read to be explicit because I have experienced problems with complicated shell scripts that play around with the descriptors - its safer to be explicit imo. –  Mar 23 '15 at 20:18
  • Good update (background info on `close`), but I don't think you're correct about "if you don't, Awk will try to send it to getline in pieces (%s VAR %s)" - a string literal should work fine (assuming you're not planning to call `close`). Also, note that some Awk implementations (e.g., BSD Awk) require a space between `-v` and `VAR=...`. – mklement0 Mar 23 '15 at 21:21
  • That may be a side effect of the misplaced `\ ` inside your Awk program (you don't need line continuation characters inside Awk programs and you also don't need them inside a multi-line single-quoted shell string). Remove it and try again. – mklement0 Mar 23 '15 at 21:27
  • @mklement0 You are probably confusing the Awk program portion with the actual call to Awk where the variable is passed in, you don't need continuations after the first line but it doesn't hurt - force of habit. –  Mar 23 '15 at 22:14
  • 1
    The problem you were having is that `"foo" "bar" | getline` is ambiguous, just like `print > "foo" "bar"`. In both cases you need to parenthesize the string concatenation, i.e. `("foo" "bar") | getline` and `print > ("foo" "bar")` as otherwise some awks will treat the unparenthesized version as if you had written `"foo" ("bar" | getline)` and `(print > "foo") "bar"`. – Ed Morton Mar 23 '15 at 22:43
  • 1
    Thanks Ed, that will work. I was trying to articulate w/ my answer notes that Awk sees the string (without parenthesization) as 3 separate strings - Awk does not automatically consider the strings adjacent to the VAR (even without spaces) as a concatenation - when you encapsulate the string w/parenthesis the redirect works fine. In this case I think its a bit easier to read and w/ the close statement it is actually shorter this way so I'll leave it alone. –  Mar 23 '15 at 23:14
  • 1
    It all depends on the awk version - some awks will concatenate the strings first and others won't. POSIX leaves it up to the implementation to decide. IMHO you should always use a variable to hold the command since again IMHO you should always close it afterwards to avoid surprises. – Ed Morton Mar 24 '15 at 02:08
  • 1
    Thanks for clearing that up, @EdMorton. `mawk` needs the parentheses, `gawk` (GNU Awk) and BSD `awk` do not. @A.Danischewski: I now understand that a backslash as the _last_ character of an Awk program's interior line doesn't hurt, but in your case it is _followed by a space_ - and that breaks _some_ Awks, namely BSD `awk` (which is why I saw an error) and `gawk`. Re `bash`: note that `system()` and pipelines in Awk invoke `sh`, which may or may not be `bash` under the hood. In fact, since you seem to be running `mawk`, it sounds like you're on Ubuntu, where `sh` is `dash`. – mklement0 Mar 24 '15 at 03:27
  • 1
    Addendum: My statement re need for parentheses across Awk implementations above referred to the `"foo" "bar" | getline` ambiguity. Curiously, with respect to the `print > "foo" "bar"` ambiguity, it is BSD `awk` that needs the parentheses, whereas `gawk` and `mawk` do not. – mklement0 Mar 24 '15 at 03:44
0

By the way you wrote your example, it looks like you want to convert an awk record ( line ) into an associative array. Here's an awk executable script that allows that by running the bc command over values in a split type array:

#!/usr/bin/awk -f

{
    # initialize the a array
    cnt = split($0, a, FS)

    if( convertArrayBase(10, 2, a, cnt) > -1 ) {

        # use the array here
        for(i=1; i<=cnt; i++) {
            print a[i]
        }
    }
}

    # Destructively updates input array, converting numbers from ibase to obase
    #
    # @ibase:  ibase value for bc
    # @obase:  obase value for bc
    # @a:      a split() type associative array where keys are numeric
    # @cnt:    size of a ( number of fields )
    #
    # @return: -1 if there's a getline error, else cnt
    #
function convertArrayBase(ibase, obase, a, cnt,         i, b, cmd) {

    cmd = sprintf("echo \"ibase=%d;obase=%d", ibase, obase)
    for(i=1; i<=cnt; i++ ) {
        cmd = cmd ";" a[i]
    }
    cmd = cmd "\" | bc"

    i = 0 # reset i
    while( (cmd | getline b) > 0 ) {
        a[++i] = b
    }
    close( cmd )
    return i==cnt ? cnt : -1
}

When used with an input of:

1 2 3
4 s 1234567

this script outputs the following:

1
10
11
100
0
100101101011010000111

The convertArrayBase function operates on split type arrays. So you have to initialize the input array (a here) with the full row (as shown) or a field's subflds(not shown) before calling the it. It destructively updates the array.

You could instead call bc directly with some helper files to get similar output. I didn't find that bc supported - ( stdin as a file name ) so it's a little more than I'd like.

Making a start_cmds file like this:

ibase=10;obase=2;

and a quit_cmd like:

;quit

Given an input file (called data.semi) where the data is separated by a ;, like this:

1;2;3
4;s;1234567

you can run bc like:

$ bc -q start_cmds data.semi quit_cmd
1
10
11
100
0
100101101011010000111

which is the same data that the awk script is outputting, but only calling bc a single time with all of the inputs. Now, while that data isn't in an awk associative array in a script, the bc output could be written as stdin input to awk and reassembed into an array like:

bc -q start_cmds data.semi quit_cmd | awk 'FNR==NR {a[FNR]=$1; next} END { for( k in a ) print k, a[k] }' -
1 1
2 10
3 11
4 100
5 0
6 100101101011010000111

where the final dash is telling awk to treat stdin as an input file and lets you add other files later for processing.

n0741337
  • 2,474
  • 2
  • 15
  • 15