Canaliser la sortie d'une commande si elle réussit


14
INPUT_FILE=`ls -rt $MY_DIR/FILE.*.xml | head -1 | xargs basename`

Je voulais exécuter la deuxième commande ( head -1) uniquement si la première commande réussit. Comment puis-je améliorer cette commande?


Qu'entendez-vous par succès? lsn'échouera pas.
Matteo

2
Mais le glob peut échouer s'il n'y a pas de fichiers correspondants.
tripleee

Réponses:


8

Essaye ça:

INPUT_FILE=`ls -rt "$MY_DIR"/FILE.*.xml | head -1 | xargs -r basename`

Si vous passez xargsl' -rindicateur, il ne s'exécutera que basenames'il lit au moins un élément de l'entrée standard ( head -1).

head -1 s'exécutera mais vous ne verrez ni n'en capturerez aucune sortie.

De plus, si vous ne voulez pas que l'utilisateur voie une sortie d'erreur ls, vous pouvez rediriger lsle flux stderr vers /dev/null.

INPUT_FILE=`ls -rt "$MY_DIR"/FILE.*.xml 2> /dev/null | head -1 | xargs -r basename`

Notez également que j'ai ajouté des guillemets autour $MY_DIR. De cette façon, la commande n'échouera pas si $MY_DIRcontient des espaces.

Si vous utilisez un shell moderne tel que bash, vous devez utiliser un $( )shell de capture au lieu de backticks. Vous devriez également envisager de changer le style de vos variables. Vous devez généralement éviter d'utiliser des noms de variables en majuscules dans les scripts. Ce style est généralement réservé aux variables réservées et environnementales.

input_file=$(ls -rt "$my_dir"/FILE.*.xml 2> /dev/null | head -1 | xargs -r basename)

N'est-ce pas b0rk s'il n'y a pas de fichiers?
Mel Boyce

En fait: ça ls -rt GLOB 2>/dev/null | head -n1 | xargs -r basenamemarche.
Mel Boyce

@MelBoyce: J'ai ajouté cela à ma réponse. Merci.

ls est un outil pour consulter de manière interactive les informations sur les fichiers. Sa sortie est formatée pour les humains et provoquera des bugs dans les scripts. Utilisez des globes ou recherchez plutôt. Comprenez pourquoi: mywiki.wooledge.org/ParsingLs
cinelli

@cinelli: C'est vrai. Je répondais juste à la question.

9

Dans un pipeline, toutes les commandes sont démarrées et exécutées simultanément, pas l'une après l'autre. Vous devez donc stocker la sortie quelque part.

if ls_output=$(ls -rtd -- "$MY_DIR"/FILE.*.xml); then
  first_file=$(printf '%s\n' "$ls_output" | head -n 1)
  first_file_name=$(basename -- "$first_file")
fi

Notez qu'il suppose que les noms de fichiers ne contiennent pas de caractères de nouvelle ligne. L'utilisation xargssignifierait également des problèmes avec les caractères vides, les guillemets simples et doubles et les barres obliques inverses. Laisser des variables sans guillemets signifierait des problèmes d'espace, de tabulation et de caractères génériques. Oublier --signifierait des problèmes avec les noms de fichiers commençant par -.

Pour obtenir le nom de base du fichier le plus ancien avec zshsans aucune de ces restrictions sur les caractères (évite également le problème de la taille limitée des arguments d'une commande):

 first_file_name=($MY_DIR/FILE.*.xml(Om[1]:t))

S'il n'y a pas de correspondance, cette commande échouera et abandonnera le script. Vous pouvez également faire:

 first_file_name=($MY_DIR/FILE.*.xml(NOm[1]:t))

Dans ce cas, le first_file_nametableau contiendra 1 élément s'il y a correspondance ou 0 sinon. Vous pouvez alors faire:

 if (($#first_file_name)); then
    printf 'Match: %s\n' $first_file_name
 else
    echo >&2 No match
 fi

4

Recherchez le dernier fichier modifié dans un répertoire:

latest() {
  local file path=${1:-.} ext=${2-} latest
  for file in "${path%/}"/*"$ext"; do 
    [[ $file -nt $latest ]] && latest=$file
  done
  [[ $latest ]] && printf '%s\n' "$latest"
}

Usage: latest [directory/path/ [.extension]]

Au lieu d'appeler à basename, utilisez l'expansion des paramètres.

in_file=$(latest in/my/dir .xml)
base_fn=${in_file##*/} base_fn=${base_fn%.*}

Dans un répertoire avec ces contenus:

foo.xml bar.xml baz.xml newest.xml

Le contenu de la variable base_fn serait: newest

Pour l'utiliser correctement afin de répondre à l'objectif de votre demande:

check_dir=/path/to/check
check_ext=.xml
if in_file=$(latest "$check_dir" "$check_ext"); then
  base_fn=${in_file##*/} base_fn=${base_fn%.*}
else
  printf '%s\n' "No file found in $check_dir" >&2
fi

EDIT: après examen de la question, j'ai réalisé que la lscommande en question recherche le fichier le plus ancien dans un répertoire. cette même fonction pourrait être renommée oldestet avoir [[ $file -ot $oldest ]] && oldest=$filepour obtenir le même effet. excuses pour toute confusion.

la chose importante à noter est que vous ne devez absolument, en aucun cas dans l’humanité, analyser la sortie de ls. jamais.


Notez que contrairement aux lsapproches basées sur des liens symboliques, s'il y a des liens symboliques, le temps de modification de la cible des liens symboliques sera pris en compte (ajoutez l' -Loption pour lsy obtenir le même comportement).
Stéphane Chazelas

0

Je pense que la meilleure option ici est:

ls -rf $MY_DIR/FILE.*.xml
if [ $? -eq 0 ]; then
      INPUT_FILE=`ls -rt $MY_DIR/FILE.*.xml|head -1|xargs basename `
else
      echo "Error!"
fi

s'il n'y a pas de fichier alors le code retour de ls est 2 mais s'il trouve un fichier sera 0.


2
Au lieu de comparer $?contre 0, vous pouvez le faire: if ls -rf $MY_DIR/FILE.*.xml ; then.

Vous pouvez également rediriger la sortie de la première commande vers /dev/null. ls -rf $MY_DIR/FILE.*.xml &> /dev/null. De cette façon, l'utilisateur ne verra pas la sortie supplémentaire.
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.