28

How can I refer to a string by index in sh/bash? That is, basically splitting it.

I am trying to strip 5 characters of a file name. All names have the structure: name_nr_code. I am trying to remove the 5 alphanumeric code bit. name_nr_ is always 10 characters.

Is there a thing like;

for i in * ; do mv "$i" "$i"[:10] ; done

1
  • 5
    Why the bash tag if you're asking for a sh solution? Aug 17, 2016 at 14:06

5 Answers 5

32

Simple as this.

(bash)

for i in * ; do mv -- "$i" "${i:0:5}" ; done

Voila.

And an explanation from Advanced Bash-Scripting Guide (Chapter 10. Manipulating Variables), (with extra NOTEs inline to highlight the errors in that manual):

Substring Extraction

${string:position}

Extracts substring from $string at $position.

If the $string parameter is "*" or "@", then this extracts the positional parameters, starting at $position.

${string:position:length}

Extracts $length characters of substring from $string at $position.

NOTE missing quotes around parameter expansions! echo should not be used for arbitrary data.

stringZ=abcABC123ABCabc
#       0123456789.....
#       0-based indexing.

echo ${stringZ:0}                       # abcABC123ABCabc
echo ${stringZ:1}                       # bcABC123ABCabc
echo ${stringZ:7}                       # 23ABCabc 

echo ${stringZ:7:3}                     # 23A
                                        # Three characters of substring.


# Is it possible to index from the right end of the string?

echo ${stringZ:-4}                      # abcABC123ABCabc
# Defaults to full string, as in ${parameter:-default}.
# However . . . 

echo ${stringZ:(-4)}                    # Cabc
echo ${stringZ: -4}                     # Cabc
# Now, it works.
# Parentheses or added space "escape" the position parameter.

The position and length arguments can be "parameterized," that is, represented as a variable, rather than as a numerical constant.


If the $string parameter is "*" or "@", then this extracts a maximum of $length positional parameters, starting at $position.

echo ${*:2}          # Echoes second and following positional parameters.
echo ${@:2}          # Same as above.

echo ${*:2:3}        # Echoes three positional parameters, starting at second.

NOTE: expr substr is a GNU extension.

expr substr $string $position $length

Extracts $length characters from $string starting at $position.

stringZ=abcABC123ABCabc
#       123456789......
#       1-based indexing.

echo `expr substr $stringZ 1 2`           # ab
echo `expr substr $stringZ 4 3`           # ABC

NOTE: That echo is redundant and makes it even less reliable. Use expr substr + "$string1" 1 2.

NOTE: expr will return with a non-zero exit status if the output is 0 (or -0, 00...).


BTW. The book is present in the official Ubuntu repository as abs-guide.

2
  • To say "position" is slightly misleading as it's actually an offset, which means that ${var:1} doesn't return the value of var from the "1st position", but actually from the 2nd.
    – Kusalananda
    Aug 18, 2016 at 5:56
  • That's true, but as long as you don't agree there can be a zero-th position. Which is fine with me.
    – user147505
    Aug 18, 2016 at 11:57
13

In POSIX sh,

  • "${var%?????}" is $var stripped of the last 5 trailing characters (or $var if $var contains fewer than 5 characters)

  • "${var%"${var#??????????}"}" is the first 10 characters of $var.

  • "${var%_*}" is $var stripped of the shortest string that matches _* at the end of $var (foo_bar_baz -> foo_bar).
  • "${var%%_*}": same but longest match instead of shortest match (foo_bar_baz -> foo).
  • if you wanted to get foo_bar_: "${var%"${var##*_}"}" (${var##pattern} is the same as ${var%%pattern} but looking for the pattern at the beginning of $var instead of the end).

With zsh:

  • $var[1,-6] for first character to 6th from the end (so all but the last 5).
  • $var[1,10] for first 10 characters.

With ksh, bash or zsh:

  • "${var:0:10}": first 10 characters of $var

With bash or zsh:

  • "${var:0:-5}": all but the last 5 characters (gives an error and exits the script if $var is set but contains fewer than 5 characters, also when $var is not set with zsh).

If you need Bourne sh compatibility, it's very difficult to do reliably. If you can guarantee the result won't end in newline characters you can do:

first_10=`expr " $var" : ' \(.{1,10\}\)'` # beware the exit status
                                          # may be non-zero if the
                                          # result is 0 or 0000000000

all_but_last_5=`expr " $var" : ' \(.*\).\{5\}'`

You'll also have a limit on the length of $var (varying between systems).

In all those solutions, if $var contains bytes that can't form part of valid characters, YMMV.

1
  • my, they really came up with some ugly syntax for inside those braces.
    – cat
    Aug 18, 2016 at 1:14
2

sh doesn't provide a built-in way of getting a substring out of a string (as far as I can see), but with bash you may do

${i:0:10}

This will give you the first ten characters of the value of the variable i.

The general format is ${variable:offset:length}.

2

Most shells support some kind of parameter expansion that can help you. In bash, you can use

substr=${string:4:5} # start at position 4, length 5.

In dash, offsets are not supported, but you can use leading and trailing patterns:

remove_first3=${string#???}
remove_last2=${string%??}
0

First off, don't use a for loop for file names.

Then, something like this should help.

find ./ -type f | while read filename ;do
  newfilename=$(echo ${filename}|cut -c 1-10)
  mv ${filename} ${newfilename}
done
3
  • 4
    Why is it bad to use for with file names?
    – choroba
    Aug 17, 2016 at 13:43
  • Quote your variables and use printf to be safer. ... and read -r.
    – Kusalananda
    Aug 17, 2016 at 13:47
  • 4
    The OP's for loop was fine except maybe for the missing --. I can see at least 10 bugs in your 4 lines of code! many of which well known bad practice like assuming file names are single line, use echo, missing quotes Aug 17, 2016 at 14:51

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.