Asked  7 Months ago    Answers:  5   Viewed   39 times

I have a line of code that works fine in my terminal:

for i in *.mp4; do echo ffmpeg -i "$i" "${i/.mp4/.mp3}"; done

Then I put the exact same line of code in a script myscript.sh:

#!/bin/sh
for i in *.mp4; do echo ffmpeg -i "$i" "${i/.mp4/.mp3}"; done

However, now I get an error when running it:

$ sh myscript.sh
myscript.sh: 2: myscript.sh: Bad substitution

Based on other questions I tried changing the shebang to #!/bin/bash, but I get the exact same error. Why can't I run this script?

 Answers

72

TL;DR: Since you are using bash specific features, your script has to run with bash and not with sh:

$ sh myscript.sh
myscript.sh: 2: myscript.sh: Bad substitution

$ bash myscript.sh
ffmpeg -i bar.mp4 bar.mp3
ffmpeg -i foo.mp4 foo.mp3

See Difference between sh and bash. To find out which sh you are using: readlink -f $(which sh).

The best way to ensure a bash specific script always runs correctly

The best practices are to both:

  1. Replace #!/bin/sh with #!/bin/bash (or whichever other shell your script depends on).
  2. Run this script (and all others!) with ./myscript.sh or /path/to/myscript.sh, without a leading sh or bash.

Here's an example:

$ cat myscript.sh
#!/bin/bash
for i in *.mp4
do
  echo ffmpeg -i "$i" "${i/.mp4/.mp3}"
done

$ chmod +x myscript.sh   # Ensure script is executable

$ ./myscript.sh
ffmpeg -i bar.mp4 bar.mp3
ffmpeg -i foo.mp4 foo.mp3

(Related: Why ./ in front of scripts?)

The meaning of #!/bin/sh

The shebang suggests which shell the system should use to run a script. This allows you to specify #!/usr/bin/python or #!/bin/bash so that you don't have to remember which script is written in what language.

People use #!/bin/sh when they only use a limited set of features (defined by the POSIX standard) for maximum portability. #!/bin/bash is perfectly fine for user scripts that take advantage of useful bash extensions.

/bin/sh is usually symlinked to either a minimal POSIX compliant shell or to a standard shell (e.g. bash). Even in the latter case, #!/bin/sh may fail because bash wil run in compatibility mode as explained in the manpage:

If bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible, while conforming to the POSIX standard as well.

The meaning of sh myscript.sh

The shebang is only used when you run ./myscript.sh, /path/to/myscript.sh, or when you drop the extension, put the script in a directory in your $PATH, and just run myscript.

If you explicitly specify an interpreter, that interpreter will be used. sh myscript.sh will force it to run with sh, no matter what the shebang says. This is why changing the shebang is not enough by itself.

You should always run the script with its preferred interpreter, so prefer ./myscript.sh or similar whenever you execute any script.

Other suggested changes to your script:

  • It is considered good practice to quote variables ("$i" instead of $i). Quoted variables will prevent problems if the stored file name contains white space characters.
  • I like that you use advanced parameter expansion. I suggest to use "${i%.mp4}.mp3" (instead of "${i/.mp4/.mp3}"), since ${parameter%word} only substitutes at the end (for example a file named foo.mp4.backup).
Tuesday, June 1, 2021
 
makadev
answered 7 Months ago
66

In bash, a process substitution substitution command foo > >(bar) finishes as soon as foo finishes. (This is not discussed in the documentation.) You can check this with

: > >(sleep 1; echo a)

This command returns immediately, then prints a asynchronously one second later.

In your case, the tee command takes just one little bit of time to finish after command completes. Adding sync gave tee enough time to complete, but this doesn't remove the race condition, any more than adding a sleep would, it just makes the race more unlikely to manifest.

More generally, sync does not have any internally observable effect: it only makes a difference if you want to access device where your filesystems are stored under a different operating system instance. In clearer terms, if your system loses power, only data written before the last sync is guaranteed to be available after you reboot.

As for removing the race condition, here are a few of possible approaches:

  • Explicitly synchronize all substituted processes.

    mkfifo sync.pipe
    command > >(tee -- "$stdoutF"; echo >sync.pipe)
           2> >(tee -- "$stderrF"; echo >sync.pipe)
    read line < sync.pipe; read line < sync.pipe
    
  • Use a different temporary file name for each command instead of reusing $stdoutF and $stderrF, and enforce that the temporary file is always newly created.

  • Give up on process substitution and use pipes instead.

    { { command | tee -- "$stdoutF" 1>&3; } 2>&1 
                | tee -- "$stderrF" 1>&2; } 3>&1
    

    If you need the command's return status, bash puts it in ${PIPESTATUS[0]}.

    { { command | tee -- "$stdoutF" 1>&3; exit ${PIPESTATUS[0]}; } 2>&1 
                | tee -- "$stderrF" 1>&2; } 3>&1
    if [ ${PIPESTATUS[0]} -ne 0 ]; then echo command failed; fi
    
Thursday, July 29, 2021
 
rblarsen
answered 5 Months ago
23

Do exactly what the recipes app does.

If you try it in initWithCoder, you don't know if the app delegate has finished initialization (which it hasn't)

If you try it viewDidLoad, you have a similar problem.

That is why you should NOT be accessing the app delegate like so:

MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = [[appDelegate managedObjectContext] retain];

This is bad form. It introduces coupling into your design. Use dependency injection, just like the example. It makes your app more flexible.

Because from the app delegate you know exactly what initialization has been performed and can pass in the context at the appropriate time.


Update:

The issue is that your View Controller instance is likely being instantiated in the Mainwindow.xib. Mainwindow.xib (and any other nibs it references) is "defrosted" before the app delegate receives UIApplicationDidFinishLaunchingNotification notification.

The order in which objects are defrosted from nibs is not guaranteed. When initWithCoder: is called on your View Controller you have no idea what other objects have been defrosted from the nib. You also can't be sure whether the app delegate has received the UIApplicationDidFinishLaunchingNotification notification.

It is similar for viewDidLoad. In viewDidLoad, you can be sure that all other objects in the nib have been properly defrosted and initialized, but since the configuration of the app delegate happens outside of the nib file, you can't be sure whether it is safe to call the app delegate.

It is better just to have the app delegate pass in the context when it is "good and ready", preferably in the applicationDidFinishLaunching: method.

Hope that is a little clearer, you should take a look at the iphone programming guide: http://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/index.html

To glean a better explanation of the iPhone application life cycle.

Hope that helps.


One More Update:

In depth discussion of the iphone launch sequence: http://www.bit-101.com/blog/?p=2159

Thursday, August 26, 2021
 
brombeer
answered 4 Months ago
26

Try this:

delete MyLog log
where log.id in
          (select l.id
           from MyLog l
           where l.UtcTimestamp < :threshold and
           and.Configuration.Application = :application)
Wednesday, October 20, 2021
 
Evan Anger
answered 2 Months ago
38

You must use the eval command:

num_active_files=`eval $var`

This allows you to generate an expression for bash to run dynamically.

Hope this helps =)

Sunday, December 5, 2021
 
Pawan Pillai
answered 4 Days ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share