1

Linux's sys filesystem represents sets of CPU ids with the syntax:

  1. 0,2,8: Set of CPUs containing 0, 2 and 8.
  2. 4-6: Set of CPUs containing 4, 5 and 6.
  3. Both syntaxes can be mixed and matched, for example: 0,2,4-6,8

For example, running cat /sys/devices/system/cpu/online prints 0-3 on my machine which means CPUs 0, 1, 2 and 3 are online.

The problem is the above syntax is difficult to iterate over using a for loop in a shell script. How can the above syntax be converted to one more conventional such as 0 2 4 5 6 8?

Ryan
  • 2,378
  • 1
  • 19
  • 29
  • Stack Overflow is a site for programming and development questions. This question appears to be off-topic because it is not about programming or development. See [What topics can I ask about here](http://stackoverflow.com/help/on-topic) in the Help Center. Perhaps [Super User](http://superuser.com/) or [Unix & Linux Stack Exchange](http://unix.stackexchange.com/) would be a better place to ask. – jww May 19 '18 at 22:31
  • 1
    @jww For what it is worth, shell is a programming language and used in software development. The question is not about Linux per se (Linux is used to bring context to the problem); rather, it is about converting one string format to another. I'd say converting being string formats is in the domain of SO. – Ryan May 19 '18 at 22:58

3 Answers3

2

Try:

$ echo 0,2,4-6,8 | awk '/-/{for (i=$1; i<=$2; i++)printf "%s%s",i,ORS;next} 1' ORS=' ' RS=, FS=-
0 2 4 5 6 8

This can be used in a loop as follows:

for n in $(echo 0,2,4-6,8 | awk '/-/{for (i=$1; i<=$2; i++)printf "%s%s",i,ORS;next} 1' RS=, FS=-)
do
   echo cpu="$n"
done

Which produces the output:

cpu=0
cpu=2
cpu=4
cpu=5
cpu=6
cpu=8

Or like:

printf "%s" 0,2,4-6,8 | awk '/-/{for (i=$1; i<=$2; i++)printf "%s%s",i,ORS;next} 1' RS=, FS=- | while read n
do
   echo cpu="$n"
done

Which also produces:

cpu=0
cpu=2
cpu=4
cpu=5
cpu=6
cpu=8

How it works

The awk command works as follows:

  • RS=,

    This tells awk to use , as the record separator.

    If, for example, the input is 0,2,4-6,8, then awk will see four records: 0 and 2 and 4-6 and 8.

  • FS=-

    This tells awk to use - as the field separator.

    With FS set this way and if, for example, the input record consists of 2-4, then awk will see 2 as the first field and 4 as the second field.

  • /-/{for (i=$1; i<=$2; i++)printf "%s%s",i,ORS;next}

    For any record that contains -, we print out each number starting with the value of the first field, $1, and ending with the value of the second field, $2. Each such number is followed by the Output Record Separator, ORS. By default, ORS is a newline character. For some of the examples above, we set ORS to a blank.

    After we have printed these numbers, we skip the rest of the commands and jump to the next record.

  • 1

    If we get here, then the record did not contain - and we print it out as is. 1 is awk's shorthand for print-the-line.

John1024
  • 109,961
  • 14
  • 137
  • 171
0

A Perl one:

echo "0,2,4-6,8" | perl -lpe 's/(\d+)-(\d+)/{$1..$2}/g; $_="echo {$_}"' | bash

Just convert the original string into echo {0,2,{4..6},8} and let bash 'brace expansion' to interpolate it.

jxc
  • 13,553
  • 4
  • 16
  • 34
-1
eval echo $(cat /sys/devices/system/cpu/online | sed 's/\([[:digit:]]\+\)-\([[:digit:]]\+\)/$(seq \1 \2)/g' | tr , ' ')

Explanation:

  1. cat /sys/devices/system/cpu/online reads the file from sysfs. This can be changed to any other file such as offline.
  2. The output is piped through the substitution s/\([[:digit:]]\+\)-\([[:digit:]]\+\)/$(seq \1 \2)/g. This matches something like 4-6 and replaces it with $(seq 4 6).
  3. tr , ' ' replaces all commas with spaces.
  4. At this point, the input 0,2,4-6,8 is transformed to 0 2 $(seq 4 6) 8. The final step is to eval this sequence to get 0 2 4 5 6 8.
  5. The example echo's the output. Alternatively, it can be written to a variable or used in a for loop.
Ryan
  • 2,378
  • 1
  • 19
  • 29