Menu

Error message "sed: -i may not be used with stdin" when executing the refind-install script.

2020-10-02
2021-01-25
  • David Anderson

    David Anderson - 2020-10-02

    I am trying to install rEFInd to a FAT formatted partition on a 2018 mac mini. The version of rEFInd is 0.12.0. The output from the command diskutil list disk0 is given below.

    /dev/disk0 (internal, physical):
       #:                       TYPE NAME                    SIZE       IDENTIFIER
       0:      GUID_partition_scheme                        *251.0 GB   disk0
       1:                        EFI EFI                     314.6 MB   disk0s1
       2:                 Apple_APFS Container disk1         200.0 GB   disk0s2
       3:       Microsoft Basic Data BOOTCAMP                40.6 GB    disk0s3
       4:       Microsoft Basic Data REFIND                  103.8 MB   disk0s4
    

    The command I entered was sudo ./refind-install --usedefault /dev/disk0s4. The error messsage was sed: -i may not be used with stdin. I believe the line in the script causing this messsage is given below.

    sed -i 's/#showtools shell/showtools install, shell/g' "$InstallDir/$TargetDir/refind.conf"

    I made the following correction to get the script to work.

    sed -i '' 's/#showtools shell/showtools install, shell/g' "$InstallDir/$TargetDir/refind.conf"

     

    Last edit: David Anderson 2020-10-02
  • joevt

    joevt - 2020-10-02

    That seems like the correct fix. The fixed code is like this:

       if [[ "$TargetDir" == '/EFI/BOOT' ]] ; then
          cp -f "$ConfFile" "$InstallDir/$TargetDir/refind.conf-sample"
          sed -i '' 's/#showtools shell/showtools install, shell/g' "$InstallDir/$TargetDir/refind.conf"
       fi
    

    The result is like this:

    list of files before copy:«««
    /tmp/refind_install//EFI/BOOT/refind.conf
    /tmp/refind_install//EFI/BOOT/refind_aa64.efi
    »»»
    ConfFile = /Volumes/Updates/Misc_Software/rEFIt/rEFInd 0.12.0/refind-bin-0.12-2.0/refind/refind.conf-sample
    list of files after copy:«««
    /tmp/refind_install//EFI/BOOT/refind.conf
    /tmp/refind_install//EFI/BOOT/refind.conf-sample
    /tmp/refind_install//EFI/BOOT/refind_aa64.efi
    »»»
    list of files after sed:«««
    /tmp/refind_install//EFI/BOOT/refind.conf
    /tmp/refind_install//EFI/BOOT/refind.conf-sample
    /tmp/refind_install//EFI/BOOT/refind_aa64.efi
    »»»
    
    diff /tmp/refind_install//EFI/BOOT/refind.conf*
    263c263
    < showtools install, shell, bootorder, gdisk, memtest, mok_tool, apple_recovery, windows_recovery, about, hidden_tags, reboot, exit, firmware, fwupdate
    ---
    > #showtools shell, bootorder, gdisk, memtest, mok_tool, apple_recovery, windows_recovery, about, hidden_tags, reboot, exit, firmware, fwupdate
    

    Another change I would make is to allow the EFI partition to already be mounted or not. The code code check if it was mounted, and mount it if it wasn't. InstallDir would point to the mounted location. Here's a function I made to mount a partition and/or get it's mount point location (works in macOS - needs minor changes for Linux):

    # mount a partition
    mountpartition() {
        local mounttype=$1
        local slice=$2
        local volume=$3
        local mountpoint=$(mount | sed -n -E "/\/dev\/$slice on (.*) \(.*/s//\1/p")
        if [ -z $mountpoint ]; then
            i=0
            local startmountpoint="/Volumes/$volume"
            mountpoint="$startmountpoint"
            while [ -d "$mountpoint" ]; do
                ((i++))
                mountpoint="$startmountpoint$i"
            done
        fi
        if [ ! -d "$mountpoint" ]; then
            sudo mkdir "$mountpoint" 2> /dev/null
            sudo mount$mounttype "/dev/$slice" "$mountpoint"
        fi
        echo -n "$mountpoint"
    }
    

    Then you would call it like this:

    mountpoint=$(mountpartition "_msdos" "$slice" "$volume")
    

    where $slice is disk0s4 in your case. $volume would be whatever you like (refind_install in the case of the refind script). Maybe replace the part of the code that looks for an unused name in /Volumes with a temp folder path guaranteed to be unused (use mktemp -d).

     
  • dakanji

    dakanji - 2020-10-02

    The script is not wrong.
    sed -i 'XYZ' is a standard form for sed as implemented on Linux.
    Apple implemented the "non-standard" sed -i '' 'XYZ' form on Macs ... which it seems you are using.

     

    Last edit: dakanji 2020-10-02
  • joevt

    joevt - 2020-10-02

    If the -i argument works differently in Linux and macOS, then it needs to be not used, or two different commands need to be included.

          case "$OSTYPE" in
             darwin*)
                sed -i '' 's/#showtools shell/showtools install, shell/g' "$InstallDir/$TargetDir/refind.conf"
                ;;
             linux*)
                sed -i 's/#showtools shell/showtools install, shell/g' "$InstallDir/$TargetDir/refind.conf"
                ;;
          esac
    
     
  • David Anderson

    David Anderson - 2020-10-03

    I encountered a problem where if I ran the command sudo ./refind-install --usedefault /dev/disk0s4 again after removing install from showtools. Here a message is displayed stating refind.conf will not be changed, but is in fact changed. In othe words, install is again added to refind.conf. The part of the script where the problem exists is given below.

       if [[ -f "$InstallDir/$TargetDir/refind.conf" ]] ; then
          echo "Existing refind.conf file found; copying sample file as refind.conf-sample"
          echo "to avoid overwriting your customizations."
          echo ""
          cp -f "$ConfFile" "$InstallDir/$TargetDir"
          if [[ $? != 0 ]] ; then
             Problems=1
          fi
       else
          echo "Copying sample configuration file as refind.conf; edit this file to configure"
          echo "rEFInd."
          echo ""
          cp -f "$ConfFile" "$InstallDir/$TargetDir/refind.conf"
          if [[ $? != 0 ]] ; then
             Problems=1
          fi
       fi
       # If installing to the fallback/EFI default filename, make a duplicate copy
       # of refind.conf and edit the main copy to include the "install" and "bootorder"
       # options, so that the system can be used to install rEFInd if it's on a USB
       # flash drive or CD-R....
       if [[ "$TargetDir" == '/EFI/BOOT' ]] ; then
          cp -f "$ConfFile" "$InstallDir/$TargetDir/refind.conf-sample"
          sed -i 's/#showtools shell/showtools install, shell/g' "$InstallDir/$TargetDir/refind.conf"
       fi
    

    I suggest the following replacement. This replacement also solves the problem with the -i option by elimiating the option from the sed command.

       # If installing to the fallback/EFI default filename, make a duplicate copy
       # of refind.conf and edit the main copy to include the "install" and "bootorder"
       # options, so that the system can be used to install rEFInd if it's on a USB
       # flash drive or CD-R....
       local DestConfFile="$InstallDir/$TargetDir/refind.conf"
       if [[ -f "$InstallDir/$TargetDir/refind.conf" ]] ; then
          if [[ "$TargetDir" == '/EFI/BOOT' ]] ; then
             echo "Existing refind.conf file found; leaving this file unchanged."
             echo "Copying sample file as refind.conf-sample. This file is"
             echo "different from the refind.conf file that would have been"
             echo "created, if refind.conf did not already exist."
             echo ""
             DestConfFile="$InstallDir/$TargetDir/refind.conf-sample"
          else
             echo "Existing refind.conf file found; copying sample file as refind.conf-sample"
             echo "to avoid overwriting your customizations."
             echo ""
             DestConfFile="$InstallDir/$TargetDir"
          fi
       else
          if [[ "$TargetDir" == '/EFI/BOOT' ]] ; then
             echo "Copying sample configuration file as refind.conf; edit this file to configure"
             echo "rEFInd. Copying sample file as refind.conf-sample. This file is different"
             echo "from the refind.conf file."
             echo ""
             rm -f "$DestConfFile" && sed 's/#showtools shell/showtools install, shell/g' "$ConfFile" > "$DestConfFile"
             if [[ $? != 0 ]] ; then
                Problems=1
             fi
             DestConfFile="$InstallDir/$TargetDir/refind.conf-sample"
          else
             echo "Copying sample configuration file as refind.conf; edit this file to configure"
             echo "rEFInd."
             echo ""
          fi
       fi
       cp -f "$ConfFile" "$DestConfFile"
       if [[ $? != 0 ]] ; then
          Problems=1
       fi
    
     

    Last edit: David Anderson 2020-10-03
  • David Anderson

    David Anderson - 2020-10-03

    I encounter a problem where if I ran the command sudo ./refind-install --usedefault /dev/disk0s4 again after removing install from showtools. Here a message is displayed stating refind.conf will not be changed, but is in fact changed. In othe words, install is again added to refind.conf. The part of the script where the problem exist is given below.

       if [[ -f "$InstallDir/$TargetDir/refind.conf" ]] ; then
          echo "Existing refind.conf file found; copying sample file as refind.conf-sample"
          echo "to avoid overwriting your customizations."
          echo ""
          cp -f "$ConfFile" "$InstallDir/$TargetDir"
          if [[ $? != 0 ]] ; then
             Problems=1
          fi
       else
          echo "Copying sample configuration file as refind.conf; edit this file to configure"
          echo "rEFInd."
          echo ""
          cp -f "$ConfFile" "$InstallDir/$TargetDir/refind.conf"
          if [[ $? != 0 ]] ; then
             Problems=1
          fi
       fi
       # If installing to the fallback/EFI default filename, make a duplicate copy
       # of refind.conf and edit the main copy to include the "install" and "bootorder"
       # options, so that the system can be used to install rEFInd if it's on a USB
       # flash drive or CD-R....
       if [[ "$TargetDir" == '/EFI/BOOT' ]] ; then
          cp -f "$ConfFile" "$InstallDir/$TargetDir/refind.conf-sample"
          sed -i 's/#showtools shell/showtools install, shell/g' "$InstallDir/$TargetDir/refind.conf"
       fi
    

    I suggest the following replacement. This replacement also solves the problem with the -i argument.

       # If installing to the fallback/EFI default filename, make a duplicate copy
       # of refind.conf and edit the main copy to include the "install" and "bootorder"
       # options, so that the system can be used to install rEFInd if it's on a USB
       # flash drive or CD-R....
       local DestConfFile="$InstallDir/$TargetDir/refind.conf"
       if [[ -f "$InstallDir/$TargetDir/refind.conf" ]] ; then
          if [[ "$TargetDir" == '/EFI/BOOT' ]] ; then
             echo "Existing refind.conf file found; leaving this file unchanged."
             echo "Copying sample file as refind.conf-sample. This file is"
             echo "different from the refind.conf file that would have been"
             echo "created, if refind.conf did not already exist."
             echo ""
             DestConfFile="$InstallDir/$TargetDir/refind.conf-sample"
          else
             echo "Existing refind.conf file found; copying sample file as refind.conf-sample"
             echo "to avoid overwriting your customizations."
             echo ""
             DestConfFile="$InstallDir/$TargetDir"
          fi
       else
          if [[ "$TargetDir" == '/EFI/BOOT' ]] ; then
             echo "Copying sample configuration file as refind.conf; edit this file to configure"
             echo "rEFInd. Copying sample file as refind.conf-sample. This file is different"
             echo "from the refind.conf file."
             echo ""
             rm -f "$DestConfFile" && sed 's/#showtools shell/showtools install, shell/g' "$ConfFile" > "$DestConfFile"
             if [[ $? != 0 ]] ; then
                Problems=1
             fi
             DestConfFile="$InstallDir/$TargetDir/refind.conf-sample"
          else
             echo "Copying sample configuration file as refind.conf; edit this file to configure"
             echo "rEFInd."
             echo ""
          fi
       fi
       cp -f "$ConfFile" "$DestConfFile"
       if [[ $? != 0 ]] ; then
          Problems=1
       fi
    
     
  • David Anderson

    David Anderson - 2020-10-05

    Joevt: I looked an your script to mount a partition. I would not make the mount point in the /Volumes folder. Instead, I would keep using the /tmp/refind_install mount point, when a volumn needs to be mounted. I am not sure why you felt the need to loop until a directory does not exist. Perhaps this is because your script uses the /Volumes folder?

    When using the --usedefault option, I found refind-install allows installations to mounted volumes after making the following changes.

    First, I made the change suggested in my previous post.

    Next, I replaced the following function in refind_install.

    # Mount the partition the user specified with the --usedefault or --ownhfs option
    MountDefaultTarget() {
       InstallDir=/tmp/refind_install
       mkdir -p "$InstallDir"
       UnmountEsp=1
       if [[ $OSTYPE == darwin* ]] ; then
          if [[ $OwnHfs == '1' ]] ; then
             Temp=`diskutil info "$TargetPart" | grep "Mount Point"`
             InstallDir=`echo $Temp | cut -f 3-30 -d ' ' | sed 's/\/\+/\//g'`
             if [[ $InstallDir == '' ]] ; then
                InstallDir=/tmp/refind_install
                mount -t hfs "$TargetPart" "$InstallDir"
             else
                UnmountEsp=0
             fi
          else
             mount -t msdos "$TargetPart" "$InstallDir"
          fi
       elif [[ $OSTYPE == linux* ]] ; then
          mount -t vfat "$TargetPart" "$InstallDir"
       fi
       if [[ $? != 0 ]] ; then
          echo "Couldn't mount $TargetPart ! Aborting!"
          rmdir "$InstallDir"
          exit 1
       fi
    } # MountDefaultTarget()
    

    The new replacement function is given below.

    # Mount the partition the user specified with the --usedefault or --ownhfs option
    MountDefaultTarget() {
       local FileSystemType="" Command=""
       local EscapedTargetPart="${TargetPart//\//\\/}"
       if [[ $OSTYPE == darwin* ]] ; then
          Command="/^$EscapedTargetPart on (.+) \(.+/s//\1/p"
          if [[ $OwnHfs == '1' ]] ; then
             FileSystemType="hfs"
          else
             FileSystemType="msdos"
          fi
       elif [[ $OSTYPE == linux* ]] ; then
          Command="/^$EscapedTargetPart on (.+) type .+ \(.+/s//\1/p"
          FileSystemType="vfat"
       fi
       InstallDir="$(mount|sed -n -E "$Command")"
       test -n "$InstallDir"
       UnmountEsp="$?"
       if [[ $UnmountEsp == '1' ]]; then
          InstallDir="/tmp/refind_install"
          mkdir -p "$InstallDir"
          mount -t "$FileSystemType" "$TargetPart" "$InstallDir"
          if [[ $? != 0 ]] ; then
             echo "Couldn't mount $TargetPart ! Aborting!"
             rmdir "$InstallDir"
             exit 1
          fi
       fi
    } # MountDefaultTarget()
    

    Finally, I removed the exit command from the CreateBootCsvFile funtion. Can you explain why this exit command is needed? This would appear to be a bug.

     

    Last edit: David Anderson 2020-10-05
    • joevt

      joevt - 2020-10-07

      I have multiple EFI partitions so I have to loop to make sure my mount point name is unique. See mountEFIpartitions at https://gist.github.com/joevt/6d7a0ede45106345a39bdfa0ac10ffd6

      I could have used the sudo diskutil mount command instead. My command is more portable (requires some modification for Linux). diskutil mount will do what my command does - check if the device is already mounted, and if not, then mount to a new mount point. My command returns "EFI", "EFI1", "EFI2", etc. diskutil will do "EFI", "EFI 1", "EFI 2", etc. I use /Volumes so the mount point appears in the Finder desktop automatically.

       
  • David Anderson

    David Anderson - 2020-10-05

    Joevt: I looked an your script to mount a partition. I would not make the mount point in the /Volumes folder. Instead, I would keep using the /tmp/refind_install mount point, when a volumn needs to be mounted. I am not sure why you felt the need to loop until a directory does not exist. Perhaps this is because your script uses the /Volumes folder?

    When using the --usedefault option, I found refind-install allows installations to mounted volumes after making the following changes.

    First, I replaced the following function in refind_install.

    # Mount the partition the user specified with the --usedefault or --ownhfs option
    MountDefaultTarget() {
       InstallDir=/tmp/refind_install
       mkdir -p "$InstallDir"
       UnmountEsp=1
       if [[ $OSTYPE == darwin* ]] ; then
          if [[ $OwnHfs == '1' ]] ; then
             Temp=`diskutil info "$TargetPart" | grep "Mount Point"`
             InstallDir=`echo $Temp | cut -f 3-30 -d ' ' | sed 's/\/\+/\//g'`
             if [[ $InstallDir == '' ]] ; then
                InstallDir=/tmp/refind_install
                mount -t hfs "$TargetPart" "$InstallDir"
             else
                UnmountEsp=0
             fi
          else
             mount -t msdos "$TargetPart" "$InstallDir"
          fi
       elif [[ $OSTYPE == linux* ]] ; then
          mount -t vfat "$TargetPart" "$InstallDir"
       fi
       if [[ $? != 0 ]] ; then
          echo "Couldn't mount $TargetPart ! Aborting!"
          rmdir "$InstallDir"
          exit 1
       fi
    } # MountDefaultTarget()
    

    The new replacement funciton is given below.

    # Mount the partition the user specified with the --usedefault or --ownhfs option
    MountDefaultTarget() {
       local FileSystemType="" Command=""
       local EscapedTargetPart="${TargetPart//\//\\/}"
       if [[ $OSTYPE == darwin* ]] ; then
          Command="/^$EscapedTargetPart on (.+) \(.+/s//\1/p"
          if [[ $OwnHfs == '1' ]] ; then
             FileSystemType="hfs"
          else
             FileSystemType="msdos"
          fi
       elif [[ $OSTYPE == linux* ]] ; then
          Command="/^$EscapedTargetPart on (.+) type .+ \(.+/s//\1/p"
          FileSystemType="vfat"
       fi
       InstallDir="$(mount|sed -n -E "$Command")"
       test -n "$InstallDir"
       UnmountEsp="$?"
       if [[ $UnmountEsp == '1' ]]; then
          InstallDir="/tmp/refind_install"
          mkdir -p "$InstallDir"
          mount -t "$FileSystemType" "$TargetPart" "$InstallDir"
          if [[ $? != 0 ]] ; then
             echo "Couldn't mount $TargetPart ! Aborting!"
             rmdir "$InstallDir"
             exit 1
          fi
       fi
    } # MountDefaultTarget()
    

    Next, I removed the exit command from the CreateBootCsvFile funtion. Can you explain why this exit command is needed? This would appear to be a bug.

     
  • David Anderson

    David Anderson - 2020-10-07

    joevt: Your script has many flaws. Below I outline one main flaw with the mountpartition funciton.

    The following line determines if the mount point exists as a directory.

    while [ -d "$mountpoint" ]; do
    

    Later, the following line creates the directory.

    sudo mkdir "$mountpoint" 2> /dev/null
    

    Finally, the next line mounts the volume.

    sudo mount$mounttype "/dev/$slice" "$mountpoint"
    

    The problem is the script uses the /Volumes directory. There is no guarantee some other process will not also be accessing the /Volumes directory. In other words, the mountpartition function is not atomic. The diskutil command can do this same operation atomically. However, the diskutil command itself is may not be a solution if you want your script to be compatible with older versions of OS X. For example, I can not find a way to use the diskutil command to mount an EFI partition when booted to Snow Leopard (OS X 10.6.8). I can if using the mount command.

    My mac mini has multiple users. Other users are mounting and unmounting independently of what I am doing.

    You posted the following :

    I use /Volumes so the mount point appears in the Finder desktop automatically.

    I am not sure why you made this statement. Perhaps you are using a older version of macOS (OS X). When using High Sierra (macOS 10.13.6) and newer versions of macOS, volumes mounted by using the mount command appear in the Finder automatically regardless of the mount point.

    BTW: The variable i in your mountpartition function should be declared local. Also, replace echo -n "$mountpoint" with echo "$mountpoint".

     

    Last edit: David Anderson 2020-10-07
    • joevt

      joevt - 2020-10-09

      You make valid points.

      My script assumes that volumes are not being randomly mounted and unmounted without user interaction (connecting/disconnecting a USB drive, mounting/unmounting a disk image, running an installer script, etc.).

      Here is a slightly better implementation (still uses /Volumes for ...reasons...):

      mountpartition() {
          local mounttype=$1
          local slice=$2
          local volume=$3
          local mountpoint=""
          mountpoint=$(mount | sed -n -E "/\/dev\/$slice on (.*) \(.*/s//\1/p")
          if [[ -n "$mountpoint" ]]; then
              echo -n "$mountpoint"
              return 0
          fi
          local i=0
          local startmountpoint="/Volumes/$volume"
          mountpoint="$startmountpoint"
          while ((1)); do
              sudo mkdir "$mountpoint" 2> /dev/null && break
              if ((i++ > 1000)); then
                  echo "Could not create mountpoint" 1>&2
                  return 1
              fi
              mountpoint="$startmountpoint$i"
          done
          sudo "mount$mounttype" "/dev/$slice" "$mountpoint" && echo -n "$mountpoint"
          return $?
      }
      

      It assumes that if a disk is mounted, it will not be unmounted somehow.
      It assumes that if it can make the mountpoint directory then some other process is not going to use it before we can use it as a mountpoint.

      The improvement here is that we ensure that we are the ones that made the directory by checking the result of mkdir (assuming the directory wasn't deleted and recreated by someone else in the split second before we mount to it).

       
  • David Anderson

    David Anderson - 2020-10-07

    Your script has many flaws. Below I outline one main flaw.

    The following line determines if the mount point exists as a directory.

    while [ -d "$mountpoint" ]; do
    

    Later, the following line creates the directory.

    sudo mkdir "$mountpoint" 2> /dev/null
    

    Finally, the next line mounts the volume.

    sudo mount$mounttype "/dev/$slice" "$mountpoint"
    

    The problem is the script uses the /Volumes directory. There is no guarantee some other processes will not also be accessing the /Volumes directory. In other words, the mountpartition function is not atomic. The diskutil command can do this same operation atomically. However, the diskutil command itself is may not be a solution if you want your script to be compatible with older versions of OS X. For example, I can find a way to use the diskutil command to mount an EFI partition when booted to Snow Leopard (OS X 10.6.8). I can if using the mount command.

    My mac mini use has multiple users. Other users are mounting and unmounting independently of what I am doing.

    BTW: The variable i in your mountpartition function should be declared local. Also, replace echo -n "$mountpoint" with echo "$mountpoint".

     
  • David Anderson

    David Anderson - 2020-10-09

    After some thought, I now realize the mount command in the refind-install script prevents the two different users from executing this script simultaneously. We both have been discussing how to install to a volume that is already mounted. Doing so would be a bad idea. So, having to unmount before running the refind-install script is the correct proceedure.

     

    Last edit: David Anderson 2020-10-09

Log in to post a comment.