| 1 | #!/usr/bin/env bash
 | 
| 2 | #
 | 
| 3 | # Usage:
 | 
| 4 | #   demo/xtrace1.sh <function name>
 | 
| 5 | 
 | 
| 6 | #set -o nounset
 | 
| 7 | #set -o pipefail
 | 
| 8 | #set -o errexit
 | 
| 9 | 
 | 
| 10 | myfunc() {
 | 
| 11 |   : "myfunc $1"
 | 
| 12 | }
 | 
| 13 | 
 | 
| 14 | banner() {
 | 
| 15 |   echo
 | 
| 16 |   echo "$@"
 | 
| 17 |   echo
 | 
| 18 | }
 | 
| 19 | 
 | 
| 20 | # bash is the only shell with the "first char repeated" behavior
 | 
| 21 | first_char() {
 | 
| 22 |   for sh in bash dash mksh zsh bin/osh; do
 | 
| 23 |     echo
 | 
| 24 |     echo $sh
 | 
| 25 |     export PS4='$PWD '
 | 
| 26 |     #export PS4='$ '
 | 
| 27 |     $sh -x -c 'echo $(echo hi)-'
 | 
| 28 |   done
 | 
| 29 | }
 | 
| 30 | 
 | 
| 31 | # bash repeats the + for command sub, eval, source.  Other shells don't.
 | 
| 32 | posix() {
 | 
| 33 |   banner COMMANDSUB
 | 
| 34 |   set -x
 | 
| 35 |   foo=$(myfunc commandsub)
 | 
| 36 |   set +x
 | 
| 37 | 
 | 
| 38 |   # Hm this gives you ++
 | 
| 39 |   banner EVAL
 | 
| 40 |   set -x
 | 
| 41 |   eval myfunc evalarg
 | 
| 42 |   set +x
 | 
| 43 | 
 | 
| 44 |   # Also gives you ++
 | 
| 45 |   banner SOURCE
 | 
| 46 |   set -x
 | 
| 47 |   . spec/testdata/source-argv.sh 1 2
 | 
| 48 |   set +x
 | 
| 49 | }
 | 
| 50 | 
 | 
| 51 | # Various stacks:
 | 
| 52 | # - proc call stack (similar: FUNCNAME)
 | 
| 53 | # - process stack (similar: BASHPID)
 | 
| 54 | # - interpreter stack (eval, source.  xtrace already respects this)
 | 
| 55 | #   - and maybe Oil subinterpreters
 | 
| 56 | 
 | 
| 57 | # User level:
 | 
| 58 | # - Color
 | 
| 59 | # - Indentation
 | 
| 60 | # - HTML
 | 
| 61 | #
 | 
| 62 | # What you really want PARSEABLE traces.  Which means each trace item is ONE
 | 
| 63 | # LINE.  And emitted by a single write() call.
 | 
| 64 | #
 | 
| 65 | # Related debugging features of OSH:
 | 
| 66 | #
 | 
| 67 | # - pp cell (ASDL), pp proc (QTT)
 | 
| 68 | # - osh -n (ASDL)
 | 
| 69 | # - Oil expressions: = keyword (ASDL)
 | 
| 70 | 
 | 
| 71 | shopt -s expand_aliases
 | 
| 72 | alias e=echo
 | 
| 73 | 
 | 
| 74 | simple() {
 | 
| 75 |   e alias
 | 
| 76 |   myfunc invoke
 | 
| 77 |   ( myfunc subshell )
 | 
| 78 | }
 | 
| 79 | 
 | 
| 80 | main() {
 | 
| 81 |   banner ALIAS
 | 
| 82 | 
 | 
| 83 |   set -x
 | 
| 84 |   e alias
 | 
| 85 |   set +x
 | 
| 86 | 
 | 
| 87 |   banner FUNC
 | 
| 88 | 
 | 
| 89 |   set -x
 | 
| 90 |   myfunc invoke
 | 
| 91 |   set +x
 | 
| 92 | 
 | 
| 93 |   banner SUBSHELL
 | 
| 94 |   # No increase in +
 | 
| 95 |   # pid and SHLVL do NOT increase.  BASHPID increases.
 | 
| 96 |   set -x
 | 
| 97 |   : pid=$$ BASHPID=$BASHPID SHLVL=$SHLVL
 | 
| 98 |   ( myfunc subshell; : pid=$$ BASHPID=$BASHPID SHLVL=$SHLVL )
 | 
| 99 |   set +x
 | 
| 100 | 
 | 
| 101 |   # Now it changes to ++
 | 
| 102 |   banner COMMANDSUB
 | 
| 103 |   set -x
 | 
| 104 |   foo=$(myfunc commandsub)
 | 
| 105 |   set +x
 | 
| 106 | 
 | 
| 107 |   banner PIPELINE
 | 
| 108 |   set -x
 | 
| 109 |   myfunc pipeline | sort
 | 
| 110 |   set +x
 | 
| 111 | 
 | 
| 112 |   banner THREE
 | 
| 113 | 
 | 
| 114 |   # Increase to three
 | 
| 115 |   set -x
 | 
| 116 |   foo=$(echo $(myfunc commandsub))
 | 
| 117 |   echo $foo
 | 
| 118 |   set +x
 | 
| 119 | 
 | 
| 120 |   # Hm this gives you ++
 | 
| 121 |   banner EVAL
 | 
| 122 |   set -x
 | 
| 123 |   eval myfunc evalarg
 | 
| 124 |   set +x
 | 
| 125 | 
 | 
| 126 |   # Also gives you ++
 | 
| 127 |   banner SOURCE
 | 
| 128 |   set -x
 | 
| 129 |   source spec/testdata/source-argv.sh 1 2
 | 
| 130 |   set +x
 | 
| 131 | 
 | 
| 132 |   banner RECURSIVE
 | 
| 133 |   set -x
 | 
| 134 |   $0 myfunc dollar-zero
 | 
| 135 |   set +x
 | 
| 136 | 
 | 
| 137 |   # TODO: SHELLOPTS not set here?
 | 
| 138 |   banner "SHELLOPTS=$SHELLOPTS"
 | 
| 139 | 
 | 
| 140 |   export SHELLOPTS
 | 
| 141 |   set -x
 | 
| 142 |   $0 myfunc dollar-zero-shellopts
 | 
| 143 |   set +x
 | 
| 144 | }
 | 
| 145 | 
 | 
| 146 | main2() {
 | 
| 147 |   set -x
 | 
| 148 | 
 | 
| 149 |   # OK this is useful.
 | 
| 150 | 
 | 
| 151 |   # https://unix.stackexchange.com/questions/355965/how-to-check-which-line-of-a-bash-script-is-being-executed
 | 
| 152 |   PS4='+${LINENO}: '
 | 
| 153 | 
 | 
| 154 |   # Test runtime errors like this
 | 
| 155 |   #PS4='+${LINENO}: $(( 1 / 0 ))'
 | 
| 156 | 
 | 
| 157 |   myfunc ps4
 | 
| 158 |   foo=$(myfunc ps4-commandsub)
 | 
| 159 |   echo foo
 | 
| 160 | }
 | 
| 161 | 
 | 
| 162 | slowfunc() {
 | 
| 163 |   for i in "$@"; do
 | 
| 164 |     sleep 0.$i
 | 
| 165 |   done
 | 
| 166 | }
 | 
| 167 | 
 | 
| 168 | concurrency() {
 | 
| 169 |   set -x
 | 
| 170 | 
 | 
| 171 |   # PID prefix would be nice here
 | 
| 172 |   slowfunc 1 3 | slowfunc 2 4 5 | slowfunc 6
 | 
| 173 | }
 | 
| 174 | 
 | 
| 175 | task() {
 | 
| 176 |   for i in "$@"; do
 | 
| 177 |     echo $i
 | 
| 178 |     sleep 0.$i
 | 
| 179 |   done
 | 
| 180 | }
 | 
| 181 | 
 | 
| 182 | through_xargs() {
 | 
| 183 |   set -x
 | 
| 184 |   export PS4='+ $$ '
 | 
| 185 | 
 | 
| 186 |   # This doesn't work because xargs invokes $0!  Not OSH.
 | 
| 187 |   export OILS_HIJACK_SHEBANG=1
 | 
| 188 | 
 | 
| 189 |   # This makes us trace through xargs.
 | 
| 190 |   #
 | 
| 191 |   # problem: $0 invokes bash because of the shebang.
 | 
| 192 |   # We can't use $SHELL $0.
 | 
| 193 |   # - bash is the only shell that sets $SHELL.
 | 
| 194 |   # - It's not the right value.  "If it is not set when the shell starts, bash
 | 
| 195 |   #   assigns to it the full pathname of the current user's login shell."
 | 
| 196 | 
 | 
| 197 |   export SHELLOPTS
 | 
| 198 |   seq 6 | xargs -n 2 -P 3 -- $0 task
 | 
| 199 | }
 | 
| 200 | 
 | 
| 201 | my_ps4() {
 | 
| 202 |   for i in {1..3}; do
 | 
| 203 |     echo -n $i
 | 
| 204 |   done
 | 
| 205 | }
 | 
| 206 | 
 | 
| 207 | # The problem with this is you don't want to fork the shell for every line!
 | 
| 208 | 
 | 
| 209 | call_func_in_ps4() {
 | 
| 210 |   set -x
 | 
| 211 |   PS4='[$(my-ps4)] '
 | 
| 212 |   echo one
 | 
| 213 |   echo two
 | 
| 214 | }
 | 
| 215 | 
 | 
| 216 | # EXPANDED argv is displayed, NOT the raw input.
 | 
| 217 | # - OK just do assignments?
 | 
| 218 | 
 | 
| 219 | # - bash shows the 'for x in 1 2 3' all on one line
 | 
| 220 | # - dash doesn't show the 'for'
 | 
| 221 | # - neither does zsh and mksh
 | 
| 222 | #   - zsh shows line numbers and the function name!
 | 
| 223 | 
 | 
| 224 | # - two statements on one line are broken up
 | 
| 225 | 
 | 
| 226 | # - bash doesn't show 'while'
 | 
| 227 | 
 | 
| 228 | # The $((i+1)) is evaluated.  Hm.
 | 
| 229 | 
 | 
| 230 | # Hm we don't implement this, only works at top level
 | 
| 231 | # set -v
 | 
| 232 | 
 | 
| 233 | loop() {
 | 
| 234 |   set -x
 | 
| 235 | 
 | 
| 236 |   for x in 1 \
 | 
| 237 |     2 \
 | 
| 238 |     3; do
 | 
| 239 |     echo $x; echo =$(echo {x}-)
 | 
| 240 |   done
 | 
| 241 | 
 | 
| 242 |   i=0
 | 
| 243 |   while test $i -lt 3; do
 | 
| 244 |     echo $x; echo ${x}-
 | 
| 245 |     i=$((i+1))
 | 
| 246 |     if true; then continue; fi
 | 
| 247 |   done
 | 
| 248 | }
 | 
| 249 | 
 | 
| 250 | atoms1() {
 | 
| 251 |   set -x
 | 
| 252 | 
 | 
| 253 |   foo=bar
 | 
| 254 | 
 | 
| 255 |   # This messes up a lot of printing.
 | 
| 256 |   x='one
 | 
| 257 |   two'
 | 
| 258 | 
 | 
| 259 |   i=1
 | 
| 260 | 
 | 
| 261 |   [[ -n $x ]]; echo "$x"
 | 
| 262 | 
 | 
| 263 |   # $i gets expanded, not i
 | 
| 264 |   (( y = 42 + i + $i )); echo yo
 | 
| 265 | 
 | 
| 266 |   [[ -n $x
 | 
| 267 |   ]]
 | 
| 268 | 
 | 
| 269 |   (( y =
 | 
| 270 |      42 +
 | 
| 271 |      i +
 | 
| 272 |      $i
 | 
| 273 |   ))
 | 
| 274 | }
 | 
| 275 | 
 | 
| 276 | atoms2() {
 | 
| 277 |   set -x
 | 
| 278 | 
 | 
| 279 |   x='one
 | 
| 280 |   two'
 | 
| 281 | 
 | 
| 282 |   declare -a a
 | 
| 283 |   a[1]="$x"
 | 
| 284 | 
 | 
| 285 |   # This works
 | 
| 286 |   declare -A A
 | 
| 287 |   A["$x"]=1
 | 
| 288 | 
 | 
| 289 |   a=(1 2 3)
 | 
| 290 |   A=([k]=v)
 | 
| 291 | 
 | 
| 292 |   a=("$x" $x)
 | 
| 293 |   A=([k]="$x")
 | 
| 294 | 
 | 
| 295 |   # Assignment builtins
 | 
| 296 | 
 | 
| 297 |   declare -g -r d=0 foo=bar
 | 
| 298 |   typeset t=1
 | 
| 299 |   local lo=2
 | 
| 300 |   export e=3 f=foo
 | 
| 301 |   readonly r=4
 | 
| 302 | }
 | 
| 303 | 
 | 
| 304 | compound() {
 | 
| 305 |   set -x
 | 
| 306 | 
 | 
| 307 |   # Nothing for time
 | 
| 308 |   time sleep 0
 | 
| 309 | 
 | 
| 310 |   # There is no tracing for () and {}
 | 
| 311 |   { echo b1
 | 
| 312 |     echo b2
 | 
| 313 |   }
 | 
| 314 | 
 | 
| 315 |   ( echo c1
 | 
| 316 |     echo c2
 | 
| 317 |   )
 | 
| 318 | 
 | 
| 319 |   # no tracing for if; just the conditions
 | 
| 320 |   if test -d /; then
 | 
| 321 |     echo yes
 | 
| 322 |   else
 | 
| 323 |     echo no
 | 
| 324 |   fi
 | 
| 325 | 
 | 
| 326 |   # Hm this causes a concurrency problem.
 | 
| 327 |   # I think we want to buffer the line
 | 
| 328 |   ls | wc -l | sort
 | 
| 329 | 
 | 
| 330 |   # There IS tracing for 'case' line
 | 
| 331 |   case foo in
 | 
| 332 |     fo*)
 | 
| 333 |       echo case
 | 
| 334 |       ;;
 | 
| 335 |     *)
 | 
| 336 |       echo default
 | 
| 337 |       ;;
 | 
| 338 |   esac
 | 
| 339 | 
 | 
| 340 |   f() {
 | 
| 341 |     echo hi
 | 
| 342 |   }
 | 
| 343 | 
 | 
| 344 | }
 | 
| 345 | 
 | 
| 346 | oil_constructs() {
 | 
| 347 |   echo TODO
 | 
| 348 |   # BareDecl, VarDecl, PlaceMutation, Expr
 | 
| 349 | }
 | 
| 350 | 
 | 
| 351 | details() {
 | 
| 352 |   PS4='+ ${BASH_SOURCE[0]}:${FUNCNAME[0]}:${LINENO} '
 | 
| 353 | 
 | 
| 354 |   # X_pid can have a space after it, or call it ${X_tag}
 | 
| 355 |   # Or should we could call the whole thing ? ${XTRACE_PREFIX} ?
 | 
| 356 |   # Idea: $PS5 and $PS6 for push and pop?
 | 
| 357 | 
 | 
| 358 |   # Problem: remove the first char behavior?  Or only respect it in PS4.
 | 
| 359 |   #
 | 
| 360 |   # This string can be OIL_XTRACE_PREFIX='' or something.
 | 
| 361 |   PS4=' ${X_indent}${X_punct}${X_tag}${BASH_SOURCE[0]}:${FUNCNAME[0]}:${LINENO} '
 | 
| 362 | 
 | 
| 363 | 
 | 
| 364 |   set -x
 | 
| 365 | 
 | 
| 366 |   echo hi
 | 
| 367 |   echo command=$(echo inner)
 | 
| 368 |   eval 'echo eval'
 | 
| 369 | }
 | 
| 370 | 
 | 
| 371 | "$@"
 |