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 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 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♦Kusalananda
312k35 gold badges614 silver badges909 bronze badges
0
list='a b c d'
for element in $list;do
echo "$element"
done
Bart
2,1011 gold badge9 silver badges26 bronze badges
answered Jul 23, 2019 at 8:06
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 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
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
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).
answered Jun 27, 2018 at 13:47
Rich JonesRich Jones
1511 silver badge2 bronze badges
��� ���� �� �������� �������������� ������. � ���
����������� ���������� �� ������� � ����� C.
for arg in [list]
do
��������(�)…
done
�� ������ ������� �����, ����������-�������� |
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
������ �� ��������� [������] |
������ 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
����������� ���������� ����� 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
1,5064 gold badges17 silver badges19 bronze badges
asked Jun 9, 2012 at 0:47
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
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
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
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.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:
- Use the inborn abilities of the for construct to split up an unquoted input by whitespace.
- 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.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é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»
answered Jul 9, 2014 at 10:25
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
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
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
Время на прочтение
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
, о методах работы с ними и о практических примерах их применения.
Внимание: в посте спрятана выгода!
Циклы 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. Но тема эта огромна, поэтому снова просим знатоков поделиться опытом, а новичков — впечатлениями.