69

When you run echo $14 what happens is that bash interprets the argument $14 as $1 and 4 separately. It then expands $1 (which in this case is equal to "1"), then appends the string 4 to it, which results in "14". Although that was the result you were expecting, it's actually a side effect from Bash's actual behaviour. Like @steeldriver ...


44

Yes. Just make the function run its commands in a ( ) subshell instead of a { } group command: doStuffAt() ( cd -- "$1" || exit # the subshell if cd failed. # do stuff ) The parentheses (( )) open a new subshell that will inherit the environment of its parent. The subshell will exit as soon as the commands running it it are done, ...


32

This is a bit of an XY problem but fortunately you've explained your real problem so it's possible to give a meaningful answer. Sure, there are commands that can write text to a file without relying on their environment to open the file. For example, sh can do that: pass it the arguments -c and echo text >filename. Note that this does meet the requirement ...


29

The "and a minimum number of flags" refers to flags set in the hashbang line. They'd be read when the script is started as ./somescript, and the kernel reads the hashbang line, building a new argument list from the path and options found there. But this does not happen if the script is started as bash somescript, as the kernel is asked to run bash, ...


26

Because SECONDS is an internal variable in bash. It stores the amount of time that the shell has been running. From man bash: SECONDS Each time this parameter is referenced, the number of seconds since shell invocation is returned. If a value is assigned to SECONDS, the value returned upon subsequent references is the number of seconds since the ...


26

You can access the last element with ${argv[-1]} (bash 4.2 or above) and remove it from the array with the unset builtin (bash 4.3 or above): last=${argv[-1]} unset 'argv[-1]' The quotes around argv[-1] are required as [...] is a glob operator, so argv[-1] unquoted could expand to argv- and/or argv1 if those files existed in the current directory (or to ...


25

From Lucas Werkmeister's informative answer on Server Fault: With systemd versions 231 and later, there's a JOURNAL_STREAM variable that is set for services whose stdout or stderr is connected to the journal. With systemd versions 232 and later, there's an INVOCATION_ID variable that is set. If you don't want to rely on those variables, or for systemd ...


25

In every POSIX compliant shell, $# is the number of arguments to the function or script, the number of positional parameters. $@ is the list of arguments to the function or script, the list $1, $2, etc. of positional parameters. In Bash, Ksh and Zsh, etc.: ${@:offset:n} are the n arguments starting at parameter offset, or all arguments to the end from ...


23

The only way I could think of is to check one of the SUDO_* environment variables set by sudo: #!/usr/bin/env sh if [ "$(id -u)" -eq 0 ] then if [ -n "$SUDO_USER" ] then printf "This script has to run as root (not sudo)\n" >&2 exit 1 fi printf "OK, script run as root (not sudo)\n"...


22

You're using the wrong variable s instead of age and parameter expansion works with curly braces ${...}: read -p "Enter age: " age echo "${age:0:1}"


22

You need to just enable job control in the shell with set -m #!/bin/bash set -m sleep 3 & # test sleep 1 # wait some sleep 4 & # run program under test jobs fg %1 quoting from bash manual: -m Monitor mode. Job control is enabled. This option is on by default for interactive shells on systems that support it (see JOB CONTROL above). Background ...


21

I suggest to use only read -d ' ' key. -d delim: continue until the first character of DELIM is read, rather than newline See: help read


21

#!/bin/bash sleep 3 & sleep_pid=$! sleep 1 sleep 4 & wait "$sleep_pid" Putting that sleep 3 in the foreground would be equivalent to waiting for it to finish. I'm assuming this is what you want to do (or at least that this is the effect that you're looking for), and this is what the script above does. It does this by saving the PID of ...


21

This while [ "$choice" != "yes" && "$choice" != "no" ] does actually not work, because the && breaks the [ command. Use either of: while [ "$choice" != "yes" ] && [ "$choice" != "no" ] while [[ "$choice" != "yes" && "$...


20

Handling the timing is the script's responsibility. Even if that means using /bin/sleep today, it might not in the future, so killing that isn't actually guaranteed to work long-term. Well, I guess you can make that guarantee, but it's neater not to. My point is you shouldn't kill the sleep directly from outside the script, since the sleep is an ...


19

Yes, sed -i reads and rewrites the file in full, and since the line length changes, it has to, as it moves the positions of all other lines. ...but in this case, the line length doesn't actually need to change. We can replace the hashbang line with #!/bin/sh␣␣ instead, with two trailing spaces. The OS will remove those when parsing the hashbang line. (...


19

Another option would be to check if the grandparent process name is "sudo": #!/bin/sh if [ "$(id -u)" -eq 0 ] then if [ $(ps -o comm= -p $(ps -o ppid= -p $$)) = "sudo" ] then echo Running under sudo else echo Running as root and not via sudo fi else echo Not running as root fi


18

The [[ ... ]] syntax isn't valid for /bin/sh. Try: if [ -e /usr/src/an-existing-file ] then echo "seen" >> /etc/rclocalmadethis fi Note that sometimes it works because /bin/sh -> /bin/bash or some other shell that supports that syntax, but you can't depend on that being the case (as you see here). You can run ls -l /bin/sh to get to ...


18

It depends. You can put the function in a subshell. (See also Do parentheses really put the command in a subshell?) What happens in a subshell stays in a subshell. Changes that the function makes to variables, the current directory, to redirections, to traps, and so on, do not affect the calling code. The subshell inherits all these properties from its ...


18

Since shell variables are global unless you declare them as local inside a function (a mean trap by the way), you can simply define a variable prereq_done=0 on top of the script and then modify the prereq() function to check for it at the beginning (exit if it is already set), and set it to 1 at the end: prereq() { if (( prereq_done == 1 )); then return; ...


17

You can pass multiple exec commands: find /var/www/mysite -exec chown www-data:www-data {} \; \ -type f -exec chmod 775 {} \;


17

You don't lose control over them. You're sending them (other than the last one) to the background. If you run the command chain you specify, the invocation ./script.sh 4 will be in the foreground, and the other scripts will be running in the background. Input will be sent to the foreground script. To suspend the foreground script, press CtrlZ. To send ...


17

${line#?}, a standard sh parameter expansion operator (originated in the Korn shell) simply removes the first character from the variable line. For example: $ line=abc; echo "${line#?}" bc More generally, ${variablename#word} removes word from the beginning of the contents of variablename. This is called prefix removal. word is treated as a ...


17

I don't agree conditions are easier to make with ((...)) and [[...]] (assuming that's what you're referring to; note that those operators are not specific to bash and come from ksh) than the standard [ or test command. [[ ... ]] and (( ... )) have several problems of their own¹ and which are much worse than those of [. If your [ fails with an unexpected ...


17

To clarify this, let me add add some debugging output to stderr (bypassing the pipe), before & after echoing "hop2": $ for test in test1 test2 test3; do (echo ${test}; sleep 4s; echo before hop2 >&2; echo hop2; echo after hop2 >&2; sleep 4s; echo hop3) | date; done Sat Apr 10 11:29:46 PDT 2021 before hop2 Sat Apr ...


16

Look at the command execution trace (set -x). With braces: + no_func ./a: line 3: no_func: command not found + echo 'there is nothing' there is nothing + exit 1 exit exits the (sub)shell. Since braces don't create a subshell, exit exits the main shell process, so it never reaches the point where it would run echo $?.


15

Your interactive shell is dash (masquerading as sh). The dash shell says sh: /usr/local/bin/wait-for: not found when it tries to execute a script that has a faulty #!-line pointing to an interpreter that can't be found. It happens to be exactly the same error that you would get when the command that you type can't be found, so it's easy to think it's a $...


15

How about this: foo () { local last="${!#}" local argv=("${@:1:$#-1}") echo "last: $last" echo "rest: ${argv[@]}" }


15

That comes up regularly on the Austin Group mailing list, and I'm not under the impression the Open Group would be opposed to specifying it. It just needs someone to propose something. See for instance this message from Eric Blake (Red Hat, sits on the weekly POSIX meeting) from 2011 (here copied from gmane): Date: Tue, 10 May 2011 07:13:32 -0600 From: Eric ...


15

The while and until keywords takes a command as their argument. If the command returns a zero exit-status, signalling "success", while would run the commands in the body of the loop, while until would not run the commands in the body of the loop. Your code seems to use while and until with $?, the exit-status of the most recent command, as a ...


Only top voted, non community-wiki answers of a minimum length are eligible