40

How to get the char at a given position of a string in shell script?

54

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
4
12

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
4
  • cool, should have checked expr more thoroughly.
    – forcefsck
    Mar 17 '11 at 18:41
  • 3
    While 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 :). Sep 6 '19 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
    Sep 6 '19 at 20:47
10

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.

See also: https://stackoverflow.com/questions/1405611/extracting-first-two-characters-of-a-string-shell-scripting

Tested in Ubuntu 19.04.

6
  • 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. Sep 6 '19 at 8:11
  • 1
    The awk one would be more efficient and reliable with awk -- 'BEGIN {print substr (ARGV[1], 2, 1)}' "$myvar" Sep 6 '19 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) Sep 6 '19 at 8:12
  • 2
    The 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<newline> 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<newline>) Sep 6 '19 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/… Sep 6 '19 at 11:02
2

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]).

1

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
1
  • 1
    I can't see how IFS would come into play in the code that you have posted.
    – Kusalananda
    Aug 7 '18 at 7:01
0

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
0

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'

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.