You can with the head implementations that support that non-standard -c option and are smart enough not to read more than needed from their input when asking to output a fixed number of bytes:
string='SHOWpijfirefjTHISezpijSTRING' printf %s "$string" | { head -c 4 head -c 9 > /dev/null head -c 4 tail -c 6 }
See how it works with GNU head or the head builtin of ksh93, but not the head of busybox. If you run strace -fe read busybox sh ./that-script, you'll see:
[pid 7739] read(0, "SHOWpijfirefjTHISezpijSTRING", 4096) = 28
Where the first head consumes all the input by reading a full block and outputs 4 bytes of it, leaving nothing to read for the next head and tail commands.
While with GNU or ksh93 head:
[pid 10293] read(0, "SHOW", 4) = 4
Also beware head and tail with -c work with bytes, not characters¹ so can only be used to report fix numbers of characters with text encoded with one byte per character.
Most modern shells will have builtin operators to slice strings based on character position.
For instance, in zsh or yash:
slice=${string[1,4]}${string[14,17]}${string[-4,-1]}
In zsh, can be shortened to:
slice=$string[1,4]$string[14,17]$string[-6,-1]
And you can cut off bits in place:
$ string[5,13]= string[9,-7]= $ print -r -- $string SHOWTHISSTRING
Or with recent versions of ksh93, bash, zsh or mksh:
slice=${string:0:4}${string:13:4}${string: -6}
POSIXly:
tmp=${string#?????????????} slice=${string%"${string#????}"}${tmp%"${tmp#????}"}${string#"${string%????}"}
¹ despite what c may suggest. That -c option was added to some head implementations to align with the -c of tail, and the -c was added to tail long before the notion that a character could be made of more than one byte.
headandtailmake this sound like homework. Readman head tail.13is wrong: byte counts are from zero, but start positions start from 1. But generally, theheadandtailore OK -- the redirections, subshells and pipes are the issue.