When copying large files in Bash shell scripts, it would be nice to have a progress bar displayed. Unfortunately the cp command does not have a progress bar option.
The following script shows how we can implement a copy function in Bash that displays a progress bar with ETA (Estimated Time to Arrival). It simulates the cp command by piping the stdin and stdout of cat, dd and cat in series. The dd command is encapsulated in a while loop so that it can perform the copying in chunks of 1MB and also to print the progress bar. Note that the progress bar is printed on stderr as stdin and stdout are used to transfer the bytes.
copy() { # src dst [width]
srcsize=$(stat -c %s $1) || return $?
dstsize=0
width=${3:-25}
mega=$(( 1024 * 1024 ))
start=$(date +%s)
cat $1 | (
while [[ dstsize -lt srcsize ]]
do
dd bs=512 count=2048 2>/dev/null || return $?
(( dstsize += $mega ))
[[ dstsize -gt srcsize ]] && dstsize=$srcsize
# print truncated filename
name=$(basename $1 | cut -b -20)
printf "\r%-20s " $name 1>&2
# print percentage
percent=$(( 100 * $dstsize / $srcsize ))
printf "%3d%% [" $percent 1>&2
# print progress bar
bar=$(( $width * $dstsize / $srcsize ))
for i in $(seq 1 $bar); do printf "=" 1>&2; done
for i in $(seq $bar $(( $width-1 ))); do printf " " 1>&2; done
# print size of file copied
if [[ $dstsize -le 1024 ]]; then
printf -v size "%d" $dstsize;
elif [[ $dstsize -le $mega ]]; then
printf -v size "%d kB" $(( $dstsize / 1024 ));
else
printf -v size "%d MB" $(( $dstsize / $mega ));
fi
printf "] %7s" "$size" 1>&2
# print estimated time of arrival
elapsed=$(( $(date +%s) - $start ))
remain=$(( $srcsize - $dstsize ))
eta=$(( ($elapsed * $remain) / $dstsize + 1))
if [[ $remain == 0 ]]; then eta=0; fi
etamin=$(( $eta / 60 ))
etasec=$(( $eta % 60 ))
if [[ $eta > 0 ]]; then etastr="ETA"; else etastr=" "; fi
printf " %02d:%02d $etastr" $etamin $etasec 1>&2
done
echo 1>&2
) | cat >$2
}
The inspiration for this code came from studying the progress bar implemented at http://www.theiling.de/projects/bar.html. The major differences of the above code from bar are in the progress bar format and the use of the extra cat command at the beginning. The above code is also less generic but that’s fine with me since it is simpler and shorter.
To use the above code, simply cut and paste it into the top of your shell script. Then wherever you have cp srcfile dstfile replace it with copy srcfile dstfile to see the progress bar. Note that the copy function is not a drop-in replacement for cp since it handles neither options nor wildcards.

17 January, 2008 at 5:19 am
It would be helpful if you can send a simple usage example or explanation.
Thanks,
Abdul
25 January, 2008 at 9:32 pm
Thanks, a good function.
To use this, paste it into your ~/.bashrc profile file then open a new terminal and do copy file1 file2.
29 January, 2008 at 9:15 am
This script is very nice.
The possibility to copy directories would be great and adding it would make this an extremely usefull script.
Thanks for sharing it!
19 July, 2008 at 2:20 am
v-cool script, it worked very nice.
thanks
21 April, 2009 at 5:58 am
What about using rsync –progress ?
22 April, 2009 at 5:31 am
It is a good option as well if you are sure rsync is installed on the system.
28 September, 2009 at 3:35 pm
Thank you, that was the code I was looking for!
If your are coping more than a couple of Mb on a fast media (HD to HD) above progress bar slows copying down. Use a bigger blocksize (32K) and copy more data per update. The code below updates the screen only 100 times.
ps: you can use /dev/stdout/ :
copy bigfile /dev/stdout 50 | wc
copy() { # src dst [width]
srcsize=$(stat -c %s $1) || return $?
dstsize=0
width=${3:-25}
mega=$(( 1024 * 1024 ))
count=$(( $srcsize / 32768 / 100 + 1 ))
step=$(( $count * 32768 ))
start=$(date +%s)
cat $1 | (
while [[ dstsize -lt srcsize ]]
do
dd bs=32768 count=$count 2>/dev/null || return $?
(( dstsize += $step ))
[[ dstsize -gt srcsize ]] && dstsize=$srcsize
# print truncated filename
# print truncated filename
name=$(basename $1 | cut -b -20)
printf “\r%-20s ” $name 1>&2
# print percentage
percent=$(( 100 * $dstsize / $srcsize ))
printf “%3d%% [" $percent 1>&2
# print progress bar
bar=$(( $width * $dstsize / $srcsize ))
for i in $(seq 1 $bar); do printf "=" 1>&2; done
for i in $(seq $bar $(( $width-1 ))); do printf " " 1>&2; done
# print size of file copied
if [[ $dstsize -le 1024 ]]; then
printf -v size “%d” $dstsize;
elif [[ $dstsize -le $mega ]]; then
printf -v size “%d kB” $(( $dstsize / 1024 ));
else
printf -v size “%d MB” $(( $dstsize / $mega ));
fi
printf “] %7s” “$size” 1>&2
# print estimated time of arrival
elapsed=$(( $(date +%s) – $start ))
remain=$(( $srcsize – $dstsize ))
eta=$(( ($elapsed * $remain) / $dstsize + 1))
if [[ $remain == 0 ]]; then eta=0; fi
etamin=$(( $eta / 60 ))
etasec=$(( $eta % 60 ))
if [[ $eta > 0 ]]; then etastr=”ETA”; else etastr=” “; fi
printf ” %02d:%02d $etastr” $etamin $etasec 1>&2
done
echo 1>&2
) | cat >$2
}
20 October, 2010 at 2:05 am
Very nice indeed! It would be nicer it it supported the standard cp switches like -a -b -d -f -r etc.
8 February, 2011 at 8:41 am
Very good site, where did you come up with the knowledge in this write-up? I’m pleased I found it though, ill be checking back soon to see what other articles you have.
28 June, 2011 at 12:02 pm
super cool, is very helpful thanks a lot!!
10 May, 2012 at 10:22 pm
i recommend pv (pipe viewer): http://www.ivarch.com/programs/pv.shtml
standard posix tool.