web-dev-qa-db-fra.com

J'ai besoin de ma commande sed -i pour que l'édition sur place fonctionne avec les deux GNU sed et BSD / OSX sed

J'ai un makefile (développé pour gmake sur Linux) que j'essaie de porter sur OSX, mais il semble que sed ne veuille pas coopérer. Ce que je fais, c'est utiliser GCC pour générer automatiquement des fichiers de dépendance, puis les ajuster un peu en utilisant sed. La partie pertinente du makefile:

$(OBJ_DIR)/%.d: $(SRC_DIR)/%.cpp
  $(CPPC) -MM -MD $< -o $@
  sed -i 's|\(.*\)\.o:|$(OBJ_DIR)/\1.o $(OBJ_DIR)/\1.d $(TEST_OBJ_DIR)/\1_utest.o:|' $@

Bien que cela fonctionne sans problème sous GNU/Linux, j'obtiens des erreurs comme celles-ci lorsque j'essaie de construire sur OSX:

sed: 1: "test/obj/equipmentConta ...": undefined label 'est/obj/equipmentContainer_utest.d'
sed: 1: "test/obj/dice_utest.d": undefined label 'est/obj/dice_utest.d'
sed: 1: "test/obj/color-string_u ...": undefined label 'est/obj/color-string_utest.d'

Il semblerait que sed coupe un personnage, mais je ne vois pas la solution.

56
Chris Tonkinson

OS X sed gère le -i argument différemment de la version Linux .

Vous pouvez générer une commande qui pourrait "fonctionner" pour les deux en ajoutant -e de cette façon:

#      vv
sed -i -e 's|\(.*\)\.o:|$(OBJ_DIR)/\1.o $(OBJ_DIR)/\1.d $(TEST_OBJ_DIR)/\1_utest.o:|' $@

OS X sed -i interprète la chose suivante après le -i comme extension de fichier pour une copie de sauvegarde de la modification sur place. (La version Linux ne fait cela que s'il n'y a pas d'espace entre le -i et l'extension.) Évidemment, un effet secondaire de l'utilisation de ceci est que vous obtiendrez un fichier de sauvegarde avec -e en tant qu'extension, ce que vous pourriez ne pas vouloir. Veuillez vous référer aux autres réponses à cette question pour plus de détails et des approches plus propres qui peuvent être utilisées à la place.

Le comportement que vous voyez est dû au fait que OS X sed consomme le s||| comme extension (!) interprète ensuite l'argument suivant comme une commande - dans ce cas, il commence par t, que sed reconnaît comme une branche -to-label commandant le label cible comme argument - d'où l'erreur que vous voyez.

Si vous créez un fichier testvous pouvez reproduire l'erreur:

$ sed -i 's|x|y|' test
sed: 1: "test": undefined label 'est'
64
martin clayton

En fait .. faire

sed -i -e "s/blah/blah/" files

ne fait pas ce que vous attendez sous OS X non plus. Au lieu de cela, il crée des fichiers de sauvegarde avec l'extension "-e".

Le bon pour OS est

sed -i "" -e "s/blah/blah/" files
49
Urkle

La réponse actuellement acceptée comporte deux failles très importantes.

  1. Avec BSD sed (la version OSX), l'option -e Est interprétée comme une extension de fichier et crée donc un fichier de sauvegarde avec une extension -e.

  2. Tester le noyau darwin comme suggéré n'est pas une approche fiable pour une solution multiplateforme car GNU ou BSD sed pourrait être présent sur un nombre illimité de systèmes.

Un test beaucoup plus fiable serait de simplement tester l'option --version Qui ne se trouve que dans la version GNU de sed.

sed --version >/dev/null 2>&1

Une fois que la version correcte de sed est déterminée, nous pouvons alors exécuter la commande dans sa syntaxe appropriée.

Syntaxe GNU sed pour l'option -i:

sed -i -- "$@"

Syntaxe sed BSD pour l'option -i:

sed -i "" "$@"

Enfin, mettez tout cela ensemble dans une fonction multiplateforme pour exécuter une commande d'édition sed sur place:

sedi () {
    sed --version >/dev/null 2>&1 && sed -i -- "$@" || sed -i "" "$@"
}

Exemple d'utilisation:

sedi 's/old/new/g' 'some_file.txt'

Cette solution a été testée sur OSX, Ubuntu, Freebsd, Cygwin, CentOS, Red Hat Enterprise et Msys.

35
arctelix

Ce n'est pas tout à fait une réponse à la question, mais on peut obtenir un comportement équivalent à Linux via

brew install gnu-sed

# Add to .bashrc / .zshrc
export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"

(auparavant, il y avait --with-default-names option pour brew install gnu-sed mais qui a été récemment supprimé)

14
Anthony Sottile

J'ai également rencontré ce problème et j'ai pensé à la solution suivante:

darwin=false;
case "`uname`" in
  Darwin*) darwin=true ;;
esac

if $darwin; then
  sedi="/usr/bin/sed -i ''"
else
  sedi="sed -i"
fi

$sedi 's/foo/bar/' /home/foobar/bar

fonctionne pour moi ;-), YMMV

Je travaille dans une équipe multi-OS où ppl s'appuie sur Windows, Linux et OS X. Certains utilisateurs d'OS X se sont plaints car ils ont eu une autre erreur - ils avaient le port GNU de sed installé, donc j'avais pour spécifier le chemin complet.

11
thecarpy

la réponse utile de Martin Clayton fournit une bonne explication du problème[1], mais une solution qui - comme il le dit - a un effet secondaire potentiellement indésirable.

Voici des solutions sans effets secondaires :

Attention: Résoudre le -i seul problème de syntaxe, comme ci-dessous, peut ne pas être suffisant, car il existe de nombreuses autres différences entre GNU sed et BSD/macOS sed (pour un discussion complète, voir cette réponse à moi).


Solution avec -i: Créez un fichier de sauvegarde temporairement, puis nettoyez-le:

Avec un argument d'option non vide suffixe (extension du nom de fichier du fichier de sauvegarde) (une valeur qui est pas la chaîne vide), vous peut utiliser -i d'une manière qui fonctionne avec BSD/macOS sed et GNU sed, par en ajoutant directement le suffixe au -i option.

Cela peut être utilisé pour créer un fichier de sauvegarde temporairement que vous pouvez nettoyer immédiatement:

sed -i.bak 's/foo/bar/' file && rm file.bak

Évidemment, si vous souhaitez conserver la sauvegarde, omettez simplement le && rm file.bak partie.


Solution de contournement compatible POSIX, utilisant un fichier temporaire et mv:

Si seul un fichier single doit être modifié sur place, le -i l'option peut être contournée pour éviter l'incompatibilité.

Si vous limitez votre script sed et d'autres options à fonctionnalités compatibles POSIX , ce qui suit est un entièrement portable solution (notez que -i est pas compatible POSIX ).

sed 's/foo/bar' file > /tmp/file.$$ && mv /tmp/file.$$ file
  • Cette commande écrit simplement les modifications dans un fichier temporaire et, si la commande sed réussit (&&), remplace le fichier d'origine par le fichier temporaire.

    • Si vous souhaitez conserver le fichier d'origine en tant que sauvegarde, ajoutez une autre commande mv qui renomme d'abord l'original.
  • Attention: Fondamentalement, c'est ce que -i le fait aussi, sauf qu'il essaie de conserver les autorisations et les attributs étendus (macOS) du fichier d'origine; cependant, si le fichier d'origine est un symlink, cette solution et -i remplacera le lien symbolique par un fichier normal.
    Voir la moitié inférieure de cette réponse à moi pour plus de détails sur la façon dont -i travaux.


[1] Pour une explication plus approfondie, voir cette réponse à moi.

10
mklement0

J'ai corrigé la solution publiée par @thecarpy:

Voici une solution multiplateforme appropriée pour sed -i:

sedi() {
  case $(uname) in
    Darwin*) sedi=('-i' '') ;;
    *) sedi='-i' ;;
  esac

  LC_ALL=C sed "${sedi[@]}" "$@"
}
2
niieani