How not to interrupt the execution of the loop in case of errors and write them to a variable? - linux

I want to backup mikrotiks via scp. This script loops through the hosts from the hosts.txt. One by one, connects to each device from the list. Does backup and all manipulations. If at some stage it was not possible to connect to the device, then an empty backup is formed, which is then sent to the cloud.
I want to check. If it was not possible to connect to the host, then write this host into a variable, line by line, and go to the next device. Next, I will notify about all failed connections.
The problem is that only the first error is written to the variable, all subsequent ones are ignored.
Tell me who knows what.
#!/bin/bash
readarray -t hosts < hosts.txt
DATE=$(date +'%Y-%m-%d_%H-%M-%S')
ROS='<br>'
ERR=( )
#Get values from main list
for host in ${hosts[*]}
do
#Get values from sub list
hostname=($(echo ${host} | tr "_" " "))
echo ${hostname[0]} - ${hostname[1]}
#connect & backup & transfer & archive & rm old files & moove to cloud
if ssh backup#${hostname[0]} -C "/system backup save name=${hostname[1]}_$DATE"; then
scp backup#${hostname[0]}:"${hostname[1]}_$DATE.backup" ./
ssh backup#${hostname[0]} -C "rm ${hostname[1]}_$DATE.backup"
tar -czvf ./${hostname[1]}_$DATE.tar.gz ${hostname[1]}_$DATE.backup
scp ./${hostname[1]}_$DATE.tar.gz my#cloud.com:/var/www/my.cloud.com/backups/mikrotik/
rm ${hostname[1]}_$DATE.backup ${hostname[1]}_$DATE.tar.gz
ROS=$ROS${hostname[1]}"<br>"
else
ERR+=(${hosts[*]} "is not ready")
fi
done
hosts.txt
10.10.8.11_CAP-1
10.10.9.12_CAP-2
10.10.10.13_CAP-3

As I noted in the comments, you're misusing the array notation. Your line ERR=(${hosts[*]} "is not ready") should be ERR+=(${hosts[*]} "is not ready") and you should define ERR as an array, not a scalar: ERR=( ) for example, or declare -a ERR. Similarly with ROS.
Here's a test script that avoids all the ssh and scp work to demonstrate that lists of passing and failing hosts work — that the arrays hosts, ROS and ERR are handled correctly.
Note the use of "${ERR[#]}" with double quotes and # instead of no quotes and *. The difference matters because the values in the array contain spaces. Try the alternatives. Note, too, that printf always prints, even when there is no argument corresponding to the %s in the format string. Hence the check on the number of elements in the array before invoking printf.
#!/bin/bash
# Needs Bash 4.x - Bash 3.2 as found on Macs does not support readarray
# readarray -t hosts < hosts.txt
hosts=( passed-1 failed-2 passed-3 failed-4 passed-5 )
declare -a ERR
declare -a ROS
status=passed
for host in "${hosts[#]}"
do
if [ "$status" = "passed" ]
then ROS+=( "$host $status" ); status="failed"
else ERR+=( "$host $status" ); status="passed"
fi
done
# Brute force but handles empty lists
for passed in "${ROS[#]}"
do printf "== PASS == [%s]\n" "$passed"
done
for failed in "${ERR[#]}"
do printf "!! FAIL !! [%s]\n" "$failed"
done
# Alternative - better spread over multiple lines each
if [ "${#ROS}" -gt 0 ]; then printf "== PASS == [%s]\n" "${ROS[#]}"; fi
if [ "${#ERR}" -gt 0 ]; then printf "!! FAIL !! [%s]\n" "${ERR[#]}"; fi
Output:
== PASS == [passed-1 passed]
== PASS == [passed-3 passed]
== PASS == [passed-5 passed]
!! FAIL !! [failed-2 failed]
!! FAIL !! [failed-4 failed]
== PASS == [passed-1 passed]
== PASS == [passed-3 passed]
== PASS == [passed-5 passed]
!! FAIL !! [failed-2 failed]
!! FAIL !! [failed-4 failed]
I'm sorry there are so many failures to backup your data!

Related

creating multiple user named and numbered files without loop from bash

Totally new in bash programming and got a problem like this:
N empty files should be created. The file sum N and also the file name should be given from users via command line.
no loop and getopts should be used.
I've tried something like this which i found from google but it doesn't works. I got confused between linux and windows bash scripts. Hope someone can help me with the problem. Thank you!
#!/bin/bash
echo $N
:start
if [ $N > 0 ]
then
touch xyzfile_$N
$N = $N - 1
pause
goto start
else
then
echo "no data will be created"
fi
The command line and the result should be (for example if N=7) look like this:
command line:
./createfiles -n filename 7
expected result:
filename_1,filename_2,filename_3...filename_7
You can do this using a recursive shell function:
fn() {
# add some sanity checks to check parameters
touch "$1_${2}"
(($2 > 1)) && fn "$1" $(($2 - 1))
}
This part is a recursive call:
(($2 > 1)) && fn "$1" $(($2 - 1))
Where it basically calls itself by decrementing 1 from 2nd argument as long as $2 is greater than 1.
Then just call it as:
fn filename 7
It will create 7 files as:
filename_1 filename_2 filename_3 filename_4 filename_5 filename_6 filename_7
I ended up the question with a for-loop like this:
createfile() {
name="$1"
num="$2"
for((i=1;i<=num;i++))
do
touch "$1_${i}"
done
}
createfile $1 ${2}
It works great actually. And now I tried to solve the problem with getopts and it seems a function call doesn't work in the case option

How to make RETURN trap in bash preserve the return code?

Below is the simplified scheme of the script I am writing. The program must take parameters in different ways, so there is a fine division to several functions.
The problem is that the chainloading of the return value from deeper functions breaks on the trap, where the result is to be checked to show a message.
#! /usr/bin/env bash
check_a_param() {
[ "$1" = return_ok ] && return 0 || return 3
}
check_params() {
# This trap should catch negative results from the functions
# performing actual checks, like check_a_param() below.
return_trap() {
local retval=$?
[ $retval -ne 0 ] && echo 'Bad, bad… Dropping to manual setup.'
return $retval
}
# check_params can be called from different functions, not only
# setup(). But the other functions don’t care about the return value
# of check_params().
[ "${FUNCNAME[1]}" = setup ] \
&& trap "return_trap; got_retval=$?; trap - RETURN; return $got_retval;" RETURN
check_a_param 'return_bad' || return $?
# …
# Here we check another parameters in the same way.
# …
echo 'Provided parameters are valid.'
return 0 # To be sure.
}
ask_for_params() {
echo 'User sets params manually step by step.'
}
setup() {
[ "$1" = force_manual ] && local MANUAL=t
# If gathered parameters do not pass check_params()
# the script shall resort to asking user for entering them.
[ ! -v MANUAL ] && {
check_params \
&& echo "check_params() returned with 0. Not running manual setup."
|| false
}|| ask_for_params
# do_the_job
}
setup "$#" # Either empty or ‘force_manual’.
How it should work:
↗ 3 → 3→ trap →3 ↗ || ask_for_params ↘
check_a_param >>> check_params >>> [ ! -v MANUAL ] ↓
↘ 0 → 0→ trap →0 ↘ && ____________ do_the_job
The idea is, if a check fails, its return code forces check_params() to return, too, which, in its turn would trigger the || ask_for_params condition in setup(). But the trap returns 0:
↗ 3 → 3→ trap →0
check_a_param >>> check_params >>> [ ! -v MANUAL ] &&… >>> do_the_job
↘ 0 → 0→ trap →0
If you try to run the script as is, you should see
Bad, bad… Dropping to manual setup.
check_params() returned with 0. Not running manual setup.
Which means that the bad result triggered the trap(!) but the mother function that has set it, didn’t pass the result.
In attempt to set a hack I’ve tried
to set retval as a global variable declare -g retval=$? in the return_trap() and use its value in the line setting the trap. The variable is set ([ -v retval ] returns successfully), but …has no value. Funny.
okay, let’s putretval=Eeh to the check_params(), outside the return_trap() and just set it to $? as a usual param. Nope, the retval in the function doesn’t set the value for the global variable, it stays ‘Eeh’. No, there’s no local directive. It should be treated as global by default. If you put test=1 to check_params() and test=3 in check_a_param() and then print it with echo $testat the end of setup(), you should see 3. At least I do. declare -g doesn’t make any difference here, as expected.
maybe that’s the scope of the function? No, that’s not it either. Moving return_trap() along with declare -g retval=Eeh doesn’t make any difference.
when the modern software means fall, it’s time to resort to good old writing to a file. Let’s print the retval to /tmp/t with retval=$?; echo $retval >/tmp/t in return_trap() and read it back with
trap "return_trap; trap - RETURN; return $(</tmp/t)" RETURN
Now we can finally see that the last return directive which reads the number from the file, actually returns 3. But check_params() still returns 0!
++ trap - RETURN
++ return 3
+ retval2=0
+ echo 'check_params() returned with 0. Not running manual setup.'
check_params() returned with 0. Not running manual setup.
If the argument to the trap command is strictly a function name, it returns the original result. The original one, not what return_trap() returns. I’ve tried to increment the result and still got 3.
You may also ask ‘Why would you need to unset the trap so much?’. It’s to avoid another bug, which causes the trap to trigger every time, even when check_params() is called from another function. Traps on RETURN are local things, they aren’t inherited by another functions unless there’s debug or trace flags explicitly set on them, but it looks like they keep traps set on them between runs. Or bash keeps traps for them. This trap should only be set when check_params() is called from a specific function, but if the trap is not unset, it continues to get triggered every time check_a_param() returns a value greater than zero independently of what’s in FUNCNAME[1].
Here I give up, because the only exit I see now is to implement a check on the calling function before each || return $? in check_params(). But it’s so ugly it hurts my eyes.
I may only add that, $? in the line setting the trap will always return 0. So, if you, for example, declare a local variable retval in return_trap(), and put such code to check it
trap "return_trap; [ -v retval ]; echo $?; trap - RETURN; return $retval" RETURN
it will print 0 regardless of whether retval is actually set or not, but if you use
trap "return_trap; [ -v retval ] && echo set || echo unset; trap - RETURN; return $retval" RETURN
It will print ‘unset’.
GNU bash, version 4.3.39(1)-release (x86_64-pc-linux-gnu)
Funny enough,
trap "return_trap; trap - RETURN" RETURN
simply works.
[ ! -v MANUAL ] && {
check_params; retval2=$?
[ $retval2 -eq 0 ] \
&& echo "check_params() returned with 0. Not running manual setup." \
|| false
}|| ask_for_params
And here’s the trace.
+ check_a_parameter return_bad
+ '[' return_bad = return_ok ']'
+ return 3
+ return 3
++ return_trap
++ local retval=3
++ echo 3
++ '[' 3 -ne 0 ']'
++ echo 'Bad, bad… Dropping to manual setup.'
Bad, bad… Dropping to manual setup.
++ return 3
++ trap - RETURN
+ retval2=3
+ '[' 3 -eq 0 ']'
+ false
+ ask_for_params
+ echo 'User sets params manually step by step.'
User sets params manually step by step.
So the answer is simple: do not try to overwrite the result in the line passed to the trap command. Bash handles everything for you.

Bash split a space-delimited string into a variable number of substrings

Suppose I have two space-delimited strings in my bash script, which are
permitted_hosts="node1 node2 node3"
and
runs_list="run1 run2 run3 run4 run5"
These respectively represent the list of permitted hosts and the list of runs to execute. So, I need to run each of the runs in $runs_list on 1 of the hosts in $permitted_hosts.
What I'd like to do is divide $runs_list into $N substrings, where $N is the number of elements in $permitted_hosts and where each of the $N substrings is mapped to a different element in $permitted_hosts.
If that's confusing, then consider this concrete workaround solution. For the exact given values of $permitted_hosts and $runs_list above, the following bash script checks the current host, and if the current host is in $permitted_hosts, then it launches the runs in $runs_list that are associated with the current host. Of course, this script doesn't use the variables $permitted_hosts and $runs_list, but it achieves the desired effect for the given example. What I'm really trying to do is modify the code below so that I can modify the values of variables $permitted_hosts and $runs_list and it will work appropriately.
#!/bin/bash
hostname=$(hostname)
if [ "$hostname" == "node1" ]; then
runs="run1 run2"
elif [ "$hostname" == "node2" ]; then
runs="run3 run4"
elif [ "$hostname" == "node3" ]; then
runs="run5"
else
echo "ERROR: Invoked on invalid host ('$hostname')! Aborting."
exit 0
fi
for run in $runs; do
./launch $run
done
So, firstly — instead of space-delimited strings, you should probably use arrays:
permitted_hosts=(node1 node2 node3)
runs_list=(run1 run2 run3 run4 run5)
If you have to start out with space-delimited strings, you can at least convert them to arrays:
permitted_hosts=($permitted_hosts_str)
runs_list=($runs_list_str)
That out of the way . . . basically you have two steps: (1) convert the hostname into an integer representing its position in permitted_hosts:
hostname="$(hostname)"
num_hosts="${#permitted_hosts[#]}" # for convenience
host_index=0
while true ; do
if [[ "${permitted_hosts[host_index]}" = "$hostname" ]] ; then
break
fi
(( ++host_index ))
if (( host_index > num_hosts )) ; then
printf 'ERROR: Invoked on invalid host ('%s')! Aborting.\n' "$hostname" >&2
exit 1
fi
done
# host_index is now an integer index into permitted_hosts
and (2) convert this integer into an appropriate subset of runs_list:
num_runs="${#runs_list[#]}" # for convenience
for (( run_index = host_index ; run_index < num_runs ; run_index += num_hosts )) ; do
./launch "${runs_list[run_index]}"
done
So, for example, if you have H hosts, then host #0 will launch run #0, run #H, run #2H, etc.; host #1 will launch run #1, run #H+1, run #2H+1, etc.; and so on.

Bash prompt with the last exit code

I've been trying to customize my Bash prompt so that it will look like
[feralin#localhost ~]$ _
with colors. I managed to get constant colors (the same colors every time I see the prompt), but I want the username ('feralin') to appear red, instead of green, if the last command had a nonzero exit status. I came up with:
\e[1;33m[$(if [[ $? == 0 ]]; then echo "\e[0;31m"; else echo "\e[0;32m"; fi)\u\e[m#\e[1;34m\h \e[0;35m\W\e[1;33m]$ \e[m
However, from my observations, the $(if ...; fi) seems to be evaluated once, when the .bashrc is run, and the result is substituted forever after. This makes the name always green, even if the last exit code is nonzero (as in, echo $?). Is this what is happening? Or is it simply something else wrong with my prompt? Long question short, how do I get my prompt to use the last exit code?
As you are starting to border on a complex PS1, you might consider using PROMPT_COMMAND. With this, you set it to a function, and it will be run after each command to generate the prompt.
You could try the following in your ~/.bashrc file:
PROMPT_COMMAND=__prompt_command # Function to generate PS1 after CMDs
__prompt_command() {
local EXIT="$?" # This needs to be first
PS1=""
local RCol='\[\e[0m\]'
local Red='\[\e[0;31m\]'
local Gre='\[\e[0;32m\]'
local BYel='\[\e[1;33m\]'
local BBlu='\[\e[1;34m\]'
local Pur='\[\e[0;35m\]'
if [ $EXIT != 0 ]; then
PS1+="${Red}\u${RCol}" # Add red if exit code non 0
else
PS1+="${Gre}\u${RCol}"
fi
PS1+="${RCol}#${BBlu}\h ${Pur}\W${BYel}$ ${RCol}"
}
This should do what it sounds like you want. Take a look a my bashrc's sub file if you want to see all the things I do with my __prompt_command function.
If you don't want to use the prompt command there are two things you need to take into account:
getting the value of $? before anything else. Otherwise it'll be overridden.
escaping all the $'s in the PS1 (so it's not evaluated when you assign it)
Working example using a variable
PS1="\$(VALU="\$?" ; echo \$VALU ; date ; if [ \$VALU == 0 ]; then echo zero; else echo nonzero; fi) "
Working example without a variable
Here the if needs to be the first thing, before any command that would override the $?.
PS1="\$(if [ \$? == 0 ]; then echo zero; else echo nonzero; fi) "
Notice how the \$() is escaped so it's not executed right away, but each time PS1 is used. Also all the uses of \$?.
Compact solution:
PS1='... $(code=${?##0};echo ${code:+[error: ${code}]})'
This approach does not require PROMPT_COMMAND (apparently this can be slower sometimes) and prints [error: <code>] if the exit code is non-zero, and nothing if it's zero:
... > false
... [error: 1]> true
... >
Change the [error: ${code}] part depending on your liking, with ${code} being the non-zero code to print.
Note the use of ' to ensure the inline $() shell gets executed when PS1 is evaluated later, not when the shell is started.
As bonus, you can make it colorful in red by adding \e[01;31m in front and \e[00m after to reset:
PS1='... \e[01;31m$(code=${?##0};echo ${code:+[error: ${code}]})\e[00m'
--
How it works:
it uses bash parameter substitution
first, the ${?##0} will read the exit code $? of the previous command
the ## will remove any 0 pattern from the beginning, effectively making a 0 result an empty var (thanks #blaskovicz for the trick)
we assign this to a temporary code variable as we need to do another substitution, and they can't be nested
the ${code:+REPLACEMENT} will print the REPLACEMENT part only if the variable code is set (non-empty)
this way we can add some text and brackets around it, and reference the variable again inline: [error: ${code}]
I wanted to keep default Debian colors, print the exact code, and only print it on failure:
# Show exit status on failure.
PROMPT_COMMAND=__prompt_command
__prompt_command() {
local curr_exit="$?"
local BRed='\[\e[0;91m\]'
local RCol='\[\e[0m\]'
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u#\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
if [ "$curr_exit" != 0 ]; then
PS1="[${BRed}$curr_exit${RCol}]$PS1"
fi
}
The following provides a leading green check mark when the exit code is zero and a red cross in all other cases. The remainder is a standard colorized prompt. The printf statements can be modified to present the two states that were originally requested.
PS1='$(if [ $? -eq 0 ]; then printf "\033[01;32m""\xE2\x9C\x93"; else printf "\033[01;31m""\xE2\x9C\x95"; fi) \[\e[00;32m\]\u#\h\[\e[00;30m\]:\[\e[01;33m\]\w\[\e[01;37m\]\$ '
Why didn't I think about that myself? I found this very interesting and added this feature to my 'info-bar' project. Eyes will turn red if the last command failed.
#!/bin/bash
eyes=(O o ∘ ◦ ⍤ ⍥) en=${#eyes[#]} mouth='_'
face () { # gen random face
[[ $error -gt 0 ]] && ecolor=$RED || ecolor=$YLW
if [[ $1 ]]; then printf "${eyes[$[RANDOM%en]]}$mouth${eyes[$[RANDOM%en]]}"
else printf "$ecolor${eyes[$[RANDOM%en]]}$YLW$mouth$ecolor${eyes[$[RANDOM%en]]}$DEF"
fi
}
info () { error=$?
[[ -d .git ]] && { # If in git project folder add git status to info bar output
git_clr=('GIT' $(git -c color.ui=always status -sb)) # Colored output 4 info
git_tst=('GIT' $(git status -sb)) # Simple output 4 test
}
printf -v line "%${COLUMNS}s" # Set border length
date=$(printf "%(%a %d %b %T)T") # Date & time 4 test
test=" O_o $PWD ${git_tst[*]} $date o_O " # Test string
step=$[$COLUMNS-${#test}]; [[ $step -lt 0 ]] && step=0 # Count spaces
line="$GRN${line// /-}$DEF\n" # Create lines
home="$BLD$BLU$PWD$DEF" # Home dir info
date="$DIM$date$DEF" # Colored date & time
#------+-----+-------+--------+-------------+-----+-------+--------+
# Line | O_o |homedir| Spaces | Git status | Date| o_O | Line |
#------+-----+-------+--------+-------------+-----+-------+--------+
printf "$line $(face) $home %${step}s ${git_clr[*]} $date $(face) \n$line" # Final info string
}
PS1='${debian_chroot:+($debian_chroot)}\n$(info)\n$ '
case "$TERM" in xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)} $(face 1) \w\a\]$PS1";;
esac
Improved demure answer:
I think this is important because the exit status is not always 0 or 1.
if [ $EXIT != 0 ]; then
PS1+="${Red}${EXIT}:\u${RCol}" # Add red if exit code != 0
else
PS1+="${Gre}${EXIT}:\u${RCol}" # Also displays exit status
fi
To preserve the original prompt format (not just colors),
you could append following to the end of file ~/.bashrc:
PS1_ORIG=$PS1 # original primary prompt value
PROMPT_COMMAND=__update_prompt # Function to be re-evaluated after each command is executed
__update_prompt() {
local PREVIOUS_EXIT_CODE="$?"
if [ $PREVIOUS_EXIT_CODE != 0 ]; then
local RedCol='\[\e[0;31m\]'
local ResetCol='\[\e[0m\]'
local replacement="${RedCol}\u${ResetCol}"
# Replace username color
PS1=${PS1_ORIG//]\\u/]$replacement}
## Alternative: keep same colors, append exit code
#PS1="$PS1_ORIG[${RedCol}error=$PREVIOUS_EXIT_CODE${ResetCol}]$ "
else
PS1=$PS1_ORIG
fi
}
See also the comment about the alternative approach that preserves username color and just appends an error code in red to the end of the original prompt format.
You can achieve a similar result to include a colored (non-zero) exit code in a prompt, without using subshells in the prompt nor prompt_command.
You color the exit code portion of the prompt, while having it only appear when non-zero.
Core 2$ section of the prompt: \\[\\033[0;31;4m\\]\${?#0}\\[\\033[0;33m\\]\$ \\[\\033[0m\\]
Key elements:
return code, if not 0: \${?#0} (specificly "removes prefix of 0")
change color without adding to calculated prompt-width: \\[\\033[0;31m\\]
\\[ - begin block
\\033 - treat as 0-width, in readline calculations for cmdline editing
[0;31;4m - escape code, change color, red fg, underline
\\] - end block
Components:
\\[\\033[0;31;4m\\] - set color 0;31m fg red, underline
\${?#0} - display non-zero status (by removing 0 prefix)
\\[\\033[0;33m\\] - set color 0;33m fg yellow
\$ - $ or # on EUID
\\[\\033[0m\\] - reset color
The full PS1 I use (on one host):
declare -x PS1="\\[\\033[0;35m\\]\\h\\[\\033[1;37m\\] \\[\\033[0;37m\\]\\w \\[\\033[0;33m\\]\\[\\033[0;31;4m\\]\${?#0}\\[\\033[0;33m\\]\$ \\[\\033[0m\\]"
Note: this addresses a natural extension to this question, in a more enduring way then a comment.
Bash
function my_prompt {
local retval=$?
local field1='\u#\h'
local field2='\w'
local field3='$([ $SHLVL -gt 1 ] && echo \ shlvl:$SHLVL)$([ \j -gt 0 ] && echo \ jobs:\j)'"$([ ${retval} -ne 0 ] && echo \ exit:$retval)"
local field4='\$'
PS1=$'\n'"\e[0;35m${field1}\e[m \e[0;34m${field2}\e[m\e[0;31m${field3}\e[m"$'\n'"\[\e[0;36m\]${field4}\[\e[m\] "
}
PROMPT_COMMAND="my_prompt; ${PROMPT_COMMAND}"
Zsh
PROMPT=$'\n''%F{magenta}%n#%m%f %F{blue}%~%f%F{red}%(2L. shlvl:%L.)%(1j. jobs:%j.)%(?.. exit:%?)%f'$'\n''%F{cyan}%(!.#.$)%f '
Images of prompt

Recursive breadth first traversal in Bash

I'm trying to discover why my traversal isn't working. I believe I've isolated the problem to the point in code where it says "directory contains" and then what was passed to the function. The function gets passed an array containing all of the new file paths to echo but for some reason it is only receiving the first one. Am I passing the array incorrectly or could it be something else?
#!/bin/bash
traverse(){
directory=$1
for x in ${directory[#]}; do
echo "directory contains: " ${directory[#]}
temp=(`ls $x`)
new_temp=( )
for y in ${temp[#]}; do
echo $x/$y
new_temp=(${new_temp[#]} $x/$y)
done
done
((depth--))
if [ $depth -gt 0 ]; then
traverse $new_temp
fi
}
You cannot pass arrays as arguments. You can only pass strings. You'll have to expand
the array to a list of its contents first. I've taken the liberty of making depth
local to your function, rather than what I assume is a global variable.
traverse(){
local depth=$1
shift
# Create a new array consisting of all the arguments.
# Get into the habit of quoting anything that
# might contain a space
for x in "$#"; do
echo "directory contains: $#"
new_temp=()
for y in "$x"/*; do
echo "$x/$y"
new_temp+=( "$x/$y" )
done
done
(( depth-- ))
if (( depth > 0 )); then
traverse $depth "${new_temp[#]}"
fi
}
$ dir=( a b c d )
$ init_depth=3
$ traverse $init_depth "${dir[#]}"

Resources