Reputation: 7721
I don't know anything about bash scripting. I have written this script to update all the repositories that I have in my directory. But instead of going through all the repos like it should, it is going through the first repository only and looping on the first one. What is the error in the code that is causing it ?
Code :
rm -rf log.txt
touch log.txt
ls > log.txt
for (( i = 0; i < $(cat log | wc -l); i++ )); do
dir_name=$(sed "${i+1}q;d" log.txt)
cd ${dir_name}
git pull origin master
cd ..
done
Upvotes: 0
Views: 207
Reputation: 587
Your script is quite convoluted, instead you can try:
for dir_name in "$(find -maxdepth 1 -type d)"; do
cd "$dir_name" && git pull origin master && cd - || break
done
EDIT (handling whitespaces correctly):
find -maxdepth 1 -type d -print0 | while read -d $'\0' dir_name; do
( cd "$dir_name" && git pull origin master ) || break
done
In case not all directories are git repositories you can use this:
for dir_name in "$(find -maxdepth 1 -type d)"; do
find -maxdepth 1 -type d -print0 | while read -d $'\0' dir_name; do
(
cd "$dir_name" || exit $?
[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" == "true" ] ||
{ echo "$dir_name: not a git repository"; exit 0; }
git pull origin master || exit $?
) || break
done
Upvotes: 2
Reputation: 76316
rm -rf log.txt touch log.txt ls > log.txt
There is rarely good reason to create temporary files in bash. Simple script like this should be able to handle everything with piping—or even better, just filename generation.
for (( i = 0; i < $(cat log | wc -l); i++ )); do
This is a really, really, rare and obscure way to do loop in bash. Normal loop is
for variable in words…; do …; done
In this case, you don't need ls
at all, because shell can generate list of files itself via filename generation. So just:
for dir_name in *; do
If you use a variable or shell expansion, it will be split, but on whitespace. You can pipe into a while loop for other splitting (see below).
dir_name=$(sed "${i+1}q;d" log.txt)
We've replaced this line by directly iterating over the names. But I should note, that this line is the reason why your original code didn't work (besides the missing extension). The problem is that
${variable+value}
is not an arithmetic expression. Instead, it returns value
if variable
is defined and nothing otherwise. So ${i+1}
is always 1
.
Arithmetic expression is instead written with $(())
.
cd ${dir_name}
Normally the {}
are only used if you need to delimit the variable name from the following text. However, what you should do is quote the expansion, because the directory name might include spaces and then the shell would split the name and the command would fail—or worse, do something dangerous! So
cd "$dir_name"
git pull origin master cd ..
If the cd "$dir_name"
fails for some reason, the cd ..
will take you somewhere you didn't want to be:
$dir_name
is a file, the first cd
won't do anything and the second will take you one level up and$dir_name
is a symlink, the first cd
will resolve it and the second will take you to parent of the actual directory.I would normally use a subshell here:
(
cd "$dir_name"
git pull origin master
)
The ()
create a subshell. All changes made to variables and current directory are local to that block.
Alternately, you can use pushd
and popd
:
pushd "$dir_name"
git pull origin master
popd
but that requires a bash, while the rest of what I am writing here is POSIX shell. Writing shells without the bash extensions is good, because many systems have a POSIX shell under /bin/sh
, that is faster than bash
, but does not have most of those extensions.
done
I mentioned a while loop. If you did want to generate the values by a program that outputs one per line, instead of a for
loop you'd pipe into a while
loop. So if you wanted to use ls
here (there is no good reason to), you'd replace the for
line with:
ls | while read -r dir_name; do
The read
command reads a line from input and sets the specified variable to it (and fails when there is no line to read). The while
then runs until the command fails. The -r
flag tells it not to interpret \
escape sequences. I don't remember off the top of my head whether the escaping mode of ls
would match what read
expects—if yes, that would be more reliable way.
Upvotes: 2
Reputation: 30888
Here is another possible approach.
ls -F | grep -e "/$" | while read repo
do
git --git-dir=${repo}.git pull origin master
done
Upvotes: -1
Reputation: 169
cat 'log.txt' instead of 'log'
rm -rf log.txt
touch log.txt
ls > log.txt
for (( i = 1; i <= $(cat log.txt | wc -l); i++ )); do
dir_name=$(sed "${i}q;d" log.txt)
cd ${dir_name}
git pull origin master
cd ..
done
Upvotes: 0