For word in string bash

I’m surprised no one has mentioned the obvious bash solution utilizing only while and read.

while read -n1 character; do
    echo "$character"
done < <(echo -n "$words")

Note the use of echo -n to avoid the extraneous newline at the end. printf is another good option and may be more suitable for your particular needs. If you want to ignore whitespace then replace "$words" with "${words// /}".

Another option is fold. Please note however that it should never be fed into a for loop. Rather, use a while loop as follows:

while read char; do
    echo "$char"
done < <(fold -w1 <<<"$words")

The primary benefit to using the external fold command (of the coreutils package) would be brevity. You can feed it’s output to another command such as xargs (part of the findutils package) as follows:

fold -w1 <<<"$words" | xargs -I% -- echo %

You’ll want to replace the echo command used in the example above with the command you’d like to run against each character. Note that xargs will discard whitespace by default. You can use -d 'n' to disable that behavior.


Internationalization

I just tested fold with some of the Asian characters and realized it doesn’t have Unicode support. So while it is fine for ASCII needs, it won’t work for everyone. In that case there are some alternatives.

I’d probably replace fold -w1 with an awk array:

awk 'BEGIN{FS=""} {for (i=1;i<=NF;i++) print $i}'

Or the grep command mentioned in another answer:

grep -o .

Performance

FYI, I benchmarked the 3 aforementioned options. The first two were fast, nearly tying, with the fold loop slightly faster than the while loop. Unsurprisingly xargs was the slowest… 75x slower.

Here is the (abbreviated) test code:

words=$(python -c 'from string import ascii_letters as l; print(l * 100)')

testrunner(){
    for test in test_while_loop test_fold_loop test_fold_xargs test_awk_loop test_grep_loop; do
        echo "$test"
        (time for (( i=1; i<$((${1:-100} + 1)); i++ )); do "$test"; done >/dev/null) 2>&1 | sed '/^$/d'
        echo
    done
}

testrunner 100

Here are the results:

test_while_loop
real    0m5.821s
user    0m5.322s
sys     0m0.526s

test_fold_loop
real    0m6.051s
user    0m5.260s
sys     0m0.822s

test_fold_xargs
real    7m13.444s
user    0m24.531s
sys     6m44.704s

test_awk_loop
real    0m6.507s
user    0m5.858s
sys     0m0.788s

test_grep_loop
real    0m6.179s
user    0m5.409s
sys     0m0.921s

Is it possible to format this sample:

for i in string1 string2 stringN
do
 echo $i
done

to something similar to this:

for i in 
string1
string2
stringN
do
 echo $i
done

EDIT: Sorry for confusion, didn’t realize that there was different methods of executing script — sh <scriptname> versus bash <scriptname> and also this thing which I cannot name right now — #!/bin/sh and #!/bin/bash :)

asked Jun 20, 2018 at 18:14

5

Using arrays in bash can aid readability: this array syntax allows arbitrary whitespace between words.

strings=(
    string1
    string2
    "string with spaces"
    stringN
)
for i in "${strings[@]}"; do
    echo "$i"
done

answered Jun 20, 2018 at 18:29

glenn jackman's user avatar

glenn jackmanglenn jackman

82.6k14 gold badges115 silver badges166 bronze badges

8

You can escape the linebreak with a backslash:

$ for i in 
> hello 
> world
> do
> echo $i
> done
hello
world
$

answered Jun 20, 2018 at 18:16

Andy Dalton's user avatar

Andy DaltonAndy Dalton

13.4k1 gold badge26 silver badges45 bronze badges

You may escape the newlines before/after each item that you loop over:

for i in 
    string1 
    string2 
    stringN
do
   printf '%sn' "$i"
done

Or, for this simple example:

printf '%sn' string1 string2 stringN

which has the same result.

Related:

  • Why do I need to place “do” in the same line as “for”?

Variation using a bash array:

strings=(
    string1
    string2
    stringN
)

printf '%sn' "${strings[@]}"

answered Jun 20, 2018 at 18:31

Kusalananda's user avatar

KusalanandaKusalananda

312k35 gold badges614 silver badges909 bronze badges

0

list='a b c d'
for element in $list;do 
    echo "$element"
done

Bart's user avatar

Bart

2,1011 gold badge9 silver badges26 bronze badges

answered Jul 23, 2019 at 8:06

guest's user avatar

guestguest

412 bronze badges

2

If switching to zsh is an option:

for string (
  string1
  'other string'
  etc..
) printf '%sn' "$string"

answered Jun 20, 2018 at 19:42

Stéphane Chazelas's user avatar

Stéphane ChazelasStéphane Chazelas

506k90 gold badges980 silver badges1461 bronze badges

Same thing, less text:

array=(
        string{1..7}
)

for i in "${array[@]}"; do
    echo "$i"
done

answered Jan 28, 2020 at 11:59

Cornelis van Ginkel's user avatar

2

You can loop over a list of strings (with and without embedded spaces) as follows:

#!/usr/bin/env bash

var1="text-without-spaces"
var2="text with spaces"

for item in "${var1}" "${var2}" "more-without-spaces" "more with spaces"; do
    echo "'${item}'"
done

NB You can leave out braces around variable identifiers in the above example e.g. use $var1 instead of ${var1}.

This outputs:

‘text-without-spaces’
‘text with spaces’
‘more-without-spaces’
‘more with spaces’

answered Feb 17 at 11:59

Petr Vepřek's user avatar

You can use the loop command, available here, like so:

$ loop 'echo "$ITEM"' --for string1,string2,string3

or, if you have the list in a file, one per line:

$ <file_list.txt loop 'echo "$ITEM"'

Beware however that it runs one sh invocation per item (to interpret that code whilst the item is stored in the ITEM environment variable), and that it currently chokes on sequences of bytes that don’t form valid characters in UTF-8 (even if the locale’s charmap is not UTF-8).

Stéphane Chazelas's user avatar

answered Jun 27, 2018 at 13:47

Rich Jones's user avatar

Rich JonesRich Jones

1511 silver badge2 bronze badges

��� ���� �� �������� �������������� ������. � ���
����������� ���������� �� ������� � ����� C.

for arg in [list]
do
��������(�)
done

Note

�� ������ ������� �����, ����������-��������
����� arg
���������������, ���� �� ������, ���������
�������� �� ������ list.

for arg in "$var1" "$var2" "$var3" ... "$varN"
# �� ������ �������, $arg = $var1
# �� ������ �������, $arg = $var2
# �� ������� �������, $arg = $var3
# ...
# �� N-��� �������, $arg = $varN

# �������� ������ ��������� � ������� ��� ����, ����� ������������� ��������� ��������� �� �� ��������� ��������� (�����).

�������� ������ ����� �������� � ���� ���������
�������.

��� �������� ����� do ��������� � ����� ������ ��
������ for, �� ����� ������ ����������
(����� do) ���������� ������� ����� � �������.

for arg in [list] ; do

������ 10-1. ������� ���� for

#!/bin/bash
# ������ ������.

for planet in �������� ������ ����� ���� ������ ������ ���� ������ ������
do
  echo $planet
done

echo

# ���� '������ ����������' ��������� � �������, �� �� ����� �������������� ��� ������������ �������� .
for planet in "�������� ������ ����� ���� ������ ������ ���� ������ ������"
do
  echo $planet
done

exit 0

Note

������ �� ��������� [������]
����� ��������� ��������� ����������. ��� ������
�������� ��� ��������� ����� ����������. � ����
������, ��� ��������������� ������� ������� ��
���������� � ������, ���������� ������������
���������� set (��. ������ 11-13).

������ 10-2. ���� for � ����� ����������� �
������ �� ��������� ������

#!/bin/bash
# ������ ������.

# ��� ����� ������� ������������� � ����������� �� ������� �� ������ (���. ����).

for planet in "�������� 36" "������ 67" "����� 93"  "���� 142" "������ 483"
do
  set -- $planet  # ��������� ���������� "planet" �� ��������� ���������� (����������� ����������).
  # ����������� "--" ������������ �� ��������������, ���� $planet "�����" ��� ���������� � ������� "-".

  # ���� ������ �� ���������� ����������� ���������, ��������� �� ��������� ������� ��� ����� "������" ������ ����������,
  # �� ����� ��������� �� � ������,
  #        original_params=("$@")

  echo "$1      � $2,000,000 ���� �� ������"
  #----��� ���������---� ��������� $2 ��������� ����
done

# (������� S.C., �� �����������.)

exit 0

� �������� ������, � ����� for, ����� ������������
����������.

������ 10-3. Fileinfo: ���������
������ ������, ������������ � ����������

#!/bin/bash
# fileinfo.sh

FILES="/usr/sbin/privatepw
/usr/sbin/pwck
/usr/sbin/go500gw
/usr/bin/fakefile
/sbin/mkreiserfs
/sbin/ypbind"     # ������ ������������ ��� ������.
                  # � ������ �������� ��������� ���� /usr/bin/fakefile.

echo

for file in $FILES
do

  if [ ! -e "$file" ]       # �������� ������� �����.
  then
    echo "���� $file �� ������."; echo
    continue                # ������� � ��������� ��������.
  fi

  ls -l $file | awk '{ print $8 "         ������: " $5 }'  # ������ 2 �����.
  whatis `basename $file`   # ���������� � �����.
  echo
done  

exit 0

[������] �����
for ����� ���� ������������
����� ������, ������� � ���� ������� ����� ���������
�������-�������.

������ 10-4. ��������� ������ ������ � �����
for

#!/bin/bash
# list-glob.sh: �������� ������ ������ � ����� for � ��������������
# �������� ����������� ���� ������ ("globbing").

echo

for file in *
do
  ls -l "$file"  # ������ ���� ������ � $PWD (������� ��������).
  # ���������, ��� ������� "*" ������������� ����� ��� �����,
  # ������, � ��������� ����������� ���� ������ ("globbing"),
  # ������� ���������� -- ����� ������, ������������ � �����.

  # ���� � �������� ��� �� ������ �����, ���������������� �������,
  # �� �� ��� ����� ����������� ��� ������.
  # ����� �������� �����, ����������� ���� nullglob
  # (shopt -s nullglob).
  # ������� S.C.
done

echo; echo

for file in [jx]*
do
  rm -f $file    # �������� ������, ������������ � "j" ��� "x" � $PWD.
  echo "������ ���� "$file"".
done

echo

exit 0

���� [������] � �����
for �� �����, �� � ��������
����� ������������ ���������� $@ — ������ ���������� ���������
������. ���� ��������� ��� ����������� �����������������
� ������ A-18.

������ 10-5. ���� for ��� ������
����������

#!/bin/bash

# ���������� ������� ���� �������� � ����������� � ��� ��� � ���������� �� ����������.

for a
do
 echo -n "$a "
done

#  ������ ���������� �� �����, ������� ���� �������� � ���������� '$@'
#+ (������ ���������� ��������� ������, ������� ���������� �������).

echo

exit 0

��� �������� ������ ����������, � ����� for
����������� ������������ ������������
������. ��. ������ 12-39, ������ 10-10 � ������ 12-33.

������ 10-6. �������� ������ ���������� �
����� for � ������� �������� �����������
������

#!/bin/bash
# ���� for �� [�������], ��������� � ������� ����������� ������.

NUMBERS="9 7 3 8 37.53"

for number in `echo $NUMBERS`  # for number in 9 7 3 8 37.53
do
  echo -n "$number "
done

echo 
exit 0

����� ������� ������ ������������� ����������� ������
��� �������� ������ ���������� �����.

������ 10-7. grep ��� ��������
������

#!/bin/bash
# bin-grep.sh: ����� ����� � �������� ������.

# ������ "grep" ��� �������� ������.
# ���������� ������� "grep -a"

E_BADARGS=65
E_NOFILE=66

if [ $# -ne 2 ]
then
  echo "������� �������������: `basename $0` string filename"
  exit $E_BADARGS
fi

if [ ! -f "$2" ]
then
  echo "���� "$2" �� ������."
  exit $E_NOFILE
fi


for word in $( strings "$2" | grep "$1" )
# ���������� "strings" ���������� ������ ����� � �������� ������.
# ������� ����� ���������� �� ��������� ������� "grep", ��� ���������� ������.
do
  echo $word
done

# ��� ��������� S.C., �������������� ���������� ����� for ����� ���� ��������
#    strings "$2" | grep "$1" | tr -s "$IFS" '[n*]'


# ���������� ��� ������ ��������:  "./bin-grep.sh mem /bin/ls"

exit 0

��� ���� ������.

������ 10-8. ������ ���� �������������
�������

#!/bin/bash
# userlist.sh

PASSWORD_FILE=/etc/passwd
n=1           # ����� �������������

for name in $(awk 'BEGIN{FS=":"}{print $1}' < "$PASSWORD_FILE" )
# ����������� ����� = :  ^^^^^^
# ����� ������� ����              ^^^^^^^^
# ������ ������� �� ����� �������            ^^^^^^^^^^^^^^^^^
do
  echo "������������ #$n = $name"
  let "n += 1"
done


# ������������ #1 = root
# ������������ #2 = bin
# ������������ #3 = daemon
# ...
# ������������ #30 = bozo

exit 0

� �������������� ������ ������������� �����������
������ ��� �������� [������].

������ 10-9. �������� ��������� ����
�������� ������ � ������� ��������

#!/bin/bash
# findstring.sh:
# ����� �������� ������ � �������� �����.

directory=/usr/local/bin/
fstring="Free Software Foundation"  # ����� ������ �� FSF.

for file in $( find $directory -type f -name '*' | sort )
do
  strings -f $file | grep "$fstring" | sed -e "s%$directory%%"
  #  ������� "sed" ���������� ��������� (���� -e),
  #+ ��� ����, ����� �������� ������� ����������� "/" ������ ������ � ������ ������
  #+ ��������� "/" - ���� �� ����������������� ��������.
  #  ������������� ������ ������� ��������� ��������� �� ������ (����������).
done

exit 0

#  ����������:
#  ---------------
#  �������� �������� ����� �������, ����� �� ����
#+ $directory � $fstring �� ��������� ������.

��������� ������ ����� for ����� ������������ ������
�������� �� ���������.

������ 10-10. ������ ������������� ������ �
��������

#!/bin/bash
# symlinks.sh: ������ ������������� ������ � ��������.


directory=${1-`pwd`}
#  ��-��������� � ������� ��������,
#  ���� ����, ������� ��������� ����������� ��������.
# ----------------------------------------------------------
# ARGS=1                 # ��������� ���� �������� ��������� ������.
#
# if [ $# -ne "$ARGS" ]  # ���� ������� ������ �� �����...
# then
#   directory=`pwd`      # ������� �������
# else
#   directory=$1
# fi
# ----------------------------------------------------------

echo "������������� ������ � �������� "$directory""

for file in "$( find $directory -type l )"   # -type l = ������������� ������
do
  echo "$file"
done | sort             # � ��������� ������ ��������� ����������������� ������.

#  ��� �������� Dominik 'Aeneas' Schnitzer,
#+ � ������ ���������� ������� ��� $( find $directory -type l )
#+ �������� "���������" ������� ������, ����������� �������.

exit 0

����� ����� ����� ���� ������������� �� stdout � ����, ���� ����������
������� ���������������� ������� ����������� �������,
��������������� ��� �����������.

������ 10-11. ������ ������������� ������ �
��������, ����������� � �����

#!/bin/bash
# symlinks.sh: ������ ������������� ������ � ��������.

OUTFILE=symlinks.list                         # ���� �� �������

directory=${1-`pwd`}
#  ��-��������� -- ������� �������,

echo "������������� ������ � �������� "$directory"" > "$OUTFILE"
echo "---------------------------" >> "$OUTFILE"

for file in "$( find $directory -type l )"    # -type l = ������������� ������
do
  echo "$file"
done | sort >> "$OUTFILE"                     # ��������������� ������
#           ^^^^^^^^^^^^^                       � ����.

exit 0

�������� ����� for ����� � ��������������
��������� ������ — ����� ������� �� ��������� ���������
for � ����� C. ��� ����� ������������ ������� �������
������.

������ 10-12. C-�������� ��������� ���������
����� for

#!/bin/bash
# ��� �������� ���������� �����.

echo

# ����������� ���������.
for a in 1 2 3 4 5 6 7 8 9 10
do
  echo -n "$a "
done

echo; echo

# +==========================================+

# � ������ C-�������� ���������.

LIMIT=10

for ((a=1; a <= LIMIT ; a++))  # ������� ������� ������ � "LIMIT" ��� "$".
do
  echo -n "$a "
done                           # ����������� ������������ �� 'ksh93'.

echo; echo

# +=========================================================================+

# ��������� � C-���� �������� "�������".

for ((a=1, b=1; a <= LIMIT ; a++, b++))  # ������� ��������� ��� ��������, ������� ����������� ���������.
do
  echo -n "$a-$b "
done

echo; echo

exit 0

��. ��� �� ������ 25-10, ������ 25-11 � ������ A-7.

� ������ ������ ��������, ������� ����� �����
«��������» ����������.

������ 10-13. ������ � �������� efax �
�������� ������

#!/bin/bash

EXPECTED_ARGS=2
E_BADARGS=65

if [ $# -ne $EXPECTED_ARGS ]
# �������� ������� ���������� ��������� ������.
then
   echo "������� �������������: `basename $0` phone# text-file"
   exit $E_BADARGS
fi


if [ ! -f "$2" ]
then
  echo "���� $2 �� �������� ��������� ������"
  exit $E_BADARGS
fi


fax make $2              # ������� fax-����� �� ��������� ������.

for file in $(ls $2.0*)  # ��� �����, ������������ � ���������� ��������������.
                         # ������������ ��������� ������ � ������.
do
  fil="$fil $file"
done

efax -d /dev/ttyS3 -o1 -t "T$1" $fil   # ���������.


# ��� ��������� S.C., � ���� for ����� ���� ��������� ���� ������� �������� � ����:
#    efax -d /dev/ttyS3 -o1 -t "T$1" $2.0*
# �� ��� �� ��� ����������� [;-)].

exit 0

�������� while ��������� ������� ����� ������� ������
�������� � ���� ������� ������� (���� ��� �������� �����
0), �� ���������� ����������
� ���� �����. � ������� �� ������ for, ����� while ������������ � ���
�������, ����� ���������� �������� ������� ��
��������.

while [condition]
do
�command
done

��� � � ������ � ������� for/in, ��� ���������� ���������
����� do � ����� ������ � �����������
�����, ���������� ��������� ������ «;» �����
do.

while [condition] ; do

�������� ��������: � ��������� �������, ����� ���
������������� ����������� getopts ��������� �
���������� while, ��������� ���������
���������� �� ����������� �����.

������ 10-14. ������� ����
while

#!/bin/bash

var0=0
LIMIT=10

while [ "$var0" -lt "$LIMIT" ]
do
  echo -n "$var0 "        # -n ��������� ������� ������.
  var0=`expr $var0 + 1`   # ����������� var0=$(($var0+1)).
done

echo

exit 0

������ 10-15. ������ ������ �����
while

#!/bin/bash

echo

while [ "$var1" != "end" ]     # �������� ������ �� while test "$var1" != "end"
do
  echo "������� �������� ���������� #1 (end - �����) "
  read var1                    # ����������� 'read $var1' ����������� (������?).
  echo "���������� #1 = $var1" # ������� �����������, ������ ��� ������� ������ "#".
  # ���� ������� ����� 'end', �� ��� ���� ��������� �� �����.
  # ������, ��� �������� ���������� ����������� � ������ �������� (����� ������).
  echo
done  

exit 0

�������� while ����� ����� ���������
�������. �� ������ ��������� �� ��� ����������
����������� ����������� �����. � ���� ������ ���������
��������� ����� ������ ���� ��������� ����.

������ 10-16. ���� while � �����������
���������

#!/bin/bash

var1=unset
previous=$var1

while echo "���������� �������� = $previous"
      echo
      previous=$var1     # ��������� ���������� ��������
      [ "$var1" != end ]
      # � ��������� "while" ������������ 4 �������, �� ������ ��������� ��������� ������.
      # *���������* ������� - ������������, ������� �����������.
do
echo "������� �������� ���������� #1 (end - �����) "
  read var1
  echo "������� �������� = $var1"
done

# ���������� �������������� ����������� � �������� works.

exit 0

��� � � ������ � for, ���� while ����� ���� ������� �
C-�������� �������, � �������������� ������� �������
������ (��. ��� �� ������ 9-28).

������ 10-17. C-�������� ���������
���������� ����� while

#!/bin/bash
# wh-loopc.sh: ���� �������� �� 1 �� 10.

LIMIT=10
a=1

while [ "$a" -le $LIMIT ]
do
  echo -n "$a "
  let "a+=1"
done           # ���� ������ ����������.

echo; echo

# +=================================================================+

# � ������ ������� � ����� ����� C.

((a = 1))      # a=1
# ������� ������ ��������� ������� ������ �������� � ����������.

while (( a <= LIMIT ))   # � ������� ������� ������ "$" ����� ����������� ����������.
do
  echo -n "$a "
  ((a += 1))   # let "a+=1"
  # ������� ������ ��������� ����������� ���������� � ����� ����� C.
done

echo

# ������, ������������, ������� �� C, ����� ����������� ���� � Bash ��� ����.

exit 0

Note

����������� ���������� ����� stdin, ��� ����� while, ����� ������������� ��
���� � ������� ������� ��������������� < � ����� �����.

In bash script,

I have a string which contains several words separated by one or more than one spaces.
ie:

Name   Age Sex  ID         Address

If I want to find any of the word, for instance I want to find the index of word «Age», how can I do it?

Is there any command that will return the index number of the word I want directly?

Thanks.

Andrea's user avatar

Andrea

1,5064 gold badges17 silver badges19 bronze badges

asked Jun 9, 2012 at 0:47

GJ.'s user avatar

2

Bash performs word splitting in strings all by itself – in fact, more often than not, avoiding that is an issue, and the reason quoting is so important. It’s easy to leverage that in your case: just put your string into an array without quoting it – bash will use word splitting to separate the individual elements. Assuming your string is stored in the variable $str,

ar=($str) # no quotes!

will return an array of 5 elements. Your array index is your word index (counting up from 0, like in most scripting and programming languages), i.e. “Age” is accessed using

${ar[1]}  # 0 => Name, 1 => Age, 2 => Sex, 3 => ID, 4 => Address

or, if you need to find the element index by content, loop over the array, i.e.

function el_index {
    cnt=0; for el in "${ar[@]}"; do
        [[ $el == "$1" ]] && echo $cnt && break
        ((++cnt))
    done
}
el_index "Age" # => 1

answered Jun 9, 2012 at 18:02

kopischke's user avatar

kopischkekopischke

2,21619 silver badges27 bronze badges

1

$ export FOO="Name   Age Sex  ID         Address"

Replace *Age with Age — this will remove anything before «Age»:

$ echo ${FOO/*Age/Age}
Age Sex ID Address

Get anything before «Age»

$ echo ${FOO/Age*/}
Name

Get the length of that string (which is the index of «Age»):

$ BEGIN=${FOO/Age*/}
$ echo ${#BEGIN}
7

answered Jun 9, 2012 at 10:43

user1034081's user avatar

1

This is a 7-years-old question, but some may need the answer in pure bash.

STRING="Name   Age Sex  ID         Address"
INDEXOF_AGE=${#${STRING/Age*/}}
echo $INDEXOF_AGE

answered May 18, 2020 at 9:27

Henry Chen's user avatar

1

You can use bash’s native regex

# a function to print the index of a field and its name
printIx() { 
  for ((l=0,i=1;i<$1;i++)) ;do 
     ((l+=${#BASH_REMATCH[i]}))
  done
  printf '%3s %sn' $l "$2"
}

#   Using a zero based index
#   "0----+----1----+----2----+----3----+----4"
str="  Name   Age Sex  ID         Address   "

if [[ $str =~ ^( *)(Name)( +)(Age)( +)(Sex)( +()ID)( +)(Address) *$ ]] ;then
  F=(Name Age Sex ID Address)
  f=(   2   4   6  8      10)  # regex back-references
  for ((g=0;g<${#f[@]};g++)) ;do
     printIx  ${f[g]} "${F[g]}"
  done 
fi

Output

  2 Name
  9 Age
 13 Sex
 20 ID
 29 Address

answered Jun 9, 2012 at 8:19

Peter.O's user avatar

Peter.OPeter.O

3,0031 gold badge28 silver badges30 bronze badges

Note: Assuming here that by index you mean you want to know which word it is (starting from 0), not which character in the string the word starts on. Other answers address the latter.

Not that I’m aware of, but you can make one. Two tricks:

  1. Use the inborn abilities of the for construct to split up an unquoted input by whitespace.
  2. Handle the case where you can’t find the column you want. In this case, I chose to send the found index to stout and let the status code indicate whether the find was successful. There are other possibilities.

Code:

#!/bin/bash
find_index() {
    local str=$1
    local search=$2
    let local n=0
    local retval=1 # here, 1 is failure, 0 success
    for col in $str; do # $str unquoted -> whitespace tokenization!
    if [ $col = $search ]; then
        echo $n
        retval=0
        break
    else
        ((n++))
    fi
    done
    return $retval
}

test="Name   Age Sex  ID         Address"
idx=`find_index "$test" Age`
if [ $? -ne 0 ]; then
    echo "Not found!"
else
    echo "Found: $idx"
fi

answered Jun 9, 2012 at 5:28

Owen S.'s user avatar

Owen S.Owen S.

2881 silver badge4 bronze badges

Try the following javascript oneliner in a shell (use javascript shell) :

$ js <<< "x = 'Name   Age Sex  ID         Address'; print(x.indexOf('Age'));"
7

Or with a here-doc:

js <<EOF
x = 'Name   Age Sex  ID         Address';
print(x.indexOf('Age'));
EOF

answered Jun 9, 2012 at 18:20

Gilles Quénot's user avatar

Gilles QuénotGilles Quénot

3,7811 gold badge25 silver badges27 bronze badges

If coreutils are available you can do it in following way:

echo ${str/Age//}| cut -d/ -f1| wc -w

Per MariusMatutiae request I’m adding an explanation how this 3 steps operation works:

echo ${str/Age//} 1. replace string which is being searched for unique char (in my case /)

cut -d/ -f1 2. cut off whole part of string which is after unique char

wc -w 3. count and print words which are left this will give us an index number

For references please check:

http://www.tldp.org/LDP/abs/html/parameter-substitution.html (go to: «Variable expansion / Substring replacement»)
http://www.gnu.org/software/coreutils/manual/coreutils.html (go to: «The cut command» and «wc invocation»

Community's user avatar

answered Jul 9, 2014 at 10:25

PiotrO's user avatar

PiotrOPiotrO

1013 bronze badges

1

A mix of two previously given answers, using pure bash arrays and substring replacement.

The idea is to get a string of all words before the one you want, then count the number of words in that substring by making it into an array.

$ haystack="Name   Age Sex  ID         Address"
$ words_before=( ${haystack%Age*} )     # truncate string, make array
$ echo ${#words_before[*]}              # count words in array
1

Of course Age can be stored in another variable needle, then use ${haystack%$needle*}. Expect problems if the word you search for is a subset of another word, in which case kopischke’s answer is still working.

answered Jan 11, 2016 at 15:51

Cimbali's user avatar

If you don’t have to strictly use bash, but can use other programs commonly found on systems with bash then you could use something like this:

echo "Name   Age Sex ID  Addr" | python -c 'print(raw_input().index("Age"))+1'

Python starts its string indexing at zero, therefore I added +1 to the end of the command.

answered Jun 9, 2012 at 7:02

jftuga's user avatar

jftugajftuga

3,1571 gold badge19 silver badges23 bronze badges

1

the size of the remaining string after the erasure using wc -c so that you dont need to create an intermediary string variable

str="Name   Age Sex  ID         Address";
nIndex="`echo -n "${str/Age*/}" |wc -c`";
declare -p nIndex

declare — nIndex=»7″

Btw, this may be useful info: https://www.thegeekstuff.com/2010/07/bash-string-manipulation/

answered Apr 25, 2022 at 23:29

Aquarius Power's user avatar

Время на прочтение
8 мин

Количество просмотров 607K

Bash-скрипты: начало
Bash-скрипты, часть 2: циклы
Bash-скрипты, часть 3: параметры и ключи командной строки
Bash-скрипты, часть 4: ввод и вывод
Bash-скрипты, часть 5: сигналы, фоновые задачи, управление сценариями
Bash-скрипты, часть 6: функции и разработка библиотек
Bash-скрипты, часть 7: sed и обработка текстов
Bash-скрипты, часть 8: язык обработки данных awk
Bash-скрипты, часть 9: регулярные выражения
Bash-скрипты, часть 10: практические примеры
Bash-скрипты, часть 11: expect и автоматизация интерактивных утилит

В прошлый раз мы рассказали об основах программирования для bash. Даже то немногое, что уже разобрано, позволяет всем желающим приступить к автоматизации работы в Linux. В этом материале продолжим рассказ о bash-скриптах, поговорим об управляющих конструкциях, которые позволяют выполнять повторяющиеся действия. Речь идёт о циклах for и while, о методах работы с ними и о практических примерах их применения.

image

Внимание: в посте спрятана выгода!

Циклы for

Оболочка bash поддерживает циклы for, которые позволяют организовывать перебор последовательностей значений. Вот какова базовая структура таких циклов:

for var in list
do
команды
done

В каждой итерации цикла в переменную var будет записываться следующее значение из списка list. В первом проходе цикла, таким образом, будет задействовано первое значение из списка. Во втором — второе, и так далее — до тех пор, пока цикл не дойдёт до последнего элемента.

Перебор простых значений

Пожалуй, самый простой пример цикла for в bash-скриптах — это перебор списка простых значений:

#!/bin/bash
for var in first second third fourth fifth
do
echo The  $var item
done

Ниже показаны результаты работы этого скрипта. Хорошо видно, что в переменную $var последовательно попадают элементы из списка. Происходит так до тех пор, пока цикл не дойдёт до последнего из них.


Простой цикл for

Обратите внимание на то, что переменная $var сохраняет значение при выходе из цикла, её содержимое можно менять, в целом, работать с ней можно как с любой другой переменной.

Перебор сложных значений

В списке, использованном при инициализации цикла for, могут содержаться не только простые строки, состоящие из одного слова, но и целые фразы, в которые входят несколько слов и знаков препинания. Например, всё это может выглядеть так:

#!/bin/bash
for var in first "the second" "the third" "I’ll do it"
do
echo "This is: $var"
done

Вот что получится после того, как этот цикл пройдётся по списку. Как видите, результат вполне ожидаем.


Перебор сложных значений

TNW-CUS-FMP — промо-код на 10% скидку на наши услуги, доступен для активации в течение 7 дней»

Инициализация цикла списком, полученным из результатов работы команды

Ещё один способ инициализации цикла for заключается в передаче ему списка, который является результатом работы некоей команды. Тут используется подстановка команд для их исполнения и получения результатов их работы.

#!/bin/bash
file="myfile"
for var in $(cat $file)
do
echo " $var"
done

В этом примере задействована команда cat, которая читает содержимое файла. Полученный список значений передаётся в цикл и выводится на экран. Обратите внимание на то, что в файле, к которому мы обращаемся, содержится список слов, разделённых знаками перевода строки, пробелы при этом не используются.


Цикл, который перебирает содержимое файла

Тут надо учесть, что подобный подход, если ожидается построчная обработка данных, не сработает для файла более сложной структуры, в строках которого может содержаться по несколько слов, разделённых пробелами. Цикл будет обрабатывать отдельные слова, а не строки.

Что, если это совсем не то, что нужно?

Разделители полей

Причина вышеописанной особенности заключается в специальной переменной окружения, которая называется IFS (Internal Field Separator) и позволяет указывать разделители полей. По умолчанию оболочка bash считает разделителями полей следующие символы:

  • Пробел
  • Знак табуляции
  • Знак перевода строки

Если bash встречает в данных любой из этих символов, он считает, что перед ним — следующее самостоятельное значение списка.

Для того, чтобы решить проблему, можно временно изменить переменную среды IFS. Вот как это сделать в bash-скрипте, если исходить из предположения, что в качестве разделителя полей нужен только перевод строки:

IFS=$'n'

После добавления этой команды в bash-скрипт, он будет работать как надо, игнорируя пробелы и знаки табуляции, считая разделителями полей лишь символы перевода строки.

#!/bin/bash
file="/etc/passwd"
IFS=$'n'
for var in $(cat $file)
do
echo " $var"
done

Если этот скрипт запустить, он выведет именно то, что от него требуется, давая, в каждой итерации цикла, доступ к очередной строке, записанной в файл.


Построчный обход содержимого файла в цикле for

Разделителями могут быть и другие символы. Например, выше мы выводили на экран содержимое файла /etc/passwd. Данные о пользователях в строках разделены с помощью двоеточий. Если в цикле нужно обрабатывать подобные строки, IFS можно настроить так:

IFS=:

Обход файлов, содержащихся в директории

Один из самых распространённых вариантов использования циклов for в bash-скриптах заключается в обходе файлов, находящихся в некоей директории, и в обработке этих файлов.

Например, вот как можно вывести список файлов и папок:

#!/bin/bash
for file in /home/likegeeks/*
do
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
fi
done

Если вы разобрались с предыдущим материалом из этой серии статей, вам должно быть понятно устройство конструкции if-then, а так же то, как отличить файл от папки. Если вам сложно понять вышеприведённый код, перечитайте этот материал.

Вот что выведет скрипт.


Вывод содержимого папки

Обратите внимание на то, как мы инициализируем цикл, а именно — на подстановочный знак «*» в конце адреса папки. Этот символ можно воспринимать как шаблон, означающий: «все файлы с любыми именами». он позволяет организовать автоматическую подстановку имён файлов, которые соответствуют шаблону.

При проверке условия в операторе if, мы заключаем имя переменной в кавычки. Сделано это потому что имя файла или папки может содержать пробелы.

Циклы for в стиле C

Если вы знакомы с языком программирования C, синтаксис описания bash-циклов for может показаться вам странным, так как привыкли вы, очевидно, к такому описанию циклов:

for (i = 0; i < 10; i++)
{
printf("number is %dn", i);
}

В bash-скриптах можно использовать циклы for, описание которых выглядит очень похожим на циклы в стиле C, правда, без некоторых отличий тут не обошлось. Схема цикла при подобном подходе выглядит так:

for (( начальное значение переменной ; условие окончания цикла; изменение переменной ))

На bash это можно написать так:

for (( a = 1; a < 10; a++ ))

А вот рабочий пример:

#!/bin/bash
for (( i=1; i <= 10; i++ ))
do
echo "number is $i"
done

Этот код выведет список чисел от 1 до 10.


Работа цикла в стиле C

Цикл while

Конструкция for — не единственный способ организации циклов в bash-скриптах. Здесь можно пользоваться и циклами while. В таком цикле можно задать команду проверки некоего условия и выполнять тело цикла до тех пор, пока проверяемое условие возвращает ноль, или сигнал успешного завершения некоей операции. Когда условие цикла вернёт ненулевое значение, что означает ошибку, цикл остановится.

Вот схема организации циклов while
while команда проверки условия
do
другие команды
done

Взглянем на пример скрипта с таким циклом:

#!/bin/bash
var1=5
while [ $var1 -gt 0 ]
do
echo $var1
var1=$[ $var1 - 1 ]
done

На входе в цикл проверяется, больше ли нуля переменная $var1. Если это так, выполняется тело цикла, в котором из значения переменной вычитается единица. Так происходит в каждой итерации, при этом мы выводим в консоль значение переменной до его модификации. Как только $var1 примет значение 0, цикл прекращается.


Результат работы цикла while

Если не модифицировать переменную $var1, это приведёт к попаданию скрипта в бесконечный цикл.

Вложенные циклы

В теле цикла можно использовать любые команды, в том числе — запускать другие циклы. Такие конструкции называют вложенными циклами:

#!/bin/bash
for (( a = 1; a <= 3; a++ ))
do
echo "Start $a:"
for (( b = 1; b <= 3; b++ ))
do
echo " Inner loop: $b"
done
done

Ниже показано то, что выведет этот скрипт. Как видно, сначала выполняется первая итерация внешнего цикла, потом — три итерации внутреннего, после его завершения снова в дело вступает внешний цикл, потом опять — внутренний.


Вложенные циклы

Обработка содержимого файла

Чаще всего вложенные циклы используют для обработки файлов. Так, внешний цикл занимается перебором строк файла, а внутренний уже работает с каждой строкой. Вот, например, как выглядит обработка файла /etc/passwd:

#!/bin/bash
IFS=$'n'
for entry in $(cat /etc/passwd)
do
echo "Values in $entry –"
IFS=:
for value in $entry
do
echo " $value"
done
done

В этом скрипте два цикла. Первый проходится по строкам, используя в качестве разделителя знак перевода строки. Внутренний занят разбором строк, поля которых разделены двоеточиями.


Обработка данных файла

Такой подход можно использовать при обработке файлов формата CSV, или любых подобных файлов, записывая, по мере надобности, в переменную окружения IFS символ-разделитель.

Управление циклами

Возможно, после входа в цикл, нужно будет остановить его при достижении переменной цикла определённого значения, которое не соответствует изначально заданному условию окончания цикла. Надо ли будет в такой ситуации дожидаться нормального завершения цикла? Нет конечно, и в подобных случаях пригодятся следующие две команды:

  • break
  • continue

Команда break

Эта команда позволяет прервать выполнение цикла. Её можно использовать и для циклов for, и для циклов while:

#!/bin/bash
for var1 in 1 2 3 4 5 6 7 8 9 10
do
if [ $var1 -eq 5 ]
then
break
fi
echo "Number: $var1"
done

Такой цикл, в обычных условиях, пройдётся по всему списку значений из списка. Однако, в нашем случае, его выполнение будет прервано, когда переменная $var1 будет равна 5.


Досрочный выход из цикла for

Вот — то же самое, но уже для цикла while:

#!/bin/bash
var1=1
while [ $var1 -lt 10 ]
do
if [ $var1 -eq 5 ]
then
break
fi
echo "Iteration: $var1"
var1=$(( $var1 + 1 ))
done

Команда break, исполненная, когда значение $var1 станет равно 5, прерывает цикл. В консоль выведется то же самое, что и в предыдущем примере.

Команда continue

Когда в теле цикла встречается эта команда, текущая итерация завершается досрочно и начинается следующая, при этом выхода из цикла не происходит. Посмотрим на команду continue в цикле for:

#!/bin/bash
for (( var1 = 1; var1 < 15; var1++ ))
do
if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]
then
continue
fi
echo "Iteration number: $var1"
done

Когда условие внутри цикла выполняется, то есть, когда $var1 больше 5 и меньше 10, оболочка исполняет команду continue. Это приводит к пропуску оставшихся в теле цикла команд и переходу к следующей итерации.


Команда continue в цикле for

Обработка вывода, выполняемого в цикле

Данные, выводимые в цикле, можно обработать, либо перенаправив вывод, либо передав их в конвейер. Делается это с помощью добавления команд обработки вывода после инструкции done.

Например, вместо того, чтобы показывать на экране то, что выводится в цикле, можно записать всё это в файл или передать ещё куда-нибудь:

#!/bin/bash
for (( a = 1; a < 10; a++ ))
do
echo "Number is $a"
done > myfile.txt
echo "finished."

Оболочка создаст файл myfile.txt и перенаправит в этот файл вывод конструкции for. Откроем файл и удостоверимся в том, что он содержит именно то, что ожидается.


Перенаправление вывода цикла в файл

Пример: поиск исполняемых файлов

Давайте воспользуемся тем, что мы уже разобрали, и напишем что-нибудь полезное. Например, если надо выяснить, какие именно исполняемые файлы доступны в системе, можно просканировать все папки, записанные в переменную окружения PATH. Весь арсенал средств, который для этого нужен, у нас уже есть, надо лишь собрать всё это воедино:

#!/bin/bash
IFS=:
for folder in $PATH
do
echo "$folder:"
for file in $folder/*
do
if [ -x $file ]
then
echo " $file"
fi
done
done

Такой вот скрипт, небольшой и несложный, позволил получить список исполняемых файлов, хранящихся в папках из PATH.


Поиск исполняемых файлов в папках из переменной PATH

Итоги

Сегодня мы поговорили о циклах for и while в bash-скриптах, о том, как их запускать, как ими управлять. Теперь вы умеете обрабатывать в циклах строки с разными разделителями, знаете, как перенаправлять данные, выведенные в циклах, в файлы, как просматривать и анализировать содержимое директорий.

Если предположить, что вы — разработчик bash-скриптов, который знает о них только то, что изложено в первой части этого цикла статей, и в этой, второй, то вы уже вполне можете написать кое-что полезное. Впереди — третья часть, разобравшись с которой, вы узнаете, как передавать bash-скриптам параметры и ключи командной строки, и что с этим всем делать.

Уважаемые читатели! В комментариях к предыдущему материалу вы рассказали нам много интересного. Уверены, всё это окажет неоценимую помощь тем, кто хочет научиться программировать для bash. Но тема эта огромна, поэтому снова просим знатоков поделиться опытом, а новичков — впечатлениями.

Понравилась статья? Поделить с друзьями:
  • For word in list python
  • For word games перевод
  • For word document example
  • For while excel миф
  • For what this word i love you