How to get the char at a given position of a string in shell script?
7 Answers
In bash with "Parameter Expansion" ${parameter:offset:length}
$ var=abcdef
$ echo ${var:0:1}
a
$ echo ${var:3:1}
d
The same parameter expansion can be used to assign a new variable:
$ x=${var:1:1}
$ echo $x
b
Edit: Without parameter expansion (not very elegant, but that's what came to me first)
$ charpos() { pos=$1;shift; echo "$@"|sed 's/^.\{'$pos'\}\(.\).*$/\1/';}
$ charpos 8 what ever here
r
-
1http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion has more details – jsbillings Mar 17 '11 at 15:24
-
Not POSIX apparently: http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html – Ciro Santilli OurBigBook.com Aug 10 '17 at 14:03
-
1You can also set the offset 'from end' like so `echo ${var: -2:1}` – Vassilis Jun 01 '18 at 15:37
-
That syntax comes from ksh93 and is also supported by `zsh` and `mksh`. – Stéphane Chazelas Sep 06 '19 at 08:08
Alternative to parameter expansion is expr substr
substr STRING POS LENGTH
substring of STRING, POS counted from 1
For example:
$ expr substr hello 2 1
e
- 29,087
- 16
- 80
- 60
-
-
3While this appears to work with the expr from GNU coreutils, `substr` is not included in the expr from FreeBSD, NetBSD or OS X. This isn't a portable solution. – ghoti Feb 14 '17 at 17:04
-
1@ghoti, note that `substr` is not originally a GNU extension. The original implementation of `expr` came from PWB Unix in the late 70s and had `substr` (but not `:`). – Stéphane Chazelas Sep 06 '19 at 08:06
-
@StéphaneChazelas, thanks for adding historical perspective. :) While I'm pretty sure PWB usage isn't relevant to the OP, it's always fun to track features and changes through the decades. GNU tends to be many people's default, but in general, I think I'd avoid using options that aren't clearly [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/expr.html), and are known to be missing from major unices. – ghoti Sep 06 '19 at 20:47
cut -c
If the variable does not contain newlines you can do:
myvar='abc'
printf '%s\n' "$myvar" | cut -c2
outputs:
b
awk substr is another POSIX alternative that works even if the variable has newlines:
myvar="$(printf 'a\nb\n')" # note that the last newline is stripped by
# the command substitution
awk -- 'BEGIN {print substr (ARGV[1], 3, 1)}' "$myvar"
outputs:
b
printf '%s\n' is to avoid problems with escape characters: https://stackoverflow.com/a/40423558/895245 e.g.:
myvar='\n'
printf '%s\n' "$myvar" | cut -c1
outputs \ as expected.
Tested in Ubuntu 19.04.
- 522,931
- 91
- 1,010
- 1,501
- 17,176
- 4
- 113
- 99
-
1`printf '%s' "$myvar" | cut -c2` is not POSIX as the output of `printf` is not text unless `$myvar` ends in a newline character. It otherwise assumes the variable doesn't contain newline characters as `cut` cuts *each line* of its input. – Stéphane Chazelas Sep 06 '19 at 08:11
-
1The `awk` one would be more efficient and reliable with `awk -- 'BEGIN {print substr (ARGV[1], 2, 1)}' "$myvar"` – Stéphane Chazelas Sep 06 '19 at 08:11
-
Note that with current versions of GNU `cut`, that doesn't work for multi-byte characters (same for mawk or busybox awk) – Stéphane Chazelas Sep 06 '19 at 08:12
-
@StéphaneChazelas thanks for the points. I didn't understand what you mean in the first one: do you mean that `printf 'abc '| cut -c2` is wrong because no `\n` (this I don't know about), or that the command will fail if myvar has newlines (this I agree)? – Ciro Santilli OurBigBook.com Sep 06 '19 at 08:18
-
2The behaviour of `cut` is unspecified if the input is not text (though `cut` implementations are required to handle lines or arbitrary length). The output of `printf abc` is not *text* as it doesn't end in a newline character. In practice depending on the implementation, if you pipe that to `cut -c2`, you get either `b`, `b
` or nothing at all. You'd need `printf 'abc\n' | cut -c2` to get a behaviour specified by POSIX (that's required to output `b – Stéphane Chazelas Sep 06 '19 at 11:00`) -
1@StéphaneChazelas ah OK, awesome, I wasn't aware that POSIX defined what a "text file" is! https://unix.stackexchange.com/questions/446237/what-conditions-must-be-met-for-a-file-to-be-a-text-file-as-defined-by-posix – Ciro Santilli OurBigBook.com Sep 06 '19 at 11:02
With zsh or yash, you'd use:
$ text='€$*₭£'
$ printf '%s\n' "${text[3]}"
*
(in zsh, you can shorten it to printf '%s\n' $text[3]).
- 522,931
- 91
- 1,010
- 1,501
You can use the cut command. To get the 3rd postion:
echo "SAMPLETEXT" | cut -c3
Check this link http://www.folkstalk.com/2012/02/cut-command-in-unix-linux-examples.html
(Advanced cases) However, modifying IFS is also a good thing, especially when your input might have spaces. In that case alone, use the one below
saveifs=$IFS
IFS=$(echo -en "\n\b")
echo "SAMPLETEXT" | cut -c3
IFS=$saveifs
- 33
- 6
-
1I can't see how `IFS` would come into play in the code that you have posted. – Kusalananda Aug 07 '18 at 07:01
This is a portable POSIX shell variant, which only uses builtins.
First a oneliner, than a better readable function.
It uses "parameter expansion" explained here:
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02
#!/bin/sh
x(){ s=$1;p=$2;i=0;l=${#s};while i=$((i+1));do r=${s#?};f=${s%"$r"};s=$r;case $i in $p)CHAR=$f&&return;;$l)return 1;;esac;done;}
x ABCDEF 3 # output substring at pos 3
echo $CHAR # output is 'C'
Here the oneliner explained.
#!/bin/sh
string_get_pos()
{
local string="$1" # e.g. ABCDEF
local pos="$2" # e.g. 3
local rest first i=0
local length="${#string}" # e.g. 6
while i=$(( i + 1 )); do
rest="${string#?}" # e.g. BCDEF
first=${string%"$rest"} # e.g. A
string="$rest"
case "$i" in
$pos) export CHAR="$first" && return 0 ;;
$length) return 1 ;;
esac
done
}
string_get_pos ABCDEF 3
echo $CHAR # output is 'C'
- 25
- 5
shell cut - print specific range of characters or given part from a string
#method1) using bash
str=2020-08-08T07:40:00.000Z
echo ${str:11:8}
#method2) using cut
str=2020-08-08T07:40:00.000Z
cut -c12-19 <<< $str
#method3) when working with awk
str=2020-08-08T07:40:00.000Z
awk '{time=gensub(/.{11}(.{8}).*/,"\\1","g",$1); print time}' <<< $str
- 101