tar notes

From Noah.org
Jump to: navigation, search


See also cpio notes, which comes up when working with the Linux kernel.

safe tar extraction of explosive tarballs

A good tarball archive will have a single base path directory. The best tarballs will have that directory path name be exactly the same as the name of the tarball, excluding the tarball extension.

Sometimes you will open a tarball into the current working directory only too find too late that you have blown a thousand files all over the place. The following is an example of how to prevent that.

is_explosive() {
    TARBALL_NAME=$1
    tar ztf "${TARBALL_NAME}" | grep -qv "^$(tar ztf "${TARBALL_NAME}" | (read line; echo ${line}))"
    return $?
}

safe_tar_x() {
    TARBALL_NAME=$1
    if is_explosive ${TARBALL_NAME}; then
        SUBDIR=${TARBALL_NAME%.tar.gz}
        SUBDIR=${SUBDIR##*/}
        mkdir "${SUBDIR}"
        echo "WARNING: This tarball is explosive. Opening in subdirectory, ${SUBDIR}, for safety." >&2
    else
        SUBDIR="."
    fi
    # Tar quirks: "--directory" must be last, and using more than
    #     one option group requires that all groups start with a dash.
    tar -zxf "${TARBALL_NAME}" --directory "${SUBDIR}"
    return $?
}

permission and ownership of files inside a tar

If you are making a backup of a directory tree that includes different users you may want to carefully preserve the ownership of all files. If you extract this tarball on a different system you may end up with username and UID collisions. It's best to eliminate username confusion and store only the numeric UID and GID. This situation happens in embedded development when you archive a root filesystem. Later you may decide to extract the archive and make updates and additions that you plan to use to create a new version of the rootfs. If you carelessly extract the original tarball on a system that has different UIDs mapped to the same usernames used in the tarball then you can end up messing up file ownership. Note that you must run both the 'create' and 'extract' operations as root or you must use a tool like fakeroot.

tar --numeric-owner -cz -f install_tree.tar.gz ${STAGING_DIR}

If you insist on creating a tarball with an absolute root path instead of a relative path then you will need to use the --transform option to strip off the leading part of the filename that you don't want. For example, say you have a boot drive from another system that you want to backup. By default `tar` will include ./ in front of every filename. You can strip these off using the following command:

tar --verbose --numeric-owner --directory /media/usb1 --transform "s,^\./,/," -cz -f rootfs.tar.gz .

Note that when you later try to extract this type of tarball that `tar` will not observe the absolute pathnames even though you went through the trouble to create the tarball this way. Instead, `tar` will strip leading / characters from all filenames. You must use the -P option to force `tar` to use absolute names.

If you are making tarballs for installing on other machines you may want the files inside the archive to be owned by the same user. The best thing is to force the UID and GID of all files to root. When a normal user extracts these files the ownership is automatically converted to their UID:GID. Usually you do not build as root. You can force ownership and group with three arguments. This will record the UID and GID in the tar file as 0 (root).

tar --numeric-owner --owner=0 --group=0 -cz -f install_tree.tar.gz ${STAGING_DIR}

This is a fancier version that sets input and output directories; trims the trailing slash off of STAGING_DIR; and sets the gzip compression level to the max.

Eventually all of this trouble to keep ownership straight will become painful. Then you will want to use fakeroot.

GZIP="--best" tar --numeric-owner --owner=0 --group=0 --directory=${STAGING_DIR%/*} --exclude=.svn -cz -f ${OUTPUT_DIR##*/}/install_tree.tar.gz ${STAGING_DIR##*/}

show progress bar when decompressing tar

This displays the percentage of the tarball that is currently being uncompressed and written. I use this when writing out 2GB root filesystem images. You really need a progress bar for these things. What I do is use `gzip --list` and some `sed` to get the total uncompressed size of the tarball. From that I calculate the blocking-factor needed to divide the file into 100 parts.Finally, I print a checkpoint message for each block. For a 2GB file this gives about 10MB a block. If that is too big then you can divide the BLOCKING_FACTOR by 10 or 100, but then it's harder to print pretty output in terms of a percentage.

tar --blocking-factor=$(($(gzip --list tarball.tgz | sed -n -e "s/.*[[:space:]]\+[0-9]\+[[:space:]]\+\([0-9]\+\)[[:space:]].*$/\1/p") / 51200 + 1)) --checkpoint=1 --checkpoint-action='ttyout=Wrote %u%  \r' -zxf tarball.tgz

This is a little easier to read as a Bash shell function:

untar_progress () 
{ 
    TARBALL=$1;
    BLOCKING_FACTOR=$(($(gzip --list ${TARBALL} | sed -n -e "s/.*[[:space:]]\+[0-9]\+[[:space:]]\+\([0-9]\+\)[[:space:]].*$/\1/p") / 51200 + 1));
    tar --blocking-factor=${BLOCKING_FACTOR} --checkpoint=1 --checkpoint-action='ttyout=Wrote %u%  \r' -zxf ${TARBALL}
}

If you prefer the have each checkpoint message on a separate line then use this syntax:

tar --blocking-factor=$(($(gzip --list tarball.tgz | sed -n -e "s/.*[[:space:]]\+[0-9]\+[[:space:]]\+\([0-9]\+\)[[:space:]].*$/\1/p") / 51200 + 1)) --checkpoint=1 --checkpoint-action='echo=Wrote %u%' -zxf tarball.tgz

producing a progress bar for `cp`

Other possibilities include monitoring /proc/<pid>/fdinfo/ or

This is an interesting `strace` hack by Chris Lamb. Very cool, but using `strace` must be slow.

#!/bin/sh
cp_p()
{
   strace -q -ewrite cp -- "${1}" "${2}" 2>&1 \
      | awk '{
        count += $NF
            if (count % 10 == 0) {
               percent = count / total_size * 100
               printf "%3d%% [", percent
               for (i=0;i<=percent;i++)
                  printf "="
               printf ">"
               for (i=percent;i<100;i++)
                  printf " "
               printf "]\r"
            }
         }
         END { print "" }' total_size=$(stat -c '%s' "${1}") count=0
}

Diomidis Spinellis came up with a solution using `lsof`.

lsof -o0 -o -p $PID |
awk '
            BEGIN { CONVFMT = "%.2f" }
            $4 ~ /^[0-9]+r$/ && $7 ~ /^0t/ {
                    offset = substr($7, 3)
                    fname = $9
                    "stat -f %z '\''" fname "'\''" | getline
                    len = $0
                    print fname, offset / len * 100 "%"
            }
    '

Diomidis Spinellis also wrote a nice wrapper script that adds progress reporting when it receives a USR1 signal.

#!/bin/bash
#
# (c) Copyright (c) 2006-2008, Diomidis Spinellis. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# Monitor the progress of the specified command
#
# For each file the specified command is reading, display the percentage
# that has been read.  This command is modelled after a similar facility
# available on Permin-Elmer/Concurrent OS32
#
# Requires:
# - trap notification in wait (bash)
# - /proc[0-9]*/fdinfo (Linux 2.6.22 and later)
# - stat(1) (or substitute ls(1) with appropriate output parsing)
#
# Tested under SUSE Linux 10.3 with kernel 2.6.22.19
#
# $Id: monitor-linux.sh,v 1.2 2008/10/27 12:19:25 dds Exp $
#

CMD=$1

trap : USR1 INT TERM

display()
{
	cd /proc/$PID/fdinfo
	for i in `grep -l '^pos:' *`
	do
		readlink -n ../fd/$i
		awk '/^pos:/{print " " $2}' $i
	done |
	# Obtain the offset and print it as a percentage
	awk '
		BEGIN { CONVFMT = "%.2f" }
		{
			fname = $1
			offset = $2
			"stat -c %s '\''" fname "'\''" | getline
			len = $0
			if (len > 0)
				print fname, offset / len * 100 "%"
		}
	' 1>&2
}

# Execute the specified command in the background so as to catch signals
# asynchronously
$@ &
PID=$!

# React to signals and termination
while :
do
	wait $PID
	STATUS=$?
	if [ $STATUS -eq 138 -a -d /proc/$PID ]
	then
		display
	else
		kill $PID 2>/dev/null
		exit $STATUS
	fi
done

And then there is the portable shell `bar` script, by Henrik Theiling. Yikes! ... By the way, he also makes some super-cool teeny tiny fonts.

#! /bin/sh

# bar
# 'cat' with ASCII progress bar
# (c) Henrik Theiling
BAR_VERSION=1.4

# Synopsis:
#   'bar' works just like 'cat', but shows a progress bar in ASCII art on stderr.
#   The script's main function is meant to be usable in any Bourne shell to be
#   suitable for install scripts without the need for any additional tool.
#
# Shell Script Usage: bar [options] [files]
# Options:
#     -h        displays help
#     ...
#
# Examples:
#   Normal pipe:
#
#     : bar mypack.tar.bz2 | tar xjpf -
#
#   Individual pipe for each file:
#
#     : bar -c 'tar xjpf -' mypack1.tar.bz2 mypack2.tar.bz2
#
#   Individual pipe, using ${bar_file} variable:
#
#     : bar -c 'echo ${bar_file}: ; gzip -dc | tar tvf -' \
#     :     -e .tar.gz \
#     :     file1 file2 file3 file4 file5 \
#     :         > package-list.txt

#####################################################
# Programs and shell commands:
#
# Required (otherwise this fails):
#
#    if, then, else, fi, expr, test, cat, eval, exec
#    shell functions
#
#    test:
#        a = b
#        a -lt b
#        a -gt b
#        a -le b
#        -f a
#        -n a
#        -z a
#
#    expr:
#        a + b
#        a - b
#        a '*' b
#        a / b
#        a : b
#
# Optional (otherwise this does not show the bar):
#
#    grep, dd, echo, ls, sed, cut
#
#    ls:
#        must output the file size at fifth position.
#
# The command line interface also uses:
#
#    awk
#

####>-SCHNIPP-<########################################################
bar_cat()
{
   # Use this shell function in your own install scripts.

   #####################################################
   # Options:

   # Width of the bar (in ten characters).  The default is 76 characters.
   test -z "${BAR_WIDTH}" && test -n "${COLUMNS}" && BAR_WIDTH=${COLUMNS}

   # Check syntax:
   ( expr "${BAR_WIDTH}" + 0 >/dev/null 2>&1 ) || BAR_WIDTH=0
   BAR_WIDTH=`expr ${BAR_WIDTH} + 0` || BAR_WIDTH=0
   test "x${BAR_WIDTH}" = x0 && BAR_WIDTH=76

   # Maximal block size to use for dd.
   test -n "${BAR_BS}" || BAR_BS=1048567

   # BEGIN PERC
   # Whether to show a percentage.
   test -n "${BAR_PERC}" || BAR_PERC=1
   # END PERC

   # BEGIN ETA
   # Whether to show estimated time of arrival (ETA).
   test -n "${BAR_ETA}" || BAR_ETA=1
   # END ETA

   # Width of the trace display:
   # BEGIN TRACE
   test -n "${BAR_TRACE_WIDTH}" || BAR_TRACE_WIDTH=10
   # END TRACE

   # The command to execute for every given file.  Each file
   # is piped into this command individually.  By default, the
   # files are simply dumped to stdout.
   test -n "${BAR_CMD}" || BAR_CMD=cat

   # The characters to be used in the bar
   test -n "${BAR_L}"  || BAR_L='['
   test -n "${BAR_R}"  || BAR_R=']'
   test -n "${BAR_C0}" || BAR_C0='.'
   test -n "${BAR_C1}" || BAR_C1='='

   # Additional extension to add to each file:
   #BAR_EXT=${BAR_EXT-}

   # Whether to clear bar after termination.  Otherwise keep the full bar.
   #BAR_CLEAR=${BAR_CLEAR-0}

   # Unless switched off by user, use the bar by default:
   test -n "${BAR_OK}" || BAR_OK=1

   #####################################################
   BAR_WIDTH=`expr ${BAR_WIDTH} - 3`

   bar_trace=''
   # BEGIN TRACE
   if test "x${BAR_TRACE}" = x1
   then
       BAR_WIDTH=`expr ${BAR_WIDTH} - ${BAR_TRACE_WIDTH}`
       bar_lauf=${BAR_TRACE_WIDTH}
       bar_t_space=''
       bar_t_dot=''
       while test "${bar_lauf}" -gt 1
       do
           bar_t_space="${bar_t_space} "
           bar_t_dot="${bar_t_dot}."
           bar_lauf=`expr ${bar_lauf} - 1`
       done
       bar_trace="${bar_t_space} "
   fi
   # END TRACE

   bar_eta=''
   BAR_GET_TIME='echo'
   # BEGIN ETA
   ( expr 1 + ${SECONDS} >/dev/null 2>&1 ) || BAR_ETA=0
   if test "x${BAR_ETA}" = x1
   then
       BAR_GET_TIME='( echo ${SECONDS} )'
       BAR_WIDTH=`expr ${BAR_WIDTH} - 6`
       bar_eta='--:-- '
   fi
   # END ETA

   bar_perc=''
   # BEGIN PERC
   if test "x${BAR_PERC}" = x1
   then
       BAR_WIDTH=`expr ${BAR_WIDTH} - 5`
       bar_perc='  0% '
   fi
   # END PERC

   BAR_GET_SIZE='( ls -l "${BAR_DIR}${bar_file}${BAR_EXT}" | sed "s@  *@ @g" | cut -d " " -f 5 ) 2>/dev/null'
       # portable?

   # check features:
   ( ( echo a                   ) >/dev/null 2>&1 ) || BAR_OK=0
   ( ( echo a | dd bs=2 count=2 ) >/dev/null 2>&1 ) || BAR_OK=0
   ( ( echo a | grep a          ) >/dev/null 2>&1 ) || BAR_OK=0
   ( ( echo a | sed 's@  *@ @g' ) >/dev/null 2>&1 ) || BAR_OK=0
   ( ( echo a | cut -d ' ' -f 1 ) >/dev/null 2>&1 ) || BAR_OK=0

   # check ranges:
   test "${BAR_WIDTH}" -ge 4 || BAR_OK=0

   BAR_ECHO='echo'
   BAR_E_C1=''
   BAR_E_C2=''
   BAR_E_NL='echo'

   # Does echo accept -n without signalling an error?
   if echo -n abc >/dev/null 2>&1
   then
       BAR_E_C1='-n'
   fi

   # Check how to print a line without newline:
   if ( ( ${BAR_ECHO} "${BAR_E_C1}" abc ; echo 1,2,3 ) | grep n ) >/dev/null 2>&1
   then
       # Try echo \c:
       if ( ( ${BAR_ECHO} 'xyz\c' ; echo 1,2,3 ) | grep c ) >/dev/null 2>&1
       then
           # Try printf:
           if ( ( printf 'ab%s' c ; echo 1,2,3 ) | grep abc ) >/dev/null 2>&1
           then
              BAR_ECHO='printf'
              BAR_E_C1='%s'
           else
              BAR_ECHO=':'
              BAR_E_C1=''
              BAR_E_NL=':'
              BAR_OK=0
           fi
       else
          BAR_E_C1=''
          BAR_E_C2='\c'
       fi
   fi

   # prepare initial bar:
   bar_shown=0
   if test "${BAR_OK}" = 1
   then
       bar_lauf=0
       bar_graph=''
       while test `expr ${bar_lauf} + 5` -le "${BAR_WIDTH}"
       do
           bar_graph="${bar_graph}${BAR_C0}${BAR_C0}${BAR_C0}${BAR_C0}${BAR_C0}"
           bar_lauf=`expr ${bar_lauf} + 5`
       done
       while test "${bar_lauf}" -lt "${BAR_WIDTH}"
       do
           bar_graph="${bar_graph}${BAR_C0}"
           bar_lauf=`expr ${bar_lauf} + 1`
       done
       ${BAR_ECHO} "${BAR_E_C1}" "
${bar_trace}${bar_eta}${bar_perc}${BAR_L}${bar_graph}${BAR_R}
${BAR_E_C2}" 1>&2
       bar_shown=1
   fi

   # for shifting large numbers so that expr can handle them:
   # Assume we can compute up to 2147483647, thus 9 arbitrary digits.
   # We must be able to do + of two numbers of 9 digits length.  Ok.
   # BEGIN LARGE
   ( ( test 1999999998 = `expr 999999999 + 999999999` ) >/dev/null 2>&1 ) || BAR_OK=0
   bar_large_num="........."
   bar_div=""
   # END LARGE
   bar_numsuff=""

   # find size:
   bar_size=0
   if test -n "${BAR_SIZE}"
   then
       bar_size=${BAR_SIZE}
       # BEGIN LARGE
       while expr "${bar_size}" : "${bar_large_num}" >/dev/null 2>&1
       do
           bar_div="${bar_div}."
           bar_numsuff="${bar_numsuff}0"
           bar_size=`expr "${bar_size}" : '\(.*\).$'`
       done
       # END LARGE
       BAR_GET_SIZE="echo '${BAR_SIZE}'"
   else
       for bar_file
       do
           bar_size1=0
           if test -f "${BAR_DIR}${bar_file}${BAR_EXT}"
           then
               bar_size1=`eval "${BAR_GET_SIZE}"`

               # BEGIN LARGE
               # divide and upround by pattern matching:
               if test -n "${bar_div}"
               then
                   bar_size1=`expr "${bar_size1}" : '\(.*\)'${bar_div}'$'` || bar_size1=0
               fi

               # adjust if still too large:
               while expr "${bar_size1}" : "${bar_large_num}" >/dev/null 2>&1
               do
                   bar_div="${bar_div}."
                   bar_numsuff="${bar_numsuff}0"
                   bar_size1=`expr "${bar_size1}" : '\(.*\).$'`
                   bar_size=`expr "${bar_size}" : '\(.*\).$'` || bar_size=0
               done

               # upround if necessary:
               if test -n "${bar_div}"
               then
                   bar_size1=`expr "${bar_size1}" + 1`
               fi
               # END LARGE

               # add to total size:
               bar_size=`expr ${bar_size} + ${bar_size1}`

               # BEGIN LARGE
               # adjust if still too large:
               while expr "${bar_size}" : "${bar_large_num}" >/dev/null 2>&1
               do
                   bar_div="${bar_div}."
                   bar_numsuff="${bar_numsuff}0"
                   bar_size=`expr "${bar_size}" : '\(.*\).$'`
               done
               # END LARGE
           else
               BAR_OK=0
           fi
       done
   fi

   bar_quad=`expr ${BAR_WIDTH} '*' ${BAR_WIDTH}`
   test "${bar_size}" -gt "${bar_quad}" || BAR_OK=0

   if test "${BAR_OK}" = 0
   then
       # For some reason, we cannot display the bar.  Thus plain operation:
       for bar_file
       do
           if test "${bar_file}" = "/dev/stdin"
           then
               eval "${BAR_CMD}"
           else
               eval "${BAR_CMD}" < "${BAR_DIR}${bar_file}${BAR_EXT}"
           fi
       done
   else
       # Compute wanted bytes per step:
       bar_want_bps=`expr ${bar_size} + ${BAR_WIDTH}`
       bar_want_bps=`expr ${bar_want_bps} - 1`
       bar_want_bps=`expr ${bar_want_bps} / ${BAR_WIDTH}`

       # Compute block count per step to keep within maximum block size:
       bar_count=1
       if test "${bar_want_bps}" -gt "${BAR_BS}"
       then
           bar_count=`expr ${bar_want_bps} + ${BAR_BS}`
           bar_count=`expr ${bar_count} - 1`
           bar_count=`expr ${bar_count} / ${BAR_BS}`
       fi

       # Compute block size for given count:
       bar_wc=`expr ${BAR_WIDTH} '*' ${bar_count}`

       bar_bs=`expr ${bar_size} + ${bar_wc}`
       bar_bs=`expr ${bar_bs} - 1`
       bar_bs=`expr ${bar_bs} / ${bar_wc}`

       # Compute bs * count, the bytes per step:
       bar_bps=`expr ${bar_bs} '*' ${bar_count}`

       # Compute bytes per hundredth:
       bar_bph=`expr ${bar_size} + 99`
       bar_bph=`expr ${bar_bph} / 100`


       # Run loop:
       bar_pos=0
       bar_graph="${BAR_L}"
       bar_cur_char=0
       bar_t0=`eval "${BAR_GET_TIME}" 2>/dev/null` || bar_t0=0
       for bar_file
       do
           # BEGIN TRACE
           if test "x${BAR_TRACE}" = x1
           then
               bar_trace=`expr "${bar_file}" : '.*/\([^/][^/]*\)$'` || bar_trace="${bar_file}"
               bar_trace=`expr "${bar_trace}${bar_t_space}" : '\('${bar_t_dot}'\)'`
               bar_trace="${bar_trace} "
           fi
           # END TRACE
           # Initial character position in bar for file:
           bar_char=`expr ${bar_pos} / ${bar_want_bps}` || bar_char=0
           while test "${bar_char}" -gt `expr ${bar_cur_char} + 4`
           do
               bar_graph="${bar_graph}${BAR_C1}${BAR_C1}${BAR_C1}${BAR_C1}${BAR_C1}"
               bar_cur_char=`expr ${bar_cur_char} + 5`
           done
           while test "${bar_char}" -gt "${bar_cur_char}"
           do
               bar_graph="${bar_graph}${BAR_C1}"
               bar_cur_char=`expr ${bar_cur_char} + 1`
           done

           # Get file size.  This must work now (we checked with test -f before).
           bar_size1=`eval "${BAR_GET_SIZE}" 2>/dev/null` || bar_size1=0

           # BEGIN LARGE
           # Divide and upround by pattern matching:
           if test -n "${bar_div}"
           then
               bar_size1=`expr "${bar_size1}" : '\(.*\)'${bar_div}'$'` || bar_size1=0
               bar_size1=`expr "${bar_size1}" + 1`
           fi
           # END LARGE

           # loop:
           bar_total=0
           (
               exec 6>&1
               exec 5<"${BAR_DIR}${bar_file}${BAR_EXT}"
               while test "${bar_total}" -lt "${bar_size1}"
               do
                   dd bs="${bar_bs}" count="${bar_count}${bar_numsuff}" <&5 >&6 2>/dev/null
                   bar_total=`expr ${bar_total} + ${bar_bps}`
                   if test "${bar_total}" -gt "${bar_size1}"
                   then
                       bar_total="${bar_size1}"
                   fi
                   bar_pos1=`expr ${bar_pos} + ${bar_total}`
                   bar_proz=`expr ${bar_pos1} / ${bar_bph}` || bar_proz=0
                   # BEGIN PERC
                   if test "x${BAR_PERC}" = x1
                   then
                       bar_perc="  ${bar_proz}% "
                       bar_perc=`expr "${bar_perc}" : '.*\(.....\)$'`
                   fi
                   # END PERC
                   # BEGIN ETA
                   if test "x${BAR_ETA}" = x1
                   then
                       bar_diff=`eval "${BAR_GET_TIME}" 2>/dev/null` || bar_diff=0
                       bar_diff=`expr ${bar_diff} - ${bar_t0} 2>/dev/null` || bar_diff=0
                       bar_100p=`expr 100 - ${bar_proz}` || bar_100p=0
                       bar_diff=`expr ${bar_diff} '*' ${bar_100p}` || bar_diff=0
                       bar_diff=`expr ${bar_diff} + ${bar_proz}` || bar_diff=0
                       bar_diff=`expr ${bar_diff} - 1` || bar_diff=0
                       bar_diff=`expr ${bar_diff} / ${bar_proz} 2>/dev/null` || bar_diff=0
                       if test "${bar_diff}" -gt 0
                       then
                           bar_t_unit=":"
                           if test "${bar_diff}" -gt 2700
                           then
                               bar_t_uni="h"
                               bar_diff=`expr ${bar_diff} / 60`
                           fi
                           bar_diff_h=`expr ${bar_diff} / 60` || bar_diff_h=0
                           if test "${bar_diff_h}" -gt 99
                           then
                               bar_eta="     ${bar_diff_h}${bar_t_unit} "
                           else
                               bar_diff_hi=`expr ${bar_diff_h} '*' 60` || bar_diff_hi=0
                               bar_diff=`expr ${bar_diff} - ${bar_diff_hi}` || bar_diff=0
                               bar_diff=`expr "00${bar_diff}" : '.*\(..\)$'`
                               bar_eta="     ${bar_diff_h}${bar_t_unit}${bar_diff} "
                           fi
                           bar_eta=`expr "${bar_eta}" : '.*\(......\)$'`
                       fi
                   fi
                   # END ETA

                   bar_char=`expr ${bar_pos1} / ${bar_want_bps}` || bar_char=0
                   while test "${bar_char}" -gt "${bar_cur_char}"
                   do
                       bar_graph="${bar_graph}${BAR_C1}"
                       ${BAR_ECHO} "${BAR_E_C1}" "
${bar_trace}${bar_eta}${bar_perc}${bar_graph}${BAR_E_C2}" 1>&2
                       bar_cur_char=`expr ${bar_cur_char} + 1`
                   done
               done
           ) | eval "${BAR_CMD}"
           bar_pos=`expr ${bar_pos} + ${bar_size1}`
       done
       # ${BAR_ECHO} "${BAR_E_C1}" "${BAR_R}${BAR_E_C2}" 1>&2
   fi

   if test "${bar_shown}" = 1
   then
       # BEGIN TRACE
       test "x${BAR_TRACE}" = x1 && bar_trace="${bar_t_space} "
       # END TRACE
       # BEGIN ETA
       test "x${BAR_ETA}" = x1   && bar_eta='      '
       # END ETA
       if test "x${BAR_CLEAR}" = x1
       then
           # BEGIN PERC
           test "x${BAR_PERC}" = x1 && bar_perc='     '
           # END PERC
          bar_lauf=0
           bar_graph=''
           while test `expr ${bar_lauf} + 5` -le "${BAR_WIDTH}"
           do
               bar_graph="${bar_graph}     "
               bar_lauf=`expr ${bar_lauf} + 5`
           done
           while test "${bar_lauf}" -lt "${BAR_WIDTH}"
           do
               bar_graph="${bar_graph} "
               bar_lauf=`expr ${bar_lauf} + 1`
           done
           ${BAR_ECHO} "${BAR_E_C1}" "
${bar_trace}${bar_eta}${bar_perc} ${bar_graph} 
${BAR_E_C2}" 1>&2
       else
           # BEGIN PERC
           test "x${BAR_PERC}" = x1 && bar_perc='100% '
           # END PERC
           bar_lauf=0
           bar_graph=''
           while test `expr ${bar_lauf} + 5` -le "${BAR_WIDTH}"
           do
               bar_graph="${bar_graph}${BAR_C1}${BAR_C1}${BAR_C1}${BAR_C1}${BAR_C1}"
               bar_lauf=`expr ${bar_lauf} + 5`
           done
           while test "${bar_lauf}" -lt "${BAR_WIDTH}"
           do
               bar_graph="${bar_graph}${BAR_C1}"
               bar_lauf=`expr ${bar_lauf} + 1`
           done
           ${BAR_ECHO} "${BAR_E_C1}" "
${bar_trace}${bar_eta}${bar_perc}${BAR_L}${bar_graph}${BAR_R}${BAR_E_C2}" 1>&2
           ${BAR_E_NL} 1>&2
       fi
   fi
}
####>-SCHNAPP-<########################################################


BAR_AWK_0=''
# Command line interface:
while test -n "$1"
do
   case "$1" in
       -o|-c|-w|-0|-1|-e|-d|-b|-s|-\[\]|-\[|-\]|-T)
           if test -z "$2"
           then
               echo "$0: Error: A non-empty argument was expected after $1" 1>&2
           fi
           BAR_ARG="$1"
           BAR_OPT="$2"
           shift
           shift
       ;;
       -o*|-c*|-w*|-0*|-1*|-e*|-d*|-b*|-T*)
           BAR_ARG=`expr "$1" : '\(-.\)'`
           BAR_OPT=`expr "$1" : '-.\(.*\)$'`
           shift
       ;;
       -h|-n|-p|-D|-D-|-q|-V|-t|-E|-L)
           BAR_ARG="$1"
           BAR_OPT=""
           shift
       ;;
       --) shift
           break
       ;;
       -*) echo "$0: Error: Unrecognized option: $1" 1>&2
           exit 1
       ;;
       *)
           break
       ;;
   esac

   case "${BAR_ARG}" in
       -h)  echo 'Usage: bar [-n] [-p] [-q] [-o FILE] [-c CMD] [-s SIZE] [-b SIZE]'
            echo '           [-w WIDTH] [-0/1/[/] CHAR] [-d DIR] [-e EXT] [Files]'
            echo '       bar -V'
            echo '       bar -D'
            echo '       bar -D-'
            echo 'Options:'
            echo '     -h         displays help'
            echo '     -o FILE    sets output file'
            echo '     -c CMD     sets individual execution command'
            echo '     -e EXT     append an extension to each file'
            echo '     -d DIR     prepend this prefix to each file (a directory must end in /)'
            echo '     -s SIZE    expected number of bytes.  Use for pipes.  This is a hint'
            echo '                only that must be greater or equal to the amount actually'
            echo '                processed.  Further, this only works for single files.'
            echo '     -b SIZE    maximal block size (bytes) (default: 1048567)'
            echo '     -w WIDTH   width in characters        (default: terminal width-3 or 76)'
            echo '     -0 CHAR    character for empty bar    (default: .)'
            echo '     -1 CHAR    character for full bar     (default: =)'
            echo '     -[ CHAR    first character of bar     (default: [)'
            echo '     -] CHAR    last  character of bar     (default: ])'
            echo '     -n         clears bar after termination'
            echo '     -t         traces (=displays) which file is processed'
            echo '     -T WIDTH   no of characters reserved for the file display of -t'
            echo '     -p         hides percentage'
            echo '     -E         hides estimated time display'
            echo '     -q         hides the whole bar, be quiet'
            echo '     -D         tries to dump the bar_cat() shell function, then exit.'
            echo '                Here, -t, -p, -E remove the corresponding feature completely.'
            echo '                Further, -L removes large file support from the code.'
            echo '     -D-        same as -D, but dumps the function body only'
            echo '     -V         displays version number'
            echo '     --         end of options: only file names follow'
            exit 0
       ;;
       -n)  BAR_CLEAR=1
       ;;
       -L)  BAR_LARGE=0
            BAR_AWK_0="${BAR_AWK_0} /END  *LARGE/ {x=1} ;"
            BAR_AWK_0="${BAR_AWK_0} /BEGIN  *LARGE/ {x=0} ;"
       ;;
       -t)  BAR_TRACE=1
            BAR_AWK_0="${BAR_AWK_0} /END  *TRACE/ {x=1} ;"
            BAR_AWK_0="${BAR_AWK_0} /BEGIN  *TRACE/ {x=0} ;"
       ;;
       -T)  BAR_TRACE_WIDTH="${BAR_OPT}"
       ;;
       -q)  BAR_OK=0
       ;;
       -p)  BAR_PERC=0
            BAR_AWK_0="${BAR_AWK_0} /END  *PERC/ {x=1} ;"
            BAR_AWK_0="${BAR_AWK_0} /BEGIN  *PERC/ {x=0} ;"
       ;;
       -E)  BAR_ETA=0
            BAR_AWK_0="${BAR_AWK_0} /END  *ETA/ {x=1} ;"
            BAR_AWK_0="${BAR_AWK_0} /BEGIN  *ETA/ {x=0} ;"
       ;;
       -V)  echo "bar v${BAR_VERSION}"
            exit 0
       ;;
       -D)  echo "BAR_VERSION=${BAR_VERSION}"
            awk "${BAR_AWK_0}"'{sub(/ *#.*$/,"")} ; /^bar_cat/ {x=1} ; {sub(/^  */,"")} ; /./ {if(x)print} ; /^}/ {x=0}' "$0"
            exit 0
       ;;
       -D-) echo "BAR_VERSION=${BAR_VERSION}"
            awk "${BAR_AWK_0}"'{sub(/ *#.*$/,"")} ; /^}/ {x=0} ; {sub(/^  */,"")} ; /./ {if(x)print} ; /^{/ {x=1}' "$0"
            exit 0
       ;;
       -o)  exec 1>"${BAR_OPT}"
       ;;
       -c)  BAR_CMD="${BAR_OPT}"
       ;;
       -b)  BAR_BS="${BAR_OPT}"
            if BAR_RAW=`expr "${BAR_BS}" : '\(.*\)k$'`
            then
                BAR_BS=`expr ${BAR_RAW} '*' 1024`
            elif BAR_RAW=`expr "${BAR_BS}" : '\(.*\)M$'`
            then
                BAR_BS=`expr ${BAR_RAW} '*' 1048567`
            fi
       ;;
       -s)  BAR_SIZE="${BAR_OPT}"
            if BAR_RAW=`expr "${BAR_SIZE}" : '\(.*\)k$'`
            then
                BAR_SIZE=`expr ${BAR_RAW} '*' 1024`
            elif BAR_RAW=`expr "${BAR_SIZE}" : '\(.*\)M$'`
            then
                BAR_SIZE=`expr ${BAR_RAW} '*' 1048567`
            fi
            if test "$#" -gt 1
            then
                echo "Error: -s cannot be specified for multiple input files." 1>&2
                exit 1
            fi
       ;;   
       -e)  BAR_EXT="${BAR_OPT}"
       ;;   
       -d)  BAR_DIR="${BAR_OPT}"
       ;;   
       -0)  BAR_C0="${BAR_OPT}"
       ;;   
       -1)  BAR_C1="${BAR_OPT}"
       ;;   
       -\[) BAR_L="${BAR_OPT}"
       ;;
       -\]) BAR_R="${BAR_OPT}"
       ;;
       -\[\])
            BAR_L="${BAR_OPT}"
            BAR_R="${BAR_OPT}"
       ;;
       -w)  BAR_WIDTH="${BAR_OPT}"
       ;;
    esac
done

# Invoke main function:
if test "$#" = 0
then
    bar_cat /dev/stdin
else
    bar_cat "$@"
fi