2

I found here how to extract a substring in bash, but I'm lost as to how to apply this after a pipe. For instance:

some func | echo ${string:12:5}

How do I assign the output of some func to the variable string?

4

${string:offset:length} is a parameter expansion operator that expands to range of characters in the $string variable.

To get a range of bytes from the input (and that would then also apply to single-byte characters), you can use:

func | tail -c +12 | head -c 5

to get 5 bytes starting with the 12th one (1-based offsets). The -c option to head is not standard but pretty common.

Note that func may be killed sometime after it has output its 16th byte, as head will exit after having output those 5 bytes, and tail would be killed if it tries to write more data after that, which would ripple to func.

You can also do:

func | dd bs=1 skip=11 count=5 2> /dev/null

2> /dev/null is to avoid the status message at the end. That suppresses all errors though. With GNU dd, you can replace it with status=none to only suppress the status.

For large values of length, that would be less efficient as it reads one byte at a time. With GNU dd again, you can avoid that by doing:

func | dd iflag=count_bytes,skip_bytes,fullblock skip=11 bs=64k count=5M status=none

Which would do as many reads of up to 64KiB each to get 5MiB bytes of data.

Now for those offset and length to be expressed in characters (single or multi-byte) instead of byte, that becomes more complicated.

An option is to store the whole output in a variable and use the ${var:offset:length} operator as others have shown. Though that means storing the whole output in memory. Using var=$(func) also means that the trailing newline characters are discarded.

Another option is to use bash's read -N which does read a given number of characters:

func | {
  IFS= read -rN 11 discarded
  IFS= read -rN 5 data
  printf '%s\n' "$data"
}

Or with perl (slightly more efficient for large data):

func | perl -Mopen=locale -sne '
  BEGIN{$total = $o + $n; $/ = \$total}
  print substr($_, $o); exit' -- -o=10000 -n=5000000
2
string="$(func)"
echo "${string:12:5}"
2

Answer

If you only wish to extract the output from some_func, you don't need to store it in a variable, you can just send the output to cut which will extract the requested characters:

some_func | cut -c 12-16  

Explanation

cut Will take the stdin and extract the requested range based on specified options.

-c Means that the range is specified in characters.

12-16 Range in which characters start with index 1, not 0.
Therefore, this will take characters at position 12,13,14,15 and 16.

As Stéphane Chazelas remainded me, note that this will work for each line of input, not just the first line.

  • 1
    Note that it gives the 12th to 16th characters (bytes with some implementation) of each line of the input, not of the input as a whole. – Stéphane Chazelas May 11 '18 at 16:31
  • Yes, you are correct, maybe i should've noted that from the beginning. I'll update the answer. – Iskustvo May 11 '18 at 22:37

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.