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 - 1gnu.org/software/bash/manual/… has more detailsjsbillings– jsbillings2011-03-17 15:24:22 +00:00Commented Mar 17, 2011 at 15:24
- Not POSIX apparently: pubs.opengroup.org/onlinepubs/009695399/utilities/…Ciro Santilli OurBigBook.com– Ciro Santilli OurBigBook.com2017-08-10 14:03:29 +00:00Commented Aug 10, 2017 at 14:03
- 1You can also set the offset 'from end' like so
echo ${var: -2:1}Vassilis– Vassilis2018-06-01 15:37:56 +00:00Commented Jun 1, 2018 at 15:37 - That syntax comes from ksh93 and is also supported by
zshandmksh.Stéphane Chazelas– Stéphane Chazelas2019-09-06 08:08:19 +00:00Commented Sep 6, 2019 at 8: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 - cool, should have checked expr more thoroughly.forcefsck– forcefsck2011-03-17 18:41:46 +00:00Commented Mar 17, 2011 at 18:41
- 3While this appears to work with the expr from GNU coreutils,
substris not included in the expr from FreeBSD, NetBSD or OS X. This isn't a portable solution.ghoti– ghoti2017-02-14 17:04:43 +00:00Commented Feb 14, 2017 at 17:04 - 1@ghoti, note that
substris not originally a GNU extension. The original implementation ofexprcame from PWB Unix in the late 70s and hadsubstr(but not:).Stéphane Chazelas– Stéphane Chazelas2019-09-06 08:06:31 +00:00Commented Sep 6, 2019 at 8: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, and are known to be missing from major unices.ghoti– ghoti2019-09-06 20:47:00 +00:00Commented Sep 6, 2019 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.
- 1
printf '%s' "$myvar" | cut -c2is not POSIX as the output ofprintfis not text unless$myvarends in a newline character. It otherwise assumes the variable doesn't contain newline characters ascutcuts each line of its input.Stéphane Chazelas– Stéphane Chazelas2019-09-06 08:11:01 +00:00Commented Sep 6, 2019 at 8:11 - 1The
awkone would be more efficient and reliable withawk -- 'BEGIN {print substr (ARGV[1], 2, 1)}' "$myvar"Stéphane Chazelas– Stéphane Chazelas2019-09-06 08:11:58 +00:00Commented Sep 6, 2019 at 8: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– Stéphane Chazelas2019-09-06 08:12:48 +00:00Commented Sep 6, 2019 at 8:12 - 2The behaviour of
cutis unspecified if the input is not text (thoughcutimplementations are required to handle lines or arbitrary length). The output ofprintf abcis not text as it doesn't end in a newline character. In practice depending on the implementation, if you pipe that tocut -c2, you get eitherb,b<newline>or nothing at all. You'd needprintf 'abc\n' | cut -c2to get a behaviour specified by POSIX (that's required to outputb<newline>)Stéphane Chazelas– Stéphane Chazelas2019-09-06 11:00:06 +00:00Commented Sep 6, 2019 at 11:00 - 1@StéphaneChazelas ah OK, awesome, I wasn't aware that POSIX defined what a "text file" is! unix.stackexchange.com/questions/446237/…Ciro Santilli OurBigBook.com– Ciro Santilli OurBigBook.com2019-09-06 11:02:40 +00:00Commented Sep 6, 2019 at 11:02
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 - 1I can't see how
IFSwould come into play in the code that you have posted.2018-08-07 07:01:19 +00:00Commented Aug 7, 2018 at 7:01
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]).
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' 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